docs: remove admin SPA references and update production URLs

The admin SPA (apps/admin/) has been retired. Its functionality now
lives in apps/app/ under /platform/* routes for super_admin users.
Updated all documentation to reflect: 2 SPAs instead of 3, removed
FRONTEND_ADMIN_URL/port 5173 references, changed production URL from
app.crewli.app to crewli.app. Retired admin-specific security audit
findings (A13-2, A13-4, A13-5, A13-7) and APPS-01 backlog item.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-15 08:21:44 +02:00
parent 2933d957a6
commit 945e22f322
13 changed files with 102 additions and 168 deletions

View File

@@ -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.
---

View File

@@ -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
```

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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) |

View File

@@ -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.
---

View File

@@ -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

View File

@@ -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.

View File

@@ -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 |

View File

@@ -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 apps `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 apps `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

View File

@@ -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)

View File

@@ -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 |