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>
84 lines
2.4 KiB
PHP
84 lines
2.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
final class CookieBearerToken
|
|
{
|
|
private const COOKIE_NAMES = [
|
|
'crewli_app_token',
|
|
'crewli_portal_token',
|
|
];
|
|
|
|
public function handle(Request $request, Closure $next): Response
|
|
{
|
|
// Skip if an Authorization header is already present
|
|
if ($request->hasHeader('Authorization')) {
|
|
return $next($request);
|
|
}
|
|
|
|
// Resolve the cookie name for the requesting app via Origin header.
|
|
// This prevents cross-app cookie leakage on localhost where the
|
|
// browser sends all cookies regardless of port.
|
|
$cookieName = $this->resolveCookieName($request);
|
|
|
|
if ($cookieName) {
|
|
$token = $request->cookie($cookieName);
|
|
if ($token) {
|
|
$request->headers->set('Authorization', 'Bearer ' . $token);
|
|
}
|
|
}
|
|
|
|
return $next($request);
|
|
}
|
|
|
|
private function resolveCookieName(Request $request): ?string
|
|
{
|
|
$origin = $request->headers->get('Origin')
|
|
?? $request->headers->get('Referer')
|
|
?? '';
|
|
|
|
if ($origin === '') {
|
|
// No Origin — fall back to first available cookie (e.g. server-to-server)
|
|
foreach (self::COOKIE_NAMES as $name) {
|
|
if ($request->cookie($name)) {
|
|
return $name;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
$originHost = parse_url($origin, PHP_URL_HOST);
|
|
$originPort = parse_url($origin, PHP_URL_PORT);
|
|
|
|
$map = [
|
|
'app' => [config('app.frontend_app_url', 'http://localhost:5174'), 'crewli_app_token'],
|
|
'portal' => [config('app.frontend_portal_url', 'http://localhost:5175'), 'crewli_portal_token'],
|
|
];
|
|
|
|
foreach ($map as [$configuredUrl, $cookieName]) {
|
|
$configHost = parse_url($configuredUrl, PHP_URL_HOST);
|
|
$configPort = parse_url($configuredUrl, PHP_URL_PORT);
|
|
|
|
if ($originHost === $configHost && $originPort === $configPort) {
|
|
return $cookieName;
|
|
}
|
|
}
|
|
|
|
// Origin didn't match any configured frontend — fall back to first available
|
|
foreach (self::COOKIE_NAMES as $name) {
|
|
if ($request->cookie($name)) {
|
|
return $name;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|