'unauthorized'], 401); } $claims = JwtToken::verify($matches[1]); if ($claims === null) { api_json(['error' => 'unauthorized'], 401); } try { $db = Database::getInstance(); $stmt = $db->prepare('SELECT u.id, u.account_role, u.is_active FROM auth_sessions s JOIN users u ON u.id = s.user_id WHERE s.session_uuid = ? AND s.user_id = ? AND s.revoked_at IS NULL AND s.replaced_by IS NULL AND s.expires_at > UTC_TIMESTAMP() AND u.is_active = 1 LIMIT 1'); $userId = (int) $claims['sub']; $sessionId = $claims['sid']; $stmt->bind_param('si', $sessionId, $userId); $stmt->execute(); $result = $stmt->get_result(); $user = $result->fetch_assoc(); $stmt->close(); if (!$user) { api_json(['error' => 'unauthorized'], 401); } return ['user_id' => (int) $user['id'], 'role' => $user['account_role'], 'session_id' => $sessionId]; } catch (Throwable $exception) { error_log('Bearer authorization check failed: ' . $exception->getMessage()); api_json(['error' => 'service_unavailable'], 503); } } public static function requireRole(array $auth, array $allowedRoles): void { if (!in_array($auth['role'] ?? null, $allowedRoles, true)) { api_json(['error' => 'forbidden'], 403); } } }