From 3a445eabd90abf94ddeed2e5cb0ddad50d296043 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Mon, 10 Nov 2025 12:56:02 +0000 Subject: [PATCH] refactor(session): remove TokenRefresher and session refresh integration from APIs --- Common/UI/Utils/API/API.ts | 14 --- Common/UI/Utils/API/TokenRefresher.ts | 157 -------------------------- StatusPage/src/Utils/API.ts | 20 ---- 3 files changed, 191 deletions(-) delete mode 100644 Common/UI/Utils/API/TokenRefresher.ts diff --git a/Common/UI/Utils/API/API.ts b/Common/UI/Utils/API/API.ts index 743c720ed4..9fbb02a822 100644 --- a/Common/UI/Utils/API/API.ts +++ b/Common/UI/Utils/API/API.ts @@ -17,9 +17,6 @@ import { UserTenantAccessPermission, } from "../../../Types/Permission"; import API from "../../../Utils/API"; -import TokenRefresher, { - SessionRefreshOptions, -} from "./TokenRefresher"; class BaseAPI extends API { public constructor(protocol: Protocol, hostname: Hostname, route?: Route) { @@ -63,11 +60,6 @@ class BaseAPI extends API { ); } - await TokenRefresher.handleAccessTokenHeader( - headers, - this.getSessionRefreshOptions(), - ); - return headers; } @@ -150,12 +142,6 @@ class BaseAPI extends API { return new Route("/accounts/forbidden"); } - protected static getSessionRefreshOptions(): SessionRefreshOptions | null { - return { - scope: "dashboard", - }; - } - public static getFriendlyMessage( err: HTTPErrorResponse | Exception | unknown, ): string { diff --git a/Common/UI/Utils/API/TokenRefresher.ts b/Common/UI/Utils/API/TokenRefresher.ts deleted file mode 100644 index 452a01ce00..0000000000 --- a/Common/UI/Utils/API/TokenRefresher.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { IDENTITY_URL } from "../../Config"; -import Dictionary from "../../../Types/Dictionary"; -import URL from "../../../Types/API/URL"; -import API from "../../../Utils/API"; -import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse"; -import HTTPResponse from "../../../Types/API/HTTPResponse"; -import { JSONObject } from "../../../Types/JSON"; -import JWTToken from "../JWT"; -import ObjectID from "../../../Types/ObjectID"; - -const ACCESS_TOKEN_HEADER: string = "access_token"; -const REFRESH_THRESHOLD_MS: number = 5 * 60 * 1000; // 5 minutes - -export type SessionRefreshScope = "dashboard" | "admin" | "statusPage"; - -export type SessionRefreshOptions = { - scope: SessionRefreshScope; - statusPageId?: ObjectID | null; -}; - -export default class TokenRefresher { - private static inFlightRefresh: Promise | null = null; - public static async handleAccessTokenHeader( - headers: Dictionary, - options: SessionRefreshOptions | null, - ): Promise { - if (!options) { - return; - } - - const rawHeaderValue: string | undefined = this.extractHeader( - headers, - ACCESS_TOKEN_HEADER, - ); - - if (!rawHeaderValue) { - return; - } - - const token: string = rawHeaderValue.trim(); - - if (!token || token.length === 0) { - return; - } - - let expiry: number | null = null; - - try { - const decoded: JSONObject = JWTToken.decodeToken(token); - if (decoded && typeof decoded["exp"] === "number") { - expiry = Math.floor(decoded["exp"] as number) * 1000; // seconds to ms - } - } catch (error) { - // If token cannot be decoded just ignore refresh, backend will handle expiry - return; - } - - if (!expiry) { - return; - } - - if (!this.shouldRefresh(expiry)) { - return; - } - - await this.refreshTokens(options); - } - - private static extractHeader( - headers: Dictionary, - headerName: string, - ): string | undefined { - const normalizedHeaderName: string = headerName.toLowerCase(); - for (const key of Object.keys(headers || {})) { - if (key.toLowerCase() === normalizedHeaderName) { - return headers[key] as string; - } - } - - return undefined; - } - - private static shouldRefresh(expiry: number): boolean { - const now: number = Date.now(); - - if (expiry <= now) { - return true; - } - - const remainingMs: number = expiry - now; - if (remainingMs <= REFRESH_THRESHOLD_MS) { - return true; - } - - return false; - } - - private static async refreshTokens( - options: SessionRefreshOptions, - ): Promise { - if (this.inFlightRefresh) { - await this.inFlightRefresh; - return; - } - - const refreshUrl: URL | null = this.getRefreshUrl(options); - - if (!refreshUrl) { - return; - } - - this.inFlightRefresh = (async () => { - try { - const response: - | HTTPResponse - | HTTPErrorResponse = await API.post({ - url: refreshUrl, - }); - - if (response instanceof HTTPErrorResponse) { - // Let existing error handler manage authentication failures - return; - } - } catch (error) { - // Swallow refresh errors; subsequent API calls will surface auth issues - return; - } finally { - this.inFlightRefresh = null; - } - })(); - - await this.inFlightRefresh; - } - - private static getRefreshUrl( - options: SessionRefreshOptions, - ): URL | null { - switch (options.scope) { - case "statusPage": { - if (!options.statusPageId) { - return null; - } - - return URL.fromString(IDENTITY_URL.toString()) - .addRoute("/status-page/refresh-token") - .addRoute(`/${options.statusPageId.toString()}`); - } - case "admin": - case "dashboard": - default: { - return URL.fromString(IDENTITY_URL.toString()).addRoute( - "/refresh-token", - ); - } - } - } -} diff --git a/StatusPage/src/Utils/API.ts b/StatusPage/src/Utils/API.ts index 7d04d5eb24..87dca5bcd9 100644 --- a/StatusPage/src/Utils/API.ts +++ b/StatusPage/src/Utils/API.ts @@ -4,7 +4,6 @@ import Route from "Common/Types/API/Route"; import ObjectID from "Common/Types/ObjectID"; import BaseAPI from "Common/UI/Utils/API/API"; import UserUtil from "./User"; -import { SessionRefreshOptions } from "Common/UI/Utils/API/TokenRefresher"; export default class API extends BaseAPI { public static override getDefaultHeaders(statusPageId: ObjectID): Headers { @@ -36,23 +35,4 @@ export default class API extends BaseAPI { : "/forbidden", ); } - - public static override getSessionRefreshOptions(): - | SessionRefreshOptions - | null { - if (!StatusPageUtil.isPrivateStatusPage()) { - return null; - } - - const statusPageId: ObjectID | null = StatusPageUtil.getStatusPageId(); - - if (!statusPageId) { - return null; - } - - return { - scope: "statusPage", - statusPageId, - }; - } }