Spaces:
Running
Running
| namespace App\Http\Middleware; | |
| use App\Domain\Identity\JwtTokenService; | |
| use App\Domain\Identity\RbacService; | |
| use App\Domain\Identity\SessionRepository; | |
| use App\Domain\Identity\UserRepository; | |
| use App\Shared\Http\ApiResponse; | |
| use Closure; | |
| use Illuminate\Http\Request; | |
| use Illuminate\Support\Facades\DB; | |
| use Throwable; | |
| class AuthenticateAccessToken | |
| { | |
| public function __construct( | |
| private readonly JwtTokenService $jwt, | |
| private readonly UserRepository $users, | |
| private readonly SessionRepository $sessions, | |
| private readonly RbacService $rbac, | |
| ) { | |
| } | |
| public function handle(Request $request, Closure $next) | |
| { | |
| // ── Staging auth bypass ────────────────────────────────────────────── | |
| // When AUTH_BYPASS=true: if a valid Bearer JWT is present, use real auth | |
| // so member and admin identities are distinct. Fall back to the bypass | |
| // actor (first DB user) when no token is provided or the token cannot be | |
| // decoded / the sub user cannot be found. | |
| if (config('app.auth_bypass', false)) { | |
| $token = $this->extractBearerToken($request); | |
| if ($token !== null) { | |
| try { | |
| $claims = $this->jwt->decode($token); | |
| $userId = (string) ($claims->sub ?? ''); | |
| if ($userId !== '') { | |
| $user = $this->users->findById($userId); | |
| if ($user !== null) { | |
| $request->attributes->set('actor_user', $user); | |
| $request->attributes->set('actor_user_id', $userId); | |
| $request->attributes->set('actor_session_id', (string) ($claims->sid ?? 'bypass-jwt')); | |
| $request->attributes->set('actor_roles', $this->rbac->rolesForUser($userId)); | |
| $request->attributes->set('actor_permissions', $this->rbac->permissionsForUser($userId)); | |
| return $next($request); | |
| } | |
| } | |
| } catch (Throwable) { | |
| // Invalid JWT — fall through to bypass actor | |
| } | |
| } | |
| $bypass = DB::table('users')->whereNull('deleted_at')->orderBy('id')->first(); | |
| $userId = $bypass !== null ? (string) $bypass->id : 'staging-bypass'; | |
| $request->attributes->set('actor_user', $bypass); | |
| $request->attributes->set('actor_user_id', $userId); | |
| $request->attributes->set('actor_session_id', 'bypass'); | |
| $request->attributes->set('actor_roles', $bypass !== null ? $this->rbac->rolesForUser($userId) : ['SUPER_ADMIN']); | |
| $request->attributes->set('actor_permissions', $bypass !== null ? $this->rbac->permissionsForUser($userId) : ['SUPER_ADMIN_ACCESS']); | |
| return $next($request); | |
| } | |
| $token = $this->extractBearerToken($request); | |
| if ($token === null) { | |
| return ApiResponse::error('UNAUTHORIZED', 'Authentication is required.', [], 401); | |
| } | |
| try { | |
| $claims = $this->jwt->decode($token); | |
| } catch (Throwable) { | |
| return ApiResponse::error('UNAUTHORIZED', 'Authentication token is invalid or expired.', [], 401); | |
| } | |
| $userId = (string) ($claims->sub ?? ''); | |
| $sessionId = (string) ($claims->sid ?? ''); | |
| $user = $this->users->findById($userId); | |
| $session = $this->sessions->findActiveById($sessionId); | |
| if ($user === null || $session === null || $session->user_id !== $userId) { | |
| return ApiResponse::error('UNAUTHORIZED', 'Authentication token is invalid or expired.', [], 401); | |
| } | |
| if ($user->disabled_at !== null || $user->user_status === 'DISABLED') { | |
| return ApiResponse::error('USER_DISABLED', 'This user account is disabled.', [], 403); | |
| } | |
| if (in_array(strtoupper((string) ($user->portal_status ?? '')), ['SUSPENDED', 'DISABLED'], true)) { | |
| return ApiResponse::error('PORTAL_ACCESS_DISABLED', 'This portal account is not active.', [], 403); | |
| } | |
| $request->attributes->set('actor_user', $user); | |
| $request->attributes->set('actor_user_id', $userId); | |
| $request->attributes->set('actor_session_id', $sessionId); | |
| $request->attributes->set('actor_roles', $this->rbac->rolesForUser($userId)); | |
| $request->attributes->set('actor_permissions', $this->rbac->permissionsForUser($userId)); | |
| return $next($request); | |
| } | |
| private function extractBearerToken(Request $request): ?string | |
| { | |
| $candidates = [ | |
| $request->headers->get('X-Authorization'), | |
| $request->headers->get('Authorization'), | |
| $request->server('HTTP_X_AUTHORIZATION'), | |
| $request->server('HTTP_AUTHORIZATION'), | |
| $request->server('REDIRECT_HTTP_AUTHORIZATION'), | |
| $request->server('Authorization'), | |
| $_SERVER['HTTP_X_AUTHORIZATION'] ?? null, | |
| $_SERVER['HTTP_AUTHORIZATION'] ?? null, | |
| $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? null, | |
| $_SERVER['Authorization'] ?? null, | |
| ]; | |
| if (function_exists('getallheaders')) { | |
| $headers = getallheaders(); | |
| $candidates[] = $headers['X-Authorization'] ?? $headers['x-authorization'] ?? null; | |
| $candidates[] = $headers['Authorization'] ?? $headers['authorization'] ?? null; | |
| } | |
| if (function_exists('apache_request_headers')) { | |
| $headers = apache_request_headers(); | |
| $candidates[] = $headers['X-Authorization'] ?? $headers['x-authorization'] ?? null; | |
| $candidates[] = $headers['Authorization'] ?? $headers['authorization'] ?? null; | |
| } | |
| foreach ($candidates as $candidate) { | |
| $value = trim((string) ($candidate ?? '')); | |
| if ($value === '') { | |
| continue; | |
| } | |
| if (preg_match('/^Bearer\s+(.+)$/i', $value, $matches) === 1) { | |
| $token = trim($matches[1]); | |
| if ($token !== '') { | |
| return $token; | |
| } | |
| } | |
| if (preg_match('/^[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+$/', $value) === 1) { | |
| return $value; | |
| } | |
| } | |
| return null; | |
| } | |
| } | |