mdn-backend / app /Http /Middleware /AuthenticateAccessToken.php
internationalscholarsprogram's picture
fix(seed): remove duplicate James enrollment2; add Sofia+David lifecycle data
8d9fa4b
Raw
History Blame Contribute Delete
6.48 kB
<?php
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;
}
}