sessions->getUser(); if ($user === null) { $uri = $request->getUri(); $path = $uri->getPath(); $query = $uri->getQuery(); $next = $query !== '' ? $path . '?' . $query : $path; $this->sessions->setNext($next); return $this->responseFactory ->createResponse(302) ->withHeader('Location', '/login'); } if ($this->shouldRevalidate()) { $revoked = $this->revalidate($user); if ($revoked) { $this->sessions->clear(); $this->sessions->flash('error', 'Your access was revoked. Please sign in again.'); return $this->responseFactory ->createResponse(302) ->withHeader('Location', '/login'); } } return $handler->handle($request); } private function shouldRevalidate(): bool { $lastRev = $this->sessions->lastRevalidatedAt(); if ($lastRev === null) { // Legacy/pre-F36 session — seed the clock and let the next // request past the threshold be the first real revalidation. $this->sessions->markRevalidated(); return false; } return (time() - $lastRev) >= $this->revalidateAfterSeconds; } /** * @return bool `true` if the session must be cleared (user revoked). */ private function revalidate(UserContext $user): bool { try { $live = $this->admin->getMe($user->userId); } catch (ApiAuthException $e) { // 401 = ui_service_token deployment problem; 403 = this user // is disabled / unknown. Only the 403 case revokes; a 401 // would lock every session out on a deployment slip-up. if ($e->statusCode === 403) { $this->logger->warning('session revalidation: user revoked by api', [ 'user_id' => $user->userId, 'api_error' => $e->apiError, ]); return true; } $this->logger->error('session revalidation: api auth error, keeping session', [ 'user_id' => $user->userId, 'status' => $e->statusCode, 'api_error' => $e->apiError, ]); $this->sessions->markRevalidated(); return false; } catch (ApiNotFoundException $e) { // Defensive: the api currently returns 403 (not 404) when the // user record is missing. Treat 404 the same way for safety. $this->logger->warning('session revalidation: user record missing', [ 'user_id' => $user->userId, 'api_error' => $e->apiError, ]); return true; } catch (ApiException $e) { // Api unreachable / 5xx / unexpected. Don't lock everyone out // on a backend blip; mark revalidated so we don't grind on it // every request, and try again after the next interval. $this->logger->warning('session revalidation: api unreachable, keeping session', [ 'user_id' => $user->userId, 'error' => $e->getMessage(), ]); $this->sessions->markRevalidated(); return false; } $live_email = $live->email; $changed = $live->role !== $user->role || ($live->displayName !== '' && $live->displayName !== $user->displayName) || $live_email !== $user->email; if ($changed) { $this->sessions->updateUser(new UserContext( userId: $user->userId, displayName: $live->displayName !== '' ? $live->displayName : $user->displayName, role: $live->role, email: $live_email, source: $user->source, )); } $this->sessions->markRevalidated(); return false; } }