Commit Graph

3 Commits

Author SHA1 Message Date
28727f246b chore: remove admin SPA and update to two-app production setup
Remove apps/admin/ entirely — platform admin functionality now lives
in apps/app/ under /platform/* routes for super_admin users.

Production URL scheme changed:
- Organizer app: crewli.app (was app.crewli.app)
- Portal: portal.crewli.app (unchanged)
- API: api.crewli.app (unchanged)
- admin.crewli.app and app.crewli.app retired

Backend:
- Removed FRONTEND_ADMIN_URL config and admin cookie (crewli_admin_token)
  from SetAuthCookie, CookieBearerToken, cors.php, app.php
- Updated .env and .env.example (two origins, no port 5173)
- Updated cookie test: admin origin test → unknown origin fallback test

Infrastructure:
- Makefile: removed admin target
- deploy/nginx: updated CSP comment, removed admin vhost
- Updated README.md, CLAUDE.md, and all dev-docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 08:44:10 +02:00
a9ef384515 fix: prevent cross-app auth session sharing on localhost
Root cause: browsers don't scope cookies by port. With SESSION_DOMAIN=
localhost, all three SPAs share cookies. The CookieBearerToken middleware
iterated all cookie names and picked the first match, so logging into
the organizer app (port 5174) also authenticated the portal (port 5175).

Fix: CookieBearerToken now resolves the correct cookie name from the
Origin header (same logic as SetAuthCookie trait). It only reads the
cookie matching the requesting app — portal origin reads only
crewli_portal_token, app origin reads only crewli_app_token, etc.

Falls back to first-available cookie when no Origin header is present
(server-to-server requests, tests without explicit Origin).

Added 3 cross-app isolation tests:
- app cookie does NOT authenticate portal requests
- portal cookie does NOT authenticate app requests
- correct cookie + matching origin = authenticated

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 17:19:42 +02:00
513ca519b2 security: migrate auth tokens to httpOnly cookies (hybrid bearer token approach)
Backend:
- CookieBearerToken middleware reads httpOnly cookie and injects Authorization
  header before Sanctum validates (prepended to API middleware group)
- SetAuthCookie trait provides cookie creation/expiry helpers with per-app
  cookie names (crewli_admin_token, crewli_app_token, crewli_portal_token)
- LoginController sets token via Set-Cookie, removes it from JSON body
- LogoutController expires the auth cookie on logout
- AuthRefreshController (POST /auth/refresh) rotates tokens with new cookie
- InvitationController accept also sets token via cookie, not JSON body
- All cookies: httpOnly, SameSite=Strict, Secure (in production)

Frontend (all three SPAs):
- Removed all localStorage token storage (apps/app, apps/portal)
- Removed all JS-readable cookie token storage (apps/admin)
- Removed Authorization: Bearer header interceptors from axios
- Auth stores now rely on GET /auth/me to validate httpOnly cookie
- Admin app: new Pinia auth store replaces useCookie-based auth pattern
- withCredentials: true ensures browser sends cookies automatically

Fixes security findings A13-1 (localStorage tokens) and A13-2 (admin
cookie flags). Tokens are now invisible to JavaScript.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:06:44 +02:00