File size: 6,479 Bytes
b2dcf0f
 
 
 
 
 
 
 
 
 
 
e7550e0
b2dcf0f
 
 
 
 
 
 
 
 
 
 
 
 
 
e7550e0
8d9fa4b
 
 
 
e7550e0
8d9fa4b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e7550e0
30d67d5
 
 
 
 
 
 
e7550e0
 
1322e11
b2dcf0f
1322e11
b2dcf0f
 
 
 
1322e11
b2dcf0f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6773345
 
 
 
b2dcf0f
 
 
 
 
 
 
 
d7511e4
1322e11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b2dcf0f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
<?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;
    }
}