chore(rules): purge apps/portal from 102_multi_tenancy.mdc

Surgical updates reflecting post-WS-3 single-SPA reality. The
OrganisationScope rules, three-level authorization, and invitation
flow are unchanged — they're still the canonical guidance.

Changes:
- globs: drop apps/portal/**/*.{vue,ts}
- Portal Architecture: "two access modes in apps/portal/" ->
  "two access modes under /portal/* routes within apps/app/"
- Token flow URL example: portal.crewli.app -> crewli.app/portal/
  with note about 301 redirect from legacy host
- Login flow URL: portal.crewli.app -> crewli.app
- CORS allowed_origins: drop FRONTEND_PORTAL_URL line
- Production example: collapse dual-host to single-host with
  pointer to AUTH_ARCHITECTURE.md §11 for the legacy env key
This commit is contained in:
2026-05-06 01:51:18 +02:00
parent d82cf42728
commit 451eab42ac

View File

@@ -1,6 +1,6 @@
---
description: Multi-tenancy and portal architecture rules for Crewli
globs: ["api/**/*.php", "apps/portal/**/*.{vue,ts}"]
globs: ["api/**/*.php"]
alwaysApply: true
---
@@ -92,16 +92,20 @@ Route::middleware(['auth:sanctum', 'event.role:event_manager'])->group(...);
## Portal Architecture
### Two Access Modes in One App (`apps/portal/`)
### Two Access Modes Under `/portal/*` Routes (within `apps/app/`)
Post-WS-3, the portal lives in the main SPA at `/portal/*` routes.
Two access modes coexist:
| Mode | Middleware | Users | Token Source |
|------|-----------|-------|-------------|
| Login | `auth:sanctum` | Volunteers, Crew | Bearer token from login |
|------|------------|-------|--------------|
| Login | `auth:sanctum` | Volunteers, Crew | Bearer token from login (httpOnly cookie) |
| Token | `portal.token` | Artists, Suppliers, Press | URL token param: `?token=ULID` |
### Token-Based Authentication Flow
```
1. Artist/supplier receives email with link: https://portal.crewli.app/advance?token=01HQ3K...
1. Artist/supplier receives email with link: https://crewli.app/portal/advance?token=01HQ3K...
(Legacy portal.crewli.app links 301-redirect, preserving the token query param)
2. Portal detects token in URL query parameter
3. POST /api/v1/portal/token-auth { token: '01HQ3K...' }
4. Backend validates token against artists.portal_token or production_requests.token
@@ -111,9 +115,9 @@ Route::middleware(['auth:sanctum', 'event.role:event_manager'])->group(...);
### Login-Based Authentication Flow
```
1. Volunteer navigates to https://portal.crewli.app/login
1. Volunteer navigates to https://crewli.app/login
2. Enters email + password
3. POST /api/v1/auth/login (same endpoint as apps/app/)
3. POST /api/v1/auth/login
4. Returns user + organisations + event roles
5. Portal shows volunteer-specific views (My Shifts, Claim Shifts, Messages, Profile)
```
@@ -190,12 +194,11 @@ class PortalTokenMiddleware
// config/cors.php
'allowed_origins' => [
env('FRONTEND_APP_URL', 'http://localhost:5174'),
env('FRONTEND_PORTAL_URL', 'http://localhost:5175'),
],
'supports_credentials' => true,
```
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`.
Production example (registered domain **crewli.app**): `FRONTEND_APP_URL=https://crewli.app` and `SANCTUM_STATEFUL_DOMAINS=crewli.app`. The legacy `FRONTEND_PORTAL_URL` env key is retained for outbound-email controllers (per AUTH_ARCHITECTURE.md §11), but resolves to the same host post-WS-3.
## Shift Claiming & Approval Flow