diff --git a/.cursor/ARCHITECTURE.md b/.cursor/ARCHITECTURE.md index 8d6aa4e2..82880018 100644 --- a/.cursor/ARCHITECTURE.md +++ b/.cursor/ARCHITECTURE.md @@ -10,16 +10,16 @@ │ INTERNET │ └─────────────────────────────────────────────────────────────────────────┘ │ - ┌───────────────────────────┼───────────────────────────┐ - │ │ │ - ▼ ▼ ▼ -┌───────────────┐ ┌───────────────┐ ┌───────────────┐ -│ Admin SPA │ │ Organizer │ │ Portal SPA │ -│ (Super Admin)│ │ SPA (Main) │ │ (External) │ -│ :5173 │ │ :5174 │ │ :5175 │ -└───────┬───────┘ └───────┬───────┘ └───────┬───────┘ - │ │ │ - └───────────────────────────┼───────────────────────────┘ + ┌───────────────┼───────────────┐ + │ │ + ▼ ▼ + ┌───────────────┐ ┌───────────────┐ + │ Organizer + │ │ Portal SPA │ + │ Admin SPA │ │ (External) │ + │ :5174 │ │ :5175 │ + └───────┬───────┘ └───────┬───────┘ + │ │ + └───────────────┼───────────────┘ │ CORS + Sanctum tokens ▼ ┌───────────────────────┐ @@ -38,32 +38,17 @@ └───────────┘ └───────────┘ └───────────┘ ``` -**Golden Rule:** Laravel is exclusively a JSON REST API. No Blade views, no Mix, no Inertia. Every response is `application/json`. Vue handles ALL UI via three SPAs. +**Golden Rule:** Laravel is exclusively a JSON REST API. No Blade views, no Mix, no Inertia. Every response is `application/json`. Vue handles ALL UI via two SPAs. --- ## Applications -### Admin Dashboard (`apps/admin/`) - -**Purpose**: Super Admin platform management. - -**Users**: Platform owner only (super_admin role). - -**Features**: -- Organisation management (CRUD, billing status) -- Platform user management -- Global settings - -**Vuexy Version**: `typescript-version/full-version` - ---- - ### Organizer App (`apps/app/`) -**Purpose**: Main application for event management per organisation. +**Purpose**: Main application for event management per organisation. Also serves as the platform admin interface for `super_admin` users via `/platform/*` routes. -**Users**: Organisation Admins, Event Managers, Staff Coordinators, Artist Managers, Volunteer Coordinators. +**Users**: Organisation Admins, Event Managers, Staff Coordinators, Artist Managers, Volunteer Coordinators, Super Admins (platform management via `/platform/*`). **Features**: - Event lifecycle management (Draft through Closed) @@ -77,6 +62,7 @@ - Form builder with conditional logic - Supplier & production management - Reporting & insights +- Platform admin: organisation management, billing, platform users (`/platform/*` routes, `super_admin` only) **Vuexy Version**: `typescript-version/full-version` (customized navigation) @@ -424,15 +410,14 @@ POST /portal/production-request ## Security & CORS -Three frontend origins in `config/cors.php` (via env): +Two frontend origins in `config/cors.php` (via env): | App | Dev URL | Env Variable | |-----|---------|--------------| -| Admin | `http://localhost:5173` | `FRONTEND_ADMIN_URL` | | App | `http://localhost:5174` | `FRONTEND_APP_URL` | | Portal | `http://localhost:5175` | `FRONTEND_PORTAL_URL` | -Production (registered domain **crewli.app**): API `https://api.crewli.app` (`APP_URL`); SPAs `https://admin.crewli.app`, `https://app.crewli.app`, `https://portal.crewli.app` via the same env keys. Frontends use `VITE_API_URL=https://api.crewli.app/api/v1`. `SANCTUM_STATEFUL_DOMAINS` = comma-separated SPA hostnames only (e.g. `admin.crewli.app,app.crewli.app,portal.crewli.app`). **`crewli.nl`** is reserved for a future marketing site only — not used for this application stack. +Production (registered domain **crewli.app**): API `https://api.crewli.app` (`APP_URL`); SPAs `https://crewli.app`, `https://portal.crewli.app` via the same env keys. Frontends use `VITE_API_URL=https://api.crewli.app/api/v1`. `SANCTUM_STATEFUL_DOMAINS` = comma-separated SPA hostnames only (e.g. `crewli.app,portal.crewli.app`). **`crewli.nl`** is reserved for a future marketing site only — not used for this application stack. --- diff --git a/.cursor/instructions.md b/.cursor/instructions.md index 162784e6..b0a2ae71 100644 --- a/.cursor/instructions.md +++ b/.cursor/instructions.md @@ -20,8 +20,7 @@ Crewli is a multi-tenant SaaS platform for professional event and festival manag | Component | Technology | Location | Port | |-----------|------------|----------|------| | API | Laravel 12 + Sanctum + Spatie Permission | `api/` | 8000 | -| Admin (Super Admin) | Vue 3 + Vuexy (full) | `apps/admin/` | 5173 | -| Organizer App (Main) | Vue 3 + Vuexy (full) | `apps/app/` | 5174 | +| Organizer + Admin App (Main) | Vue 3 + Vuexy (full) | `apps/app/` | 5174 | | Portal (External) | Vue 3 + Vuexy (stripped) | `apps/portal/` | 5175 | | Database | MySQL 8 | Docker | 3306 | | Cache / Queues | Redis | Docker | 6379 | @@ -205,8 +204,7 @@ make services-stop # Stop services ### Development Servers ```bash make api # Laravel on :8000 -make admin # Admin SPA on :5173 -make app # Organizer SPA on :5174 +make app # Organizer + Admin SPA on :5174 make portal # Portal SPA on :5175 ``` diff --git a/.cursor/rules/001_workspace.mdc b/.cursor/rules/001_workspace.mdc index dedacb9d..dd1256e9 100644 --- a/.cursor/rules/001_workspace.mdc +++ b/.cursor/rules/001_workspace.mdc @@ -123,8 +123,7 @@ crewli/ | Service | URL | Env Variable | |---------|-----|--------------| | API | `http://localhost:8000/api/v1` | - | -| Admin SPA | `http://localhost:5173` | `FRONTEND_ADMIN_URL` | -| Organizer SPA | `http://localhost:5174` | `FRONTEND_APP_URL` | +| Organizer + Admin SPA | `http://localhost:5174` | `FRONTEND_APP_URL` | | Portal SPA | `http://localhost:5175` | `FRONTEND_PORTAL_URL` | | MySQL | `localhost:3306` | - | | Redis | `localhost:6379` | - | @@ -137,12 +136,11 @@ crewli/ | Service | URL | Env variable | |---------|-----|--------------| | API | `https://api.crewli.app` | `APP_URL` | -| Admin SPA | `https://admin.crewli.app` | `FRONTEND_ADMIN_URL` | -| Organizer SPA | `https://app.crewli.app` | `FRONTEND_APP_URL` | +| Organizer + Admin SPA | `https://crewli.app` | `FRONTEND_APP_URL` | | Portal SPA | `https://portal.crewli.app` | `FRONTEND_PORTAL_URL` | ### CORS -Three frontend origins configured in `config/cors.php` via env variables. Each Vite dev server gets its own port for CORS isolation. In production, set the same env vars to the `https://…` origins above (see `api/.env.example`). +Two frontend origins configured in `config/cors.php` via env variables. Each Vite dev server gets its own port for CORS isolation. In production, set the same env vars to the `https://...` origins above (see `api/.env.example`). ## Git Conventions diff --git a/.cursor/rules/101_vue.mdc b/.cursor/rules/101_vue.mdc index 8327393e..9235c9ec 100644 --- a/.cursor/rules/101_vue.mdc +++ b/.cursor/rules/101_vue.mdc @@ -18,15 +18,12 @@ alwaysApply: true ## App-Specific Rules -### `apps/admin/` (Super Admin) -- Full Vuexy template unchanged (sidebar, dark mode, customizer) -- Minimal modifications needed - -### `apps/app/` (Organizer - Main App) +### `apps/app/` (Organizer + Platform Admin - Main App) - Sidebar nav customized for Crewli structure - Remove Vuexy demo/customizer components - Full Vuetify component usage - 90% of development work happens here +- Super admin functionality under `/platform/*` routes for `super_admin` users ### `apps/portal/` (External Portal) - Stripped Vuexy: no sidebar, no customizer, no dark mode toggle diff --git a/.cursor/rules/102_multi_tenancy.mdc b/.cursor/rules/102_multi_tenancy.mdc index 15bee8ea..55ca8961 100644 --- a/.cursor/rules/102_multi_tenancy.mdc +++ b/.cursor/rules/102_multi_tenancy.mdc @@ -189,14 +189,13 @@ class PortalTokenMiddleware ```php // config/cors.php 'allowed_origins' => [ - env('FRONTEND_ADMIN_URL', 'http://localhost:5173'), env('FRONTEND_APP_URL', 'http://localhost:5174'), env('FRONTEND_PORTAL_URL', 'http://localhost:5175'), ], 'supports_credentials' => true, ``` -Production example (subdomains on **crewli.app**): `FRONTEND_ADMIN_URL=https://admin.crewli.app`, `FRONTEND_APP_URL=https://app.crewli.app`, `FRONTEND_PORTAL_URL=https://portal.crewli.app`, and `SANCTUM_STATEFUL_DOMAINS=admin.crewli.app,app.crewli.app,portal.crewli.app`. +Production example (subdomains on **crewli.app**): `FRONTEND_APP_URL=https://crewli.app`, `FRONTEND_PORTAL_URL=https://portal.crewli.app`, and `SANCTUM_STATEFUL_DOMAINS=crewli.app,portal.crewli.app`. ## Shift Claiming & Approval Flow diff --git a/dev-docs/AUTH_ARCHITECTURE.md b/dev-docs/AUTH_ARCHITECTURE.md index 555771d6..8eddc7aa 100644 --- a/dev-docs/AUTH_ARCHITECTURE.md +++ b/dev-docs/AUTH_ARCHITECTURE.md @@ -7,14 +7,13 @@ ## 1. Authentication Overview -Crewli uses **stateless token-based authentication** via Laravel Sanctum. Three SPA clients communicate with a single REST API. Tokens are stored exclusively in **httpOnly cookies** set by the server — they are never exposed to JavaScript via response bodies, localStorage, or JS-readable cookies. +Crewli uses **stateless token-based authentication** via Laravel Sanctum. Two SPA clients communicate with a single REST API. Tokens are stored exclusively in **httpOnly cookies** set by the server — they are never exposed to JavaScript via response bodies, localStorage, or JS-readable cookies. ### Client Applications | App | URL (dev) | URL (prod) | Purpose | |-----|-----------|------------|---------| -| Admin | localhost:5173 | admin.crewli.app | Super admin / platform management | -| App | localhost:5174 | app.crewli.app | Organiser dashboard | +| App | localhost:5174 | crewli.app | Organiser dashboard + platform admin (`/platform/*` for super_admin) | | Portal | localhost:5175 | portal.crewli.app | Volunteers, artists, suppliers | ### Access Modes @@ -30,7 +29,6 @@ The Portal supports two access modes: | App | Cookie Name | Domain | Secure | httpOnly | SameSite | Max-Age | |-----|-------------|--------|--------|----------|----------|---------| -| Admin | `crewli_admin_token` | `.crewli.app` (prod) / `localhost` (dev) | Yes (prod) | Yes | Strict | 7 days | | App | `crewli_app_token` | `.crewli.app` (prod) / `localhost` (dev) | Yes (prod) | Yes | Strict | 7 days | | Portal | `crewli_portal_token` | `.crewli.app` (prod) / `localhost` (dev) | Yes (prod) | Yes | Strict | 7 days | @@ -57,7 +55,7 @@ The `CookieBearerToken` middleware (registered before `auth:sanctum` in the API 3. Reads only that cookie and sets `Authorization: Bearer` on the request 4. Sanctum's existing token validation processes the header normally -**Cross-app isolation:** In local development, all three SPAs share `localhost` (different ports). Browsers do not scope cookies by port, so all three app cookies are sent with every API request. The middleware prevents cross-app authentication by only reading the cookie that matches the requesting app's Origin header. Without this, logging into one app would authenticate all apps. +**Cross-app isolation:** In local development, both SPAs share `localhost` (different ports). Browsers do not scope cookies by port, so both app cookies are sent with every API request. The middleware prevents cross-app authentication by only reading the cookie that matches the requesting app's Origin header. Without this, logging into one app would authenticate the other. If the `Origin` header is absent (e.g. server-to-server requests), the middleware falls back to the first available cookie. If an `Authorization` header is already present (e.g. from the portal token flow), the middleware skips cookie injection entirely. @@ -170,7 +168,6 @@ Request | Setting | Location | Purpose | |---------|----------|---------| | `SESSION_DOMAIN` | `.env` | Cookie domain (`.crewli.app` in prod, `localhost` in dev) | -| `FRONTEND_ADMIN_URL` | `.env` / `config/app.php` | Admin SPA origin (cookie name resolution + CORS) | | `FRONTEND_APP_URL` | `.env` / `config/app.php` | App SPA origin | | `FRONTEND_PORTAL_URL` | `.env` / `config/app.php` | Portal SPA origin | | `sanctum.expiration` | `config/sanctum.php` | Token TTL (7 days = 10080 minutes) | diff --git a/dev-docs/BACKLOG.md b/dev-docs/BACKLOG.md index 5921ff26..fc169679 100644 --- a/dev-docs/BACKLOG.md +++ b/dev-docs/BACKLOG.md @@ -351,15 +351,9 @@ voor third-party integraties (ticketing, HR, etc.) ## Apps & Platforms -### APPS-01 — apps/admin/ volledig bouwen +### ~~APPS-01 — apps/admin/ volledig bouwen~~ RETIRED -**Aanleiding:** Super Admin panel voor platform-beheer. -**Wat:** - -- Alle organisaties beheren -- Billing status wijzigen -- Platform-gebruikers beheren -- Usage statistieken +**Status:** Retired — admin SPA (`apps/admin/`) is afgeschaft. Super admin functionaliteit is verplaatst naar `apps/app/` onder `/platform/*` routes voor `super_admin` gebruikers. --- diff --git a/dev-docs/MASTER_PROMPT_CC.md b/dev-docs/MASTER_PROMPT_CC.md index 8a0f940e..b680ac8f 100644 --- a/dev-docs/MASTER_PROMPT_CC.md +++ b/dev-docs/MASTER_PROMPT_CC.md @@ -17,9 +17,8 @@ Crewli is a multi-tenant SaaS platform for event and festival management. - **Backend:** Laravel 12 REST API (no Blade views, no Inertia), Sanctum auth, Spatie Permission, MySQL 8, Redis -- **Frontend:** Three standalone Vue 3 + TypeScript SPAs on Vuexy 9.5 / - Vuetify 3.10 — `apps/admin/` (port 5173), `apps/app/` (port 5174), - `apps/portal/` (port 5175) +- **Frontend:** Two standalone Vue 3 + TypeScript SPAs on Vuexy 9.5 / + Vuetify 3.10 — `apps/app/` (port 5174), `apps/portal/` (port 5175) - **State:** Pinia + TanStack Vue Query - **Forms:** VeeValidate + Zod - **API base path:** `/api/v1/` @@ -47,8 +46,7 @@ crewli/ │ │ └── seeders/ │ └── tests/Feature/Api/V1/ ├── apps/ -│ ├── admin/ # Super Admin SPA -│ ├── app/ # Organizer SPA (main app) +│ ├── app/ # Organizer + Platform Admin SPA (main app) │ │ └── src/ │ │ ├── lib/axios.ts # THE ONLY axios instance │ │ ├── composables/api/ # TanStack Query composables diff --git a/dev-docs/MASTER_PROMPT_CURSOR.md b/dev-docs/MASTER_PROMPT_CURSOR.md index 2a72168b..f3956f08 100644 --- a/dev-docs/MASTER_PROMPT_CURSOR.md +++ b/dev-docs/MASTER_PROMPT_CURSOR.md @@ -15,9 +15,8 @@ Crewli — multi-tenant SaaS for event/festival management. - **Frontend:** Vue 3 + TypeScript + Vuexy 9.5 (Vuetify 3.10) + Pinia + TanStack Vue Query + VeeValidate + Zod -- **Three SPAs:** - - `apps/admin/` — Super Admin (port 5173) - - `apps/app/` — Organizer main app (port 5174) +- **Two SPAs:** + - `apps/app/` — Organizer + Platform Admin main app (port 5174) - `apps/portal/` — Dual-mode portal (port 5175) - **API base:** `VITE_API_URL` from `.env.local` (default `http://localhost:8000`) - **Axios instance:** `src/lib/axios.ts` — this is the ONLY axios instance. diff --git a/dev-docs/SECURITY_AUDIT.md b/dev-docs/SECURITY_AUDIT.md index b4e21166..cc734c7b 100644 --- a/dev-docs/SECURITY_AUDIT.md +++ b/dev-docs/SECURITY_AUDIT.md @@ -5,7 +5,7 @@ - **Total findings: 57** - **Critical: 10 | High: 19 | Medium: 18 | Low: 10** -Audit scope: all files under `api/` and `apps/` (admin, app, portal). +Audit scope: all files under `api/` and `apps/` (app, portal). --- @@ -337,7 +337,7 @@ Audit scope: all files under `api/` and `apps/` (admin, app, portal). #### [MEDIUM] A05-3: Session `SameSite=Lax` may break cross-subdomain Sanctum cookie auth - **File:** `api/config/session.php:202` -- **Description:** With three SPAs on different subdomains calling `api.crewli.app`, `SameSite=Lax` blocks session cookies on cross-origin requests. +- **Description:** With two SPAs on different subdomains calling `api.crewli.app`, `SameSite=Lax` blocks session cookies on cross-origin requests. - **Risk:** Sanctum SPA authentication may fail silently in production. - **Fix:** Set `SESSION_DOMAIN=.crewli.app` and `SESSION_SAME_SITE=none` (with `SESSION_SECURE_COOKIE=true`) in production. @@ -364,7 +364,7 @@ Audit scope: all files under `api/` and `apps/` (admin, app, portal). #### Positive findings (A05) -- CORS `allowed_origins` correctly restricted to three specific SPAs (not `*`). +- CORS `allowed_origins` correctly restricted to two specific SPAs (not `*`). - `supports_credentials: true` correctly set for Sanctum. - `APP_DEBUG` defaults to `false` in `config/app.php`. - No Telescope/Horizon routes found (not installed). @@ -378,21 +378,21 @@ Audit scope: all files under `api/` and `apps/` (admin, app, portal). #### [CRITICAL] A06-1: `@casl/ability` 6.7.3 — Prototype Pollution -- **File:** `apps/*/package.json` -- **Description:** CASL is used for frontend permission enforcement across all three apps. Prototype pollution could corrupt ability checks. +- **File:** `apps/app/package.json`, `apps/portal/package.json` +- **Description:** CASL is used for frontend permission enforcement across both apps. Prototype pollution could corrupt ability checks. - **Risk:** Permission bypass in the frontend. Admin tokens at elevated risk. -- **Fix:** Upgrade to `>=6.7.5` in all three apps. +- **Fix:** Upgrade to `>=6.7.5` in both apps. #### [CRITICAL] A06-2: `axios` ~1.13.x — SSRF and metadata exfiltration -- **File:** `apps/*/package.json` +- **File:** `apps/app/package.json`, `apps/portal/package.json` - **Description:** Two critical CVEs: `NO_PROXY` hostname normalization bypass (SSRF) and unrestricted cloud metadata exfiltration via header injection. - **Risk:** Cloud metadata theft, SSRF in SSR contexts. -- **Fix:** Upgrade to `>=1.15.0` in all three apps. +- **Fix:** Upgrade to `>=1.15.0` in both apps. #### [CRITICAL] A06-3: `swiper` 11.2.10 — Prototype Pollution -- **File:** `apps/*/package.json` +- **File:** `apps/app/package.json`, `apps/portal/package.json` - **Description:** Prototype pollution vulnerability. - **Risk:** Client-side code execution. - **Fix:** Upgrade to `>=12.1.2` (major version bump — review migration guide). @@ -420,7 +420,7 @@ Audit scope: all files under `api/` and `apps/` (admin, app, portal). #### [LOW] A06-7: Frontend dev dependencies — 45 vulnerabilities in build tools -- **File:** `apps/*/pnpm-lock.yaml` +- **File:** `apps/app/pnpm-lock.yaml`, `apps/portal/pnpm-lock.yaml` - **Description:** `vite`, `rollup`, `tar`, `undici`, `minimatch`, `svgo`, `flatted`, `immutable`, `lodash`, `picomatch` — various high/moderate vulnerabilities in dev/build-time dependencies. - **Risk:** Not shipped to production. CI/CD pipeline risk. - **Fix:** Update dev dependencies: `pnpm update` for each app. @@ -554,58 +554,51 @@ Audit scope: all files under `api/` and `apps/` (admin, app, portal). #### [CRITICAL] A13-1: Bearer tokens stored in `localStorage` (apps/app and apps/portal) -- **File:** `apps/app/src/stores/useAuthStore.ts:7,10,30,66` -- **File:** `apps/portal/src/stores/useAuthStore.ts:6,9,18,20` +- **File:** `apps/app/src/stores/useAuthStore.ts` +- **File:** `apps/portal/src/stores/useAuthStore.ts` - **Description:** Sanctum bearer tokens stored in `localStorage` under `crewli_token` and `crewli_portal_token`. Accessible to any JavaScript on the page. - **Risk:** Any XSS vulnerability (or supply-chain attack) can steal tokens and impersonate users indefinitely. - **Fix:** Migrate to `httpOnly; Secure; SameSite=Strict` cookies set by the Laravel backend. Remove `setItem`/`getItem` usage. -#### [HIGH] A13-2: Admin app cookies lack `httpOnly`, `Secure`, and `SameSite` flags +#### ~~[HIGH] A13-2: Admin app cookies lack `httpOnly`, `Secure`, and `SameSite` flags~~ RETIRED -- **File:** `apps/admin/src/@core/composable/useCookie.ts:38-43` -- **Description:** Token, user data, and ability rules stored in cookies written via `document.cookie` with no `httpOnly`, `secure`, or `sameSite`. -- **Risk:** XSS token theft and CSRF. -- **Fix:** Refactor to have the API set httpOnly cookies via `Set-Cookie` response header. Add `sameSite: 'strict'` as interim measure. +- **File:** `apps/admin/` (removed) +- **Description:** The admin SPA has been retired. Its functionality now lives in `apps/app/` under `/platform/*` routes. +- **Resolution:** Finding no longer applicable — `apps/admin/` has been removed. #### [HIGH] A13-3: Open redirect vulnerability on post-login redirect (all apps) - **File:** `apps/portal/src/pages/login.vue:61,74-76` - **File:** `apps/app/src/pages/login.vue:55` -- **File:** `apps/admin/src/pages/login.vue:97` - **Description:** All login pages accept `?to=` query parameter and redirect to it after login without validating it's a relative path. Portal falls back to `window.location.href` with the raw value. - **Risk:** Phishing: `https://portal.crewli.app/login?to=https://evil.com/steal`. - **Fix:** Validate that redirect target starts with `/` before using it. -#### [HIGH] A13-4: `v-html` with API-sourced data in admin app (template pages) +#### ~~[HIGH] A13-4: `v-html` with API-sourced data in admin app (template pages)~~ RETIRED -- **File:** `apps/admin/src/pages/front-pages/help-center/article/[title].vue:59-62` -- **File:** `apps/admin/src/pages/apps/academy/course-details.vue:154` -- **Description:** Template/demo pages from Vuexy render API data with `v-html` — stored XSS vector. -- **Risk:** XSS within the admin app where users have super_admin privileges. -- **Fix:** Remove these template demo pages entirely, or sanitize with DOMPurify. +- **File:** `apps/admin/` (removed) +- **Description:** The admin SPA has been retired. Its functionality now lives in `apps/app/` under `/platform/*` routes. +- **Resolution:** Finding no longer applicable — `apps/admin/` has been removed. -#### [HIGH] A13-5: Admin router guard relies on JS-readable cookie without server validation +#### ~~[HIGH] A13-5: Admin router guard relies on JS-readable cookie without server validation~~ RETIRED -- **File:** `apps/admin/src/plugins/1.router/guards.ts:40-42` -- **Description:** Guard reads `userData`/`accessToken` from `document.cookie`. Does not call API to validate — just checks truthy value. -- **Risk:** An XSS attacker who steals cookies bypasses the guard. Stale or tampered cookies pass validation. -- **Fix:** Use the `authStore.initialize()` pattern from the app/portal guards (server-validates token on init). +- **File:** `apps/admin/` (removed) +- **Description:** The admin SPA has been retired. Its functionality now lives in `apps/app/` under `/platform/*` routes. +- **Resolution:** Finding no longer applicable — `apps/admin/` has been removed. #### [MEDIUM] A13-6: Server error messages forwarded to UI -- **File:** `apps/admin/src/pages/events/create.vue:74` - **File:** `apps/app/src/pages/login.vue:59-69` - **File:** `apps/portal/src/pages/login.vue:51` - **Description:** Raw `data.message` from API errors rendered in UI. Could expose internal details if backend misconfigured. - **Risk:** Information disclosure (table names, file paths). - **Fix:** Use safelist-based error mapping. The portal's `mapLoginErrorMessage` is a good pattern — extend it. -#### [MEDIUM] A13-7: Admin `main.ts` mount-error handler uses `innerHTML` interpolation +#### ~~[MEDIUM] A13-7: Admin `main.ts` mount-error handler uses `innerHTML` interpolation~~ RETIRED -- **File:** `apps/admin/src/main.ts:41` -- **Description:** Error fallback injects `error` object via template string into `innerHTML`. -- **Risk:** XSS if error message is attacker-influenced. -- **Fix:** Use `document.createTextNode(String(error))` instead. +- **File:** `apps/admin/` (removed) +- **Description:** The admin SPA has been retired. Its functionality now lives in `apps/app/` under `/platform/*` routes. +- **Resolution:** Finding no longer applicable — `apps/admin/` has been removed. #### [MEDIUM] A13-8: Portal localStorage event state persists across session expiry @@ -616,8 +609,8 @@ Audit scope: all files under `api/` and `apps/` (admin, app, portal). #### [LOW] A13-9: No Content Security Policy on any SPA — RESOLVED -- **File:** `apps/*/index.html`, `api/app/Http/Middleware/SecurityHeaders.php`, `deploy/nginx/` -- **Description:** ~~None of the three apps set a CSP meta tag or header.~~ +- **File:** `apps/app/index.html`, `apps/portal/index.html`, `api/app/Http/Middleware/SecurityHeaders.php`, `deploy/nginx/` +- **Description:** ~~Neither app set a CSP meta tag or header.~~ - **Risk:** Injected scripts have unrestricted access. - **Resolution:** API CSP enforced via `SecurityHeaders` middleware (`default-src 'none'; frame-ancestors 'none'`). SPA CSP configured via Nginx snippets (`deploy/nginx/csp-spa.conf`, `csp-portal.conf`). Dev CSP meta tags added to all `index.html` files for local testing. See `deploy/README.md` for rollout instructions. @@ -632,7 +625,7 @@ Audit scope: all files under `api/` and `apps/` (admin, app, portal). The following security measures ARE correctly implemented: 1. **Password hashing:** bcrypt with `BCRYPT_ROUNDS=12`. `$password` in User model's `$hidden` array. -2. **CORS origins:** Correctly restricted to three specific SPA origins (not `*`). `supports_credentials: true` set. +2. **CORS origins:** Correctly restricted to two specific SPA origins (not `*`). `supports_credentials: true` set. 3. **SQL injection prevention:** All Eloquent queries use PDO parameter binding. All `whereRaw()`/`selectRaw()` calls use `?` placeholders. 4. **No shell execution:** No `exec()`, `shell_exec()`, `system()`, `proc_open()`, or `passthru()` in application code. 5. **Blade escaping:** All Blade output uses `{{ }}` (escaped). Zero instances of `{!! !!}`. @@ -685,5 +678,5 @@ The following security measures ARE correctly implemented: | 17 | A01-13: Move event routes under org prefix | Large | | 18 | A09-1/2/3: Add security logging | Medium | | 19 | A07-3: Strengthen password rules | Small | -| 20 | A13-2/5: Fix admin cookie security | Medium | +| 20 | ~~A13-2/5: Fix admin cookie security~~ RETIRED (apps/admin removed) | N/A | | 21 | A06-3: Upgrade swiper (major version) | Medium | diff --git a/dev-docs/SETUP.md b/dev-docs/SETUP.md index a44e8b4d..3dd923f2 100644 --- a/dev-docs/SETUP.md +++ b/dev-docs/SETUP.md @@ -82,7 +82,7 @@ Requirements: - Use the command: composer create-project laravel/laravel api - After creation, install Sanctum: composer require laravel/sanctum - Configure for API-only (we don't need web routes) -- Set up CORS for localhost:5173, localhost:5174, localhost:5175 +- Set up CORS for localhost:5174, localhost:5175 - Use MySQL with these credentials: - Host: 127.0.0.1 - Database: crewli @@ -111,33 +111,26 @@ DB_DATABASE=crewli DB_USERNAME=crewli DB_PASSWORD=secret -FRONTEND_ADMIN_URL=http://localhost:5173 FRONTEND_APP_URL=http://localhost:5174 FRONTEND_PORTAL_URL=http://localhost:5175 -SANCTUM_STATEFUL_DOMAINS=localhost:5173,localhost:5174,localhost:5175 +SANCTUM_STATEFUL_DOMAINS=localhost:5174,localhost:5175 SESSION_DOMAIN=localhost ``` -**Production (domain `crewli.app`):** set `APP_URL=https://api.crewli.app`, point `FRONTEND_ADMIN_URL` / `FRONTEND_APP_URL` / `FRONTEND_PORTAL_URL` to `https://admin.crewli.app`, `https://app.crewli.app`, and `https://portal.crewli.app`, and `SANCTUM_STATEFUL_DOMAINS=admin.crewli.app,app.crewli.app,portal.crewli.app` (hostnames only). Each SPA build should use `VITE_API_URL=https://api.crewli.app/api/v1`. Full template: `api/.env.example`. The product uses **`crewli.app`** only; **`crewli.nl`** is for a future public marketing site, not this API or SPAs. +**Production (domain `crewli.app`):** set `APP_URL=https://api.crewli.app`, point `FRONTEND_APP_URL` / `FRONTEND_PORTAL_URL` to `https://crewli.app` and `https://portal.crewli.app`, and `SANCTUM_STATEFUL_DOMAINS=crewli.app,portal.crewli.app` (hostnames only). Each SPA build should use `VITE_API_URL=https://api.crewli.app/api/v1`. Full template: `api/.env.example`. The product uses **`crewli.app`** only; **`crewli.nl`** is for a future public marketing site, not this API or SPAs. --- ## Step 3: Vuexy frontends (this repo) -This monorepo already contains three SPAs under `apps/`: +This monorepo already contains two SPAs under `apps/`: | Directory | Role | Typical Vuexy source | |-----------|------|----------------------| -| `apps/admin/` | Super Admin | full-version (TypeScript) | -| `apps/app/` | Organizer (main product) | full-version or customized starter | +| `apps/app/` | Organizer + Platform Admin (main product) | full-version (TypeScript) | | `apps/portal/` | External portal (volunteers, token links) | stripped starter / custom layout | -If you ever need to re-copy from a Vuexy ZIP, use the paths above — not legacy `apps/band` or `apps/customers`. - -```bash -# Example only — adjust to your Vuexy download path -cp -r ~/Downloads/vuexy/typescript-version/full-version/* apps/admin/ -``` +Super admin functionality lives in `apps/app/` under `/platform/*` routes, accessible to `super_admin` users. --- @@ -146,19 +139,12 @@ cp -r ~/Downloads/vuexy/typescript-version/full-version/* apps/admin/ ### Install Dependencies ```bash -cd apps/admin && pnpm install -cd ../app && pnpm install +cd apps/app && pnpm install cd ../portal && pnpm install ``` ### Create Environment Files -**apps/admin/.env.local** -```env -VITE_API_URL=http://localhost:8000/api/v1 -VITE_APP_NAME="Crewli Admin" -``` - **apps/app/.env.local** ```env VITE_API_URL=http://localhost:8000/api/v1 @@ -173,13 +159,13 @@ VITE_APP_NAME="Crewli Portal" ### Dev server ports -From the repo root, `make admin`, `make app`, and `make portal` start Vite on **5173**, **5174**, and **5175** respectively. If you run `pnpm dev` manually, configure the same ports in each app’s `vite.config.ts` under `server.port`. +From the repo root, `make app` and `make portal` start Vite on **5174** and **5175** respectively. If you run `pnpm dev` manually, configure the same ports in each app’s `vite.config.ts` under `server.port`. --- ## Step 5: API client in SPAs -`apps/admin/src/lib/api-client.ts`, `apps/app/src/lib/api-client.ts`, and `apps/portal/src/lib/api-client.ts` share the same pattern: `VITE_API_URL` base, Bearer token from the `accessToken` cookie, 401 → clear cookies and redirect to `/login`. Build new composables on `apiClient`; keep Vuexy `useApi` for template demos only. +`apps/app/src/lib/api-client.ts` and `apps/portal/src/lib/api-client.ts` share the same pattern: `VITE_API_URL` base, Bearer token from the `accessToken` cookie, 401 → clear cookies and redirect to `/login`. Build new composables on `apiClient`; keep Vuexy `useApi` for template demos only. --- @@ -214,13 +200,10 @@ make services # Tab 2: Laravel API make api -# Tab 3: Admin SPA (optional) -make admin - -# Tab 4: Organizer SPA (optional) +# Tab 3: Organizer SPA (optional) make app -# Tab 5: Portal SPA (optional) +# Tab 4: Portal SPA (optional) make portal ``` @@ -267,7 +250,7 @@ Check `api/config/cors.php` allows your frontend origins. ### Vuexy TypeScript Errors ```bash -cd apps/admin +cd apps/app pnpm install pnpm type-check ``` @@ -278,7 +261,7 @@ pnpm type-check 1. Services running (Docker) 2. Laravel API configured and migrated -3. SPAs installed (`apps/admin`, `apps/app`, `apps/portal`) +3. SPAs installed (`apps/app`, `apps/portal`) 4. Environment files for API + each SPA 5. Authentication and organisation switching 6. Events, sections, time slots, shifts diff --git a/dev-docs/dev-guide.md b/dev-docs/dev-guide.md index 51406536..c145d138 100644 --- a/dev-docs/dev-guide.md +++ b/dev-docs/dev-guide.md @@ -53,8 +53,7 @@ crewli/ # Monorepo root │ │ └── seeders/ │ └── tests/Feature/Api/V1/ # PHPUnit feature tests per controller ├── apps/ -│ ├── admin/ # Super Admin SPA (Vuexy) -│ ├── app/ # Organizer SPA (Vuexy) -- HOOFDAPP +│ ├── app/ # Organizer + Platform Admin SPA (Vuexy) -- HOOFDAPP │ └── portal/ # Externe portals (vrijwilliger, artiest, leverancier) ├── docs/ # Design document, API docs, ERD │ ├── design-document.md @@ -91,7 +90,7 @@ php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvid php artisan vendor:publish --provider="Spatie\LaravelActivitylog\ActivitylogServiceProvider" ``` -**Frontend — alle apps (apps/app/, apps/admin/, apps/portal/)** +**Frontend — alle apps (apps/app/, apps/portal/)** ```bash # TanStack Query voor API state management @@ -103,7 +102,7 @@ npm install vee-validate zod @vee-validate/zod # Drag-and-drop (form builder, timetable, prioriteitsranking) npm install vuedraggable@next -# In apps/app/ en apps/admin/ ook: +# In apps/app/ ook: npm install @fullcalendar/vue3 @fullcalendar/timeline @fullcalendar/resource-timeline ``` @@ -138,8 +137,7 @@ Design Document: /resources/design/design-document.md ## Repository Structuur - api/ Laravel backend -- apps/app/ Organizer SPA (hoofdapp) -- apps/admin/ Super Admin SPA +- apps/app/ Organizer + Platform Admin SPA (hoofdapp, super admin onder /platform/*) - apps/portal/ Externe portals (vrijwilliger, artiest, leverancier) ## Backend Regels (STRIKT VOLGEN) diff --git a/dev-docs/start-guide.md b/dev-docs/start-guide.md index 3a274f53..5405a196 100644 --- a/dev-docs/start-guide.md +++ b/dev-docs/start-guide.md @@ -12,12 +12,12 @@ Dit is de volledige, vastgestelde architectuur van Crewli. Alle beslissingen hie ### 1.1 Systeemoverzicht -| **apps/admin/** | **apps/app/** | **apps/portal/** | -|---|---|---| -| Super Admin SPA | Organizer SPA | Portal SPA | +| **apps/app/** | **apps/portal/** | +|---|---| +| Organizer + Platform Admin SPA | Portal SPA | Vuexy + Vue 3 + TypeScript | Pinia + TanStack Query | Axios → CORS → Sanctum Token -Alle drie apps zijn Vue 3 SPA's — Vuexy template — communiceren uitsluitend via REST API +Beide apps zijn Vue 3 SPA's — Vuexy template — communiceren uitsluitend via REST API **api/ — Laravel 12 REST API (ENIGE backend — geen Blade views)** PHP 8.2 | Sanctum | Spatie Permission | MySQL 8 | Redis | Queue Workers @@ -35,16 +35,14 @@ PHP 8.2 | Sanctum | Spatie Permission | MySQL 8 | Redis | Queue Workers | **App / Laag** | **Technology** | **Gebruik & verantwoordelijkheid** | |----|----|----| | api/ | Laravel 12 + Sanctum | REST API, authenticatie, business logic, database, queue workers, e-mail, PDF-generatie. Geen enkele HTML pagina. | -| apps/admin/ | Vue 3 + Vuexy (vol) | Super Admin SPA: organisations beheren, billing, platform-gebruikers. Klein en eenvoudig. | -| apps/app/ | Vue 3 + Vuexy (vol) | Organizer SPA: de hoofdapp. Events, shifts, persons, artists, briefings, Mission Control. 90% van je werk. | +| apps/app/ | Vue 3 + Vuexy (vol) | Organizer + Platform Admin SPA: de hoofdapp. Events, shifts, persons, artists, briefings, Mission Control. Super admin functionaliteit onder `/platform/*` routes. | | apps/portal/ | Vue 3 + Vuexy (gestript) | Portal SPA: twee toegangsmodi. Login voor vrijwilligers/crew. Token voor artiesten/leveranciers/pers. | ### 1.3 Vuexy — waar en hoe | **App / Laag** | **Technology** | **Gebruik & verantwoordelijkheid** | |----|----|----| -| apps/admin/ | Vuexy volledig | Admin template ongewijzigd: sidebar, dark mode, customizer. Weinig aanpassingen nodig. | -| apps/app/ | Vuexy volledig | Sidebar nav aanpassen voor Crewli-structuur. Customizer/demo-componenten verwijderen. Full Vuetify component gebruik. | +| apps/app/ | Vuexy volledig | Sidebar nav aanpassen voor Crewli-structuur. Customizer/demo-componenten verwijderen. Full Vuetify component gebruik. Super admin functionaliteit onder `/platform/*` routes. | | apps/portal/ | Vuexy gestript | Geen sidebar nav, geen customizer, geen dark mode toggle. Wel: Vuetify componenten, Vuexy SCSS variabelen, Vuexy fonts. Eigen layout: top-bar met event-logo + naam. Mobile-first. | ### 1.4 Portal: twee toegangsmodi @@ -109,11 +107,11 @@ Route::prefix('v1')->group(function () { > **LET OP — CORS** > -> Laravel CORS config (config/cors.php) moet drie origins toestaan: +> Laravel CORS config (config/cors.php) moet twee origins toestaan: > -> admin.crewli.app | app.crewli.app | portal.crewli.app +> crewli.app | portal.crewli.app > -> In development: http://localhost:5173 | :5174 | :5175 +> In development: http://localhost:5174 | :5175 ## 2 — Actielijst: Wat Je Nu Doet @@ -187,17 +185,16 @@ Een centrale axios instance — dubbele laag verwijderen - ☐ Verwijder daarna: src/lib/api-client.ts en src/utils/api.ts - ☐ Hernoem src/composables/useApi.ts → src/composables/useApiHelpers.ts (algemene helpers) -- ☐ Doe hetzelfde voor apps/portal/ en apps/admin/ (zelfde patroon) +- ☐ Doe hetzelfde voor apps/portal/ (zelfde patroon) ### Stap 3 — TanStack Query installeren **NU** Ontbreekt nog — vereist voor alle API state management -- ☐ **Installeer in alle drie apps (app/, admin/, portal/)** +- ☐ **Installeer in beide apps (app/, portal/)** ```sh cd apps/app && pnpm add @tanstack/vue-query - cd ../admin && pnpm add @tanstack/vue-query cd ../portal && pnpm add @tanstack/vue-query ``` @@ -216,7 +213,7 @@ Ontbreekt nog — vereist voor alle API state management }) ``` -- ☐ Installeer ook formuliervalidatie (alle apps) +- ☐ Installeer ook formuliervalidatie (beide apps) ```sh pnpm add vee-validate zod @vee-validate/zod @@ -251,12 +248,11 @@ Spatie packages zijn vereist voor fase 1 # Registreer in bootstrap/app.php als 'portal.token' ``` -- ☐ Update config/cors.php voor drie frontend origins +- ☐ Update config/cors.php voor twee frontend origins ```php // config/cors.php 'allowed_origins' => [ - env('FRONTEND_ADMIN_URL', 'http://localhost:5173'), env('FRONTEND_APP_URL', 'http://localhost:5174'), env('FRONTEND_PORTAL_URL','http://localhost:5175'), ], @@ -285,9 +281,9 @@ CLAUDE.md, .cursorrules, docs/ — dit is het belangrijkste wat je doet - Login-based (auth:sanctum): vrijwilligers, crew — persons met user_id - Token-based (portal.token middleware): artiesten, leveranciers, pers — persons zonder user_id - - apps/ mapping: admin/ = Super Admin, app/ = Organizer, portal/ = Externe gebruikers + - apps/ mapping: app/ = Organizer + Platform Admin (super admin via /platform/*), portal/ = Externe gebruikers - - CORS: drie origins configureren in zowel Laravel als Vite dev server + - CORS: twee origins configureren in zowel Laravel als Vite dev server ### Stap 6 — Vite dev ports configureren **NU** @@ -296,7 +292,6 @@ Elk frontend-app een eigen port zodat CORS werkt - ☐ **Pas vite.config.ts aan in elke app** ```ts - // apps/admin/vite.config.ts → port: 5173 // apps/app/vite.config.ts → port: 5174 // apps/portal/vite.config.ts → port: 5175 server: { @@ -440,12 +435,12 @@ Na werkende apps/app/ basis | **1** | apps/band/ hernoemd naar apps/portal/ | ☐ Klaar | | **2** | Demo-rommel verwijderd uit apps/app/ | ☐ Klaar | | **3** | Dubbele API-laag opgeruimd → een src/lib/axios.ts per app | ☐ Klaar | -| **4** | TanStack Query geinstalleerd in alle drie apps + geregistreerd in main.ts | ☐ Klaar | -| **5** | VeeValidate + Zod geinstalleerd in alle drie apps | ☐ Klaar | +| **4** | TanStack Query geinstalleerd in beide apps + geregistreerd in main.ts | ☐ Klaar | +| **5** | VeeValidate + Zod geinstalleerd in beide apps | ☐ Klaar | | **6** | Spatie packages geinstalleerd in api/ + configs gepubliceerd | ☐ Klaar | | **7** | PortalTokenMiddleware skeleton aangemaakt | ☐ Klaar | -| **8** | config/cors.php: drie frontend origins geconfigureerd | ☐ Klaar | -| **9** | Vite dev ports: admin=5173, app=5174, portal=5175 | ☐ Klaar | +| **8** | config/cors.php: twee frontend origins geconfigureerd | ☐ Klaar | +| **9** | Vite dev ports: app=5174, portal=5175 | ☐ Klaar | | **10** | .env.local aangemaakt per app met VITE_API_URL | ☐ Klaar | | **11** | CLAUDE.md aangemaakt en volledig ingevuld (Dev Guide sectie 3.1 + portal architectuur) | ☐ Klaar | | **12** | .cursorrules aangemaakt (Dev Guide sectie 3.2) | ☐ Klaar |