@dloizides/auth-client 3.0.0 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/buildKeycloakUrls.ts","../src/utils/isTokenExpired.ts","../src/utils/parseRealmFromIssuer.ts","../src/utils/normalizeTokenResponse.ts","../src/events/AuthEventEmitter.ts","../src/AuthClient.ts","../src/oidc/discovery.ts","../src/oidc/pkce.ts","../src/utils/buildTokenRequestBody.ts","../src/oidc/tokenExchange.ts","../src/types/KeycloakRoles.ts","../src/storage/BrowserStorageTokenStorage.ts","../src/storage/InMemoryTokenStorage.ts","../src/storage/CookieTokenStorage.ts","../src/storage/SecureStoreTokenStorage.ts","../src/biometric/BiometricGate.ts","../src/interceptor/RefreshInterceptor.ts","../src/inactivity/InactivityTracker.ts","../src/http/HttpClient.ts","../src/api/AuthApiClient.ts","../src/bff/BffAuthClient.ts","../src/utils/normalizeKeycloakUser.ts","../src/utils/extractAuthCode.ts","../src/utils/decodeJwt.ts"],"names":["KeycloakRoles"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * URL builders for the realm-aware Keycloak surface area.\n *\n * Every helper takes `baseUrl` and `realm` explicitly — no hardcoded realm\n * names. This is the contract that Phase 2 of the product split relies on:\n * the same package serves the future Questioner-realm app and OnlineMenu-realm\n * app without code change.\n */\n\nconst REALM_PATH_PREFIX = '/realms';\nconst PROTOCOL_PATH = '/protocol/openid-connect';\n\nfunction trimTrailingSlash(value: string): string {\n return value.replace(/\\/$/, '');\n}\n\n/**\n * Compute the issuer URL: `{baseUrl}/realms/{realm}`.\n */\nexport function buildIssuerUrl(baseUrl: string, realm: string): string {\n return `${trimTrailingSlash(baseUrl)}${REALM_PATH_PREFIX}/${encodeURIComponent(realm)}`;\n}\n\n/**\n * Compute the authorization endpoint URL.\n */\nexport function buildAuthorizationEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/auth`;\n}\n\n/**\n * Compute the token endpoint URL.\n */\nexport function buildTokenEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/token`;\n}\n\n/**\n * Compute the userinfo endpoint URL.\n */\nexport function buildUserInfoEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/userinfo`;\n}\n\n/**\n * Compute the logout endpoint URL.\n */\nexport function buildLogoutEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/logout`;\n}\n\nexport interface AuthorizationUrlInput {\n baseUrl: string;\n realm: string;\n clientId: string;\n redirectUri: string;\n scope?: string;\n state?: string;\n codeChallenge?: string;\n codeChallengeMethod?: 'S256' | 'plain';\n}\n\n/**\n * Build a complete authorization URL the user agent can navigate to.\n *\n * All PKCE-related fields are optional so this helper also serves\n * non-PKCE flows (e.g. confidential server-side clients) — but PKCE is\n * the recommended path for SPA / native consumers.\n */\nexport function buildAuthorizationUrl(input: AuthorizationUrlInput): string {\n const params = new URLSearchParams({\n client_id: input.clientId,\n redirect_uri: input.redirectUri,\n response_type: 'code',\n });\n if (typeof input.scope === 'string' && input.scope !== '') {\n params.set('scope', input.scope);\n }\n if (typeof input.state === 'string' && input.state !== '') {\n params.set('state', input.state);\n }\n if (typeof input.codeChallenge === 'string' && input.codeChallenge !== '') {\n params.set('code_challenge', input.codeChallenge);\n params.set('code_challenge_method', input.codeChallengeMethod ?? 'S256');\n }\n return `${buildAuthorizationEndpoint(input.baseUrl, input.realm)}?${params.toString()}`;\n}\n","import type { AuthTokens } from '../types/AuthTokens';\n\nconst SECONDS_TO_MILLIS = 1000;\nconst DEFAULT_LEEWAY_MS = 30 * SECONDS_TO_MILLIS;\n\n/**\n * Determine whether a token bundle is expired.\n *\n * `expiresAt` is interpreted as an absolute UNIX millisecond timestamp.\n *\n * `leewayMs` (default 30 s) shaves a small window off the expiry to compensate\n * for clock skew and round-trip latency: a token that expires at exactly\n * `Date.now()` is essentially useless because by the time the request arrives\n * at the API it will have expired.\n */\nexport function isTokenExpired(\n tokens: Pick<AuthTokens, 'expiresAt'> | null | undefined,\n leewayMs: number = DEFAULT_LEEWAY_MS,\n now: number = Date.now(),\n): boolean {\n if (!tokens) {\n return true;\n }\n if (typeof tokens.expiresAt !== 'number' || tokens.expiresAt <= 0) {\n return true;\n }\n return tokens.expiresAt - leewayMs <= now;\n}\n\n/**\n * Compute the absolute expiry timestamp from a token endpoint `expires_in` value.\n *\n * Returns `0` when `expiresIn` is missing or non-positive — signalling \"unknown\n * expiry, treat as expired\" downstream.\n */\nexport function computeExpiresAt(\n expiresInSeconds: number | undefined,\n now: number = Date.now(),\n): number {\n if (typeof expiresInSeconds !== 'number' || expiresInSeconds <= 0) {\n return 0;\n }\n return now + expiresInSeconds * SECONDS_TO_MILLIS;\n}\n","/**\n * Extract the realm name from a Keycloak issuer URL.\n *\n * Keycloak issuer URLs follow the shape `{baseUrl}/realms/{realm}` (with optional\n * `/protocol/openid-connect` suffix on token endpoints). This helper reverses\n * that convention so existing apps that store only the issuer URL can derive\n * `realm` for the realm-aware {@link AuthClient} constructor.\n *\n * @returns the realm name, or `null` if the URL doesn't match the convention.\n */\nexport function parseRealmFromIssuer(issuerUrl: string | null | undefined): string | null {\n if (typeof issuerUrl !== 'string' || issuerUrl === '') {\n return null;\n }\n const match = /\\/realms\\/([^/?#]+)/i.exec(issuerUrl);\n if (!match || match[1] === undefined || match[1] === '') {\n return null;\n }\n return decodeURIComponent(match[1]);\n}\n\n/**\n * Extract the base URL (scheme + host + optional path prefix) from a\n * Keycloak issuer URL by stripping the `/realms/{realm}...` suffix.\n *\n * Returns the original input unchanged when no `/realms/` segment is found —\n * callers that need strict validation should pair this with `parseRealmFromIssuer`.\n */\nexport function parseBaseUrlFromIssuer(issuerUrl: string | null | undefined): string | null {\n if (typeof issuerUrl !== 'string' || issuerUrl === '') {\n return null;\n }\n const idx = issuerUrl.search(/\\/realms\\//i);\n if (idx === -1) {\n return issuerUrl.replace(/\\/$/, '');\n }\n return issuerUrl.substring(0, idx).replace(/\\/$/, '');\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { RawTokenResponse, TokenResponse } from '../types/TokenResponse';\nimport { computeExpiresAt } from './isTokenExpired';\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/**\n * Map a raw OIDC token endpoint response (snake_case) to camelCase.\n *\n * Throws when `access_token` is missing or empty — callers should let this\n * propagate to the auth state machine, which treats it as a login failure.\n */\nexport function normalizeTokenResponse(raw: RawTokenResponse): TokenResponse {\n const accessToken = asString(raw.access_token);\n if (accessToken === undefined) {\n throw new Error('Token response missing access_token');\n }\n return {\n accessToken,\n refreshToken: asString(raw.refresh_token),\n idToken: asString(raw.id_token),\n expiresIn: asNumber(raw.expires_in),\n tokenType: asString(raw.token_type),\n scope: asString(raw.scope),\n };\n}\n\n/**\n * Convert a normalized {@link TokenResponse} into a persistable\n * {@link AuthTokens} bundle by computing `expiresAt` from `expiresIn`.\n */\nexport function tokenResponseToAuthTokens(\n response: TokenResponse,\n now: number = Date.now(),\n): AuthTokens {\n return {\n accessToken: response.accessToken,\n refreshToken: response.refreshToken,\n idToken: response.idToken,\n expiresAt: computeExpiresAt(response.expiresIn, now),\n };\n}\n","/**\n * Tiny dependency-free event emitter for auth lifecycle events.\n *\n * Consumers subscribe to `onSessionExpired` to navigate to the login screen\n * when refresh fails or the inactivity timeout fires. We don't reach for\n * `EventTarget`/`EventEmitter` because we want one consistent API across web,\n * React Native, and node test environments without polyfills.\n */\nexport type AuthEventName = 'sessionExpired';\n\nexport type AuthEventListener = () => void;\n\nexport interface AuthEventUnsubscribe {\n (): void;\n}\n\nexport class AuthEventEmitter {\n private readonly listeners: Map<AuthEventName, Set<AuthEventListener>> = new Map();\n\n on(event: AuthEventName, listener: AuthEventListener): AuthEventUnsubscribe {\n let bucket = this.listeners.get(event);\n if (bucket === undefined) {\n bucket = new Set();\n this.listeners.set(event, bucket);\n }\n bucket.add(listener);\n return (): void => {\n const current = this.listeners.get(event);\n if (current !== undefined) {\n current.delete(listener);\n }\n };\n }\n\n emit(event: AuthEventName): void {\n const bucket = this.listeners.get(event);\n if (bucket === undefined) {\n return;\n }\n // Snapshot so listeners can unsubscribe during dispatch without skipping siblings.\n const snapshot = Array.from(bucket);\n for (const listener of snapshot) {\n listener();\n }\n }\n\n /** Remove all listeners. Useful for `AuthClient.dispose()` and tests. */\n clear(): void {\n this.listeners.clear();\n }\n}\n","import {\n buildAuthorizationEndpoint,\n buildAuthorizationUrl,\n buildIssuerUrl,\n buildLogoutEndpoint,\n buildTokenEndpoint,\n buildUserInfoEndpoint,\n} from './utils/buildKeycloakUrls';\nimport { isTokenExpired } from './utils/isTokenExpired';\nimport { parseBaseUrlFromIssuer, parseRealmFromIssuer } from './utils/parseRealmFromIssuer';\nimport { normalizeTokenResponse, tokenResponseToAuthTokens } from './utils/normalizeTokenResponse';\nimport { AuthEventEmitter, type AuthEventListener, type AuthEventName, type AuthEventUnsubscribe } from './events/AuthEventEmitter';\n\nimport type { AuthApiClient, RawAuthLoginResponse } from './api/AuthApiClient';\nimport type { AuthClientConfig } from './types/AuthClientConfig';\nimport type { AuthTokens } from './types/AuthTokens';\nimport type { TokenStorage } from './types/TokenStorage';\nimport type { InactivityTracker } from './inactivity/InactivityTracker';\nimport type { RefreshInterceptor } from './interceptor/RefreshInterceptor';\nimport type { TokenResponse } from './types/TokenResponse';\n\nconst DEFAULT_SCOPE = 'openid profile email';\nconst OFFLINE_ACCESS_SCOPE = 'offline_access';\n\n/**\n * Inputs to {@link AuthClient.fromIssuerUrl}.\n *\n * Used by consumers that store only an issuer URL and want to derive `realm`\n * + `baseUrl` rather than configure them separately.\n */\nexport interface AuthClientFromIssuerInput {\n issuerUrl: string;\n clientId: string;\n redirectUri?: string;\n scope?: string;\n}\n\n/**\n * Optional collaborators wired into {@link AuthClient} for the v2 surface.\n *\n * - `api` enables `loginWith*`, `logout`, `requestPasswordReset`,\n * `confirmPasswordReset`. Without it, those methods throw.\n * - `interceptor` enables `init()` to silently refresh tokens at boot, and is\n * used by `loginWithOtp/Password` to mark inactivity-active.\n * - `inactivityTracker` enforces the 90-day timeout at `init()`.\n *\n * Consumers can omit any/all of these — the v1 PKCE / token-storage surface\n * keeps working unchanged.\n */\nexport interface AuthClientCollaborators {\n api?: AuthApiClient;\n interceptor?: RefreshInterceptor;\n inactivityTracker?: InactivityTracker;\n events?: AuthEventEmitter;\n /**\n * Observability hook fired when a fresh token bundle has been acquired\n * (any login path: OTP, password, or direct-KC PKCE). For app-side\n * analytics/logging only — NOT for BFF integration (Phase 2 designs that\n * fresh).\n */\n onTokenAcquired?: (tokens: AuthTokens) => void;\n /**\n * Observability hook fired when an existing token bundle has been\n * refreshed. For app-side analytics/logging only.\n */\n onTokenRefreshed?: (tokens: AuthTokens) => void;\n}\n\n/**\n * Direct-to-KC (PKCE) routing flag added in v2.1.0.\n *\n * When `true`, `AuthClient` consumers can route their PKCE auth code through\n * the shared OIDC primitives (`exchangeAuthorizationCodeViaOidc`,\n * `refreshTokensViaOidc`) instead of the proxied identity-api `/auth/login`\n * + `/auth/refresh` flow.\n *\n * Default `false` — v2.0 behavior unchanged.\n *\n * The flag is read-only at runtime (`isDirectMode()`) so apps can render\n * conditionally on whether they've opted in.\n */\nexport interface DirectKcOptions {\n useDirectKcAuth?: boolean;\n}\n\nexport interface LoginOptions {\n /** When true, request `offline_access` scope so the IdP issues a long-lived refresh token. */\n offlineAccess?: boolean;\n}\n\nexport interface LogoutOptions {\n /** Revoke all sessions on the IdP, not just the current one. */\n everywhere?: boolean;\n}\n\nexport class AuthClient {\n private readonly config: AuthClientConfig;\n private readonly directKcAuth: boolean;\n private readonly tokenStorage: TokenStorage;\n private readonly api: AuthApiClient | undefined;\n private readonly interceptor: RefreshInterceptor | undefined;\n private readonly inactivityTracker: InactivityTracker | undefined;\n private readonly events: AuthEventEmitter;\n private readonly onTokenAcquired: ((tokens: AuthTokens) => void) | undefined;\n private readonly onTokenRefreshed: ((tokens: AuthTokens) => void) | undefined;\n\n /**\n * @throws Error when `baseUrl`, `realm`, or `clientId` is missing or empty.\n */\n constructor(\n config: AuthClientConfig & DirectKcOptions,\n storage: TokenStorage,\n collaborators: AuthClientCollaborators = {},\n ) {\n AuthClient.validateConfig(config);\n this.config = {\n ...config,\n scope: config.scope ?? DEFAULT_SCOPE,\n };\n this.directKcAuth = config.useDirectKcAuth === true;\n this.tokenStorage = storage;\n this.api = collaborators.api;\n this.interceptor = collaborators.interceptor;\n this.inactivityTracker = collaborators.inactivityTracker;\n this.events = collaborators.events ?? new AuthEventEmitter();\n this.onTokenAcquired = collaborators.onTokenAcquired;\n this.onTokenRefreshed = collaborators.onTokenRefreshed;\n }\n\n /**\n * Whether this client is configured to route auth flows directly to\n * Keycloak (v2.1.0 direct-KC path) instead of through the proxied\n * identity-api `/auth/*` endpoints.\n *\n * Apps can render conditionally on this — e.g. to swap a login form for\n * a \"Sign in with Keycloak\" redirect button.\n */\n isDirectMode(): boolean {\n return this.directKcAuth;\n }\n\n /**\n * Persist a token bundle produced by an external flow (e.g. the\n * app-side `useKeycloakExchange` hook that consumes the shared\n * `exchangeAuthorizationCode` primitive). Fires `onTokenAcquired` after\n * persistence and marks the inactivity tracker active.\n *\n * Designed for the v2.1.0 direct-KC path where the PKCE code exchange\n * happens in the app's React-Query hook (which needs `useDispatch`/etc.)\n * but the token persistence + observability should still flow through\n * the shared client.\n */\n async acceptDirectKcTokens(response: TokenResponse): Promise<AuthTokens> {\n const tokens = tokenResponseToAuthTokens(response);\n await this.tokenStorage.write(tokens);\n if (this.inactivityTracker !== undefined) {\n await this.inactivityTracker.markActive();\n }\n if (this.onTokenAcquired !== undefined) {\n this.onTokenAcquired(tokens);\n }\n return tokens;\n }\n\n /**\n * Same as {@link acceptDirectKcTokens} but fires `onTokenRefreshed`.\n * Use after a `refreshAccessToken()` swap to keep observability counts\n * separated between \"fresh login\" and \"silent refresh\".\n */\n async acceptDirectKcRefresh(response: TokenResponse): Promise<AuthTokens> {\n const tokens = tokenResponseToAuthTokens(response);\n await this.tokenStorage.write(tokens);\n if (this.inactivityTracker !== undefined) {\n await this.inactivityTracker.markActive();\n }\n if (this.onTokenRefreshed !== undefined) {\n this.onTokenRefreshed(tokens);\n }\n return tokens;\n }\n\n /**\n * Build an {@link AuthClient} from a standalone issuer URL by parsing the\n * realm and base URL. Useful when migrating from the legacy\n * `KEYCLOAK_ISSUER` env var convention.\n *\n * @throws Error when the issuer URL doesn't match `{base}/realms/{realm}`.\n */\n static fromIssuerUrl(\n input: AuthClientFromIssuerInput,\n storage: TokenStorage,\n collaborators: AuthClientCollaborators = {},\n ): AuthClient {\n const realm = parseRealmFromIssuer(input.issuerUrl);\n const baseUrl = parseBaseUrlFromIssuer(input.issuerUrl);\n if (realm === null || baseUrl === null || baseUrl === '') {\n throw new Error(`AuthClient.fromIssuerUrl: cannot parse realm from \"${input.issuerUrl}\"`);\n }\n return new AuthClient(\n {\n baseUrl,\n realm,\n clientId: input.clientId,\n redirectUri: input.redirectUri,\n scope: input.scope,\n },\n storage,\n collaborators,\n );\n }\n\n private static validateConfig(config: AuthClientConfig): void {\n if (typeof config.baseUrl !== 'string' || config.baseUrl === '') {\n throw new Error('AuthClient: baseUrl is required');\n }\n if (typeof config.realm !== 'string' || config.realm === '') {\n throw new Error('AuthClient: realm is required');\n }\n if (typeof config.clientId !== 'string' || config.clientId === '') {\n throw new Error('AuthClient: clientId is required');\n }\n }\n\n get realm(): string {\n return this.config.realm;\n }\n\n get clientId(): string {\n return this.config.clientId;\n }\n\n get baseUrl(): string {\n return this.config.baseUrl.replace(/\\/$/, '');\n }\n\n get scope(): string {\n // Constructor always materialises a scope (either user-supplied or DEFAULT_SCOPE).\n return this.config.scope as string;\n }\n\n get redirectUri(): string | undefined {\n return this.config.redirectUri;\n }\n\n /** Issuer URL: `{baseUrl}/realms/{realm}`. */\n get issuerUrl(): string {\n return buildIssuerUrl(this.baseUrl, this.realm);\n }\n\n get authorizationEndpoint(): string {\n return buildAuthorizationEndpoint(this.baseUrl, this.realm);\n }\n\n get tokenEndpoint(): string {\n return buildTokenEndpoint(this.baseUrl, this.realm);\n }\n\n get userInfoEndpoint(): string {\n return buildUserInfoEndpoint(this.baseUrl, this.realm);\n }\n\n get logoutEndpoint(): string {\n return buildLogoutEndpoint(this.baseUrl, this.realm);\n }\n\n /**\n * Build a fully-formed authorization URL the user agent can navigate to.\n *\n * @throws Error when `redirectUri` is not configured.\n */\n buildAuthorizationUrl(input: {\n state?: string;\n codeChallenge?: string;\n codeChallengeMethod?: 'S256' | 'plain';\n offlineAccess?: boolean;\n } = {}): string {\n if (typeof this.config.redirectUri !== 'string' || this.config.redirectUri === '') {\n throw new Error('AuthClient.buildAuthorizationUrl: redirectUri is required');\n }\n return buildAuthorizationUrl({\n baseUrl: this.baseUrl,\n realm: this.realm,\n clientId: this.clientId,\n redirectUri: this.config.redirectUri,\n scope: this.resolveScope(input.offlineAccess),\n state: input.state,\n codeChallenge: input.codeChallenge,\n codeChallengeMethod: input.codeChallengeMethod,\n });\n }\n\n async getTokens(): Promise<AuthTokens | null> {\n return this.tokenStorage.read();\n }\n\n async setTokens(tokens: AuthTokens): Promise<void> {\n return this.tokenStorage.write(tokens);\n }\n\n async clearTokens(): Promise<void> {\n return this.tokenStorage.clear();\n }\n\n /**\n * Read the current access token if it exists and is not expired.\n * Returns `null` for \"no usable token\".\n */\n async getAccessToken(now: number = Date.now()): Promise<string | null> {\n const tokens = await this.tokenStorage.read();\n if (tokens === null) {\n return null;\n }\n if (isTokenExpired(tokens, undefined, now)) {\n return null;\n }\n return tokens.accessToken;\n }\n\n /** Subscribe to lifecycle events (currently `sessionExpired` only). */\n on(event: AuthEventName, listener: AuthEventListener): AuthEventUnsubscribe {\n return this.events.on(event, listener);\n }\n\n /**\n * Boot-time wiring. Checks the inactivity tracker; if expired, clears\n * tokens and emits `sessionExpired`. Returns whether a usable session\n * survived.\n */\n async init(): Promise<{ hasSession: boolean }> {\n if (this.inactivityTracker !== undefined) {\n const expired = await this.inactivityTracker.isExpired();\n if (expired) {\n await this.tokenStorage.clear();\n await this.inactivityTracker.clear();\n this.events.emit('sessionExpired');\n return { hasSession: false };\n }\n }\n const tokens = await this.tokenStorage.read();\n return { hasSession: tokens !== null };\n }\n\n /**\n * Trigger a refresh via the configured interceptor. Returns the new tokens\n * or `null` when the refresh failed (in which case `sessionExpired` has\n * already fired).\n *\n * @throws Error when no interceptor is configured.\n */\n async refresh(): Promise<AuthTokens | null> {\n if (this.interceptor === undefined) {\n throw new Error('AuthClient.refresh: no RefreshInterceptor configured');\n }\n const tokens = await this.interceptor.refreshTokens();\n if (tokens !== null && this.onTokenRefreshed !== undefined) {\n this.onTokenRefreshed(tokens);\n }\n return tokens;\n }\n\n async loginWithOtp(input: { email: string; otp: string; tenantId?: string } & LoginOptions): Promise<AuthTokens> {\n return this.runLogin(this.requireApi().loginWithOtp({\n email: input.email,\n otp: input.otp,\n tenantId: input.tenantId,\n offlineAccess: input.offlineAccess ?? false,\n }));\n }\n\n async loginWithPassword(\n input: { email: string; password: string; tenantId?: string } & LoginOptions,\n ): Promise<AuthTokens> {\n return this.runLogin(this.requireApi().loginWithPassword({\n email: input.email,\n password: input.password,\n tenantId: input.tenantId,\n offlineAccess: input.offlineAccess ?? false,\n }));\n }\n\n async logout(options: LogoutOptions = {}): Promise<void> {\n const api = this.requireApi();\n try {\n await api.logout(options.everywhere ?? false);\n } finally {\n await this.tokenStorage.clear();\n if (this.inactivityTracker !== undefined) {\n await this.inactivityTracker.clear();\n }\n }\n }\n\n async requestPasswordReset(input: { email: string; tenantId?: string }): Promise<void> {\n return this.requireApi().forgotPassword({ email: input.email, tenantId: input.tenantId });\n }\n\n async confirmPasswordReset(input: { token: string; newPassword: string }): Promise<void> {\n return this.requireApi().resetPassword({ token: input.token, newPassword: input.newPassword });\n }\n\n /** Internal: run a login HTTP call, persist tokens, mark inactivity-active. */\n private async runLogin(promise: Promise<RawAuthLoginResponse>): Promise<AuthTokens> {\n const raw = await promise;\n if (typeof raw.access_token !== 'string' || raw.access_token === '') {\n throw new Error('AuthClient: login response missing access_token');\n }\n // After the guard above, raw.access_token is `string`; widen the optional-shaped\n // RawAuthLoginResponse into the strict RawTokenResponse the normaliser expects.\n const normalized = normalizeTokenResponse({ ...raw, access_token: raw.access_token });\n const tokens = tokenResponseToAuthTokens(normalized);\n await this.tokenStorage.write(tokens);\n if (this.inactivityTracker !== undefined) {\n await this.inactivityTracker.markActive();\n }\n if (this.onTokenAcquired !== undefined) {\n this.onTokenAcquired(tokens);\n }\n return tokens;\n }\n\n private requireApi(): AuthApiClient {\n if (this.api === undefined) {\n throw new Error('AuthClient: no AuthApiClient configured');\n }\n return this.api;\n }\n\n private resolveScope(offlineAccess?: boolean): string {\n if (offlineAccess !== true) {\n return this.scope;\n }\n if (this.scope.includes(OFFLINE_ACCESS_SCOPE)) {\n return this.scope;\n }\n return `${this.scope} ${OFFLINE_ACCESS_SCOPE}`.trim();\n }\n}\n","/**\n * OIDC discovery document fetcher.\n *\n * Fetches `{issuer}/.well-known/openid-configuration` and caches the result\n * per-issuer for the lifetime of the process. Discovery responses are stable\n * for hours; the cache prevents the auth flow from hitting KC on every login.\n *\n * Pure (no React, no hooks). Consumed by app-side hooks that orchestrate the\n * PKCE flow.\n */\n\nimport type { HttpClient } from '../http/HttpClient';\n\n/**\n * Subset of the OIDC Discovery 1.0 metadata the auth-client needs.\n *\n * The KC discovery doc carries many more fields; we type only what the PKCE\n * flow consumes to keep the surface small and to fail loudly when KC ever\n * stops returning one of these.\n */\nexport interface OidcDiscoveryDocument {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n end_session_endpoint?: string;\n userinfo_endpoint?: string;\n jwks_uri?: string;\n}\n\nexport interface FetchDiscoveryDocumentInput {\n /** Issuer URL — `{baseUrl}/realms/{realm}`. Trailing slash tolerated. */\n issuerUrl: string;\n /** Transport. Pass `createFetchHttpClient(fetch)` in browser/Node18+. */\n http: HttpClient;\n}\n\nconst cache = new Map<string, OidcDiscoveryDocument>();\n\nfunction normalizeIssuer(issuerUrl: string): string {\n return issuerUrl.replace(/\\/$/, '');\n}\n\nfunction isOidcDiscoveryDocument(data: unknown): data is OidcDiscoveryDocument {\n if (data === null || typeof data !== 'object') {\n return false;\n }\n const d = data as Record<string, unknown>;\n return (\n typeof d.issuer === 'string'\n && d.issuer !== ''\n && typeof d.authorization_endpoint === 'string'\n && d.authorization_endpoint !== ''\n && typeof d.token_endpoint === 'string'\n && d.token_endpoint !== ''\n );\n}\n\n/**\n * Fetch + cache the OIDC discovery document for an issuer.\n *\n * Cache key = normalized issuer URL (trailing slash stripped).\n *\n * @throws Error when the HTTP call fails, returns non-2xx, or returns a body\n * missing required OIDC metadata fields.\n */\nexport async function fetchDiscoveryDocument(\n input: FetchDiscoveryDocumentInput,\n): Promise<OidcDiscoveryDocument> {\n const key = normalizeIssuer(input.issuerUrl);\n const cached = cache.get(key);\n if (cached !== undefined) {\n return cached;\n }\n const response = await input.http({\n url: `${key}/.well-known/openid-configuration`,\n method: 'GET',\n });\n if (!response.ok) {\n throw new Error(\n `OIDC discovery failed: ${String(response.status)} for ${key}`,\n );\n }\n if (!isOidcDiscoveryDocument(response.data)) {\n throw new Error(`OIDC discovery returned invalid metadata for ${key}`);\n }\n cache.set(key, response.data);\n return response.data;\n}\n\n/**\n * Clear the per-issuer discovery cache. Test-only — production code does not\n * call this. Useful when a test mocks different metadata across cases.\n */\nexport function clearDiscoveryCache(): void {\n cache.clear();\n}\n","/**\n * PKCE (RFC 7636) primitives for the OIDC authorization-code flow.\n *\n * Pure (no React). Browser-compatible — uses `crypto.subtle` for SHA-256 and\n * `crypto.getRandomValues` for the verifier. Node 16+ exposes both via\n * `globalThis.crypto`.\n */\n\n/** RFC 7636 §4.1: code_verifier MUST be 43..128 chars from the unreserved set. */\nconst VERIFIER_MIN_LENGTH = 43;\nconst VERIFIER_MAX_LENGTH = 128;\nconst DEFAULT_VERIFIER_LENGTH = 64;\nconst RANDOM_BYTES_PER_CHAR = 1;\n\nconst UNRESERVED_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n\nfunction getCrypto(): Crypto {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n // Runtime check: in some Node test environments `crypto.subtle` may not\n // exist even though the TS lib types mark it as non-optional.\n // eslint-disable-next-line sonarjs/different-types-comparison, @typescript-eslint/no-unnecessary-condition\n if (c === undefined || c.subtle === undefined) {\n throw new Error('pkce: globalThis.crypto.subtle is required (Node 16+ / modern browser)');\n }\n return c;\n}\n\nfunction assertVerifierLength(length: number): void {\n if (length < VERIFIER_MIN_LENGTH || length > VERIFIER_MAX_LENGTH) {\n throw new Error(`pkce: code_verifier length must be ${String(VERIFIER_MIN_LENGTH)}-${String(VERIFIER_MAX_LENGTH)} chars (RFC 7636)`);\n }\n}\n\n/**\n * Base64-URL encode an ArrayBuffer (no padding, `-` and `_` substitutions).\n *\n * Required for the S256 challenge — RFC 7636 §4.2.\n */\nfunction base64UrlEncode(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i] as number);\n }\n const b64 = (globalThis as { btoa?: (s: string) => string }).btoa?.(binary)\n ?? Buffer.from(binary, 'binary').toString('base64');\n // Strip trailing '=' padding by slicing — avoids the sonarjs/slow-regex\n // warning on /=+$/ even though base64 padding is bounded to 0..2 chars.\n let end = b64.length;\n while (end > 0 && b64.charCodeAt(end - 1) === '='.charCodeAt(0)) {\n end -= 1;\n }\n return b64.slice(0, end).replace(/\\+/g, '-').replace(/\\//g, '_');\n}\n\n/**\n * Generate a cryptographically random PKCE code_verifier.\n *\n * Default length 64 sits well inside the RFC 7636 43..128 band.\n *\n * @throws Error when `length` falls outside the RFC band.\n */\nexport function generateCodeVerifier(length: number = DEFAULT_VERIFIER_LENGTH): string {\n assertVerifierLength(length);\n const crypto = getCrypto();\n const bytes = new Uint8Array(length * RANDOM_BYTES_PER_CHAR);\n crypto.getRandomValues(bytes);\n let out = '';\n for (let i = 0; i < length; i++) {\n const byte = bytes[i] as number;\n out += UNRESERVED_CHARS[byte % UNRESERVED_CHARS.length];\n }\n return out;\n}\n\n/**\n * Derive the S256 code_challenge from a code_verifier.\n *\n * `code_challenge = BASE64URL(SHA256(code_verifier))` — RFC 7636 §4.2.\n *\n * @throws Error when `verifier` is shorter than 43 or longer than 128 chars.\n */\nexport async function deriveCodeChallenge(verifier: string): Promise<string> {\n assertVerifierLength(verifier.length);\n const crypto = getCrypto();\n const data = new TextEncoder().encode(verifier);\n const digest = await crypto.subtle.digest('SHA-256', data);\n return base64UrlEncode(digest);\n}\n\nexport interface PkcePair {\n codeVerifier: string;\n codeChallenge: string;\n codeChallengeMethod: 'S256';\n}\n\n/**\n * Convenience: produce a fresh verifier + matching challenge in one call.\n */\nexport async function generatePkcePair(length?: number): Promise<PkcePair> {\n const codeVerifier = generateCodeVerifier(length);\n const codeChallenge = await deriveCodeChallenge(codeVerifier);\n return { codeVerifier, codeChallenge, codeChallengeMethod: 'S256' };\n}\n","/**\n * Inputs for the OAuth `authorization_code` token request.\n */\nexport interface AuthorizationCodeBodyInput {\n clientId: string;\n code: string;\n redirectUri: string;\n codeVerifier: string;\n}\n\n/**\n * Inputs for the OAuth `refresh_token` token request.\n */\nexport interface RefreshTokenBodyInput {\n clientId: string;\n refreshToken: string;\n}\n\n/**\n * Build the `application/x-www-form-urlencoded` body for the\n * `grant_type=authorization_code` token endpoint call (PKCE flow).\n */\nexport function buildAuthorizationCodeBody(input: AuthorizationCodeBodyInput): string {\n return new URLSearchParams({\n client_id: input.clientId,\n grant_type: 'authorization_code',\n code: input.code,\n redirect_uri: input.redirectUri,\n code_verifier: input.codeVerifier,\n }).toString();\n}\n\n/**\n * Build the `application/x-www-form-urlencoded` body for the\n * `grant_type=refresh_token` token endpoint call.\n */\nexport function buildRefreshTokenBody(input: RefreshTokenBodyInput): string {\n return new URLSearchParams({\n client_id: input.clientId,\n grant_type: 'refresh_token',\n refresh_token: input.refreshToken,\n }).toString();\n}\n","/**\n * OIDC token-endpoint helpers.\n *\n * Pure (no React, no hooks). Wraps the realm-aware token endpoint with the\n * PKCE `authorization_code` and `refresh_token` grants. The transport is\n * injected so callers can use the HTTP client of their choice.\n *\n * Use these from app-side hooks (e.g. `useKeycloakExchange`) instead of\n * duplicating the body-builder + POST + normalise dance.\n */\n\nimport { buildTokenEndpoint } from '../utils/buildKeycloakUrls';\nimport {\n buildAuthorizationCodeBody,\n buildRefreshTokenBody,\n} from '../utils/buildTokenRequestBody';\nimport { normalizeTokenResponse } from '../utils/normalizeTokenResponse';\n\nimport type { HttpClient } from '../http/HttpClient';\nimport type { RawTokenResponse, TokenResponse } from '../types/TokenResponse';\n\nconst FORM_HEADERS: Record<string, string> = {\n 'Content-Type': 'application/x-www-form-urlencoded',\n};\n\nexport interface ExchangeAuthorizationCodeInput {\n http: HttpClient;\n baseUrl: string;\n realm: string;\n clientId: string;\n code: string;\n redirectUri: string;\n codeVerifier: string;\n}\n\nexport interface RefreshAccessTokenInput {\n http: HttpClient;\n baseUrl: string;\n realm: string;\n clientId: string;\n refreshToken: string;\n}\n\nasync function postTokenEndpoint(\n http: HttpClient,\n url: string,\n body: string,\n): Promise<TokenResponse> {\n const response = await http({\n url,\n method: 'POST',\n headers: FORM_HEADERS,\n body,\n });\n if (!response.ok) {\n throw new Error(`token endpoint POST failed: ${String(response.status)}`);\n }\n return normalizeTokenResponse(response.data as RawTokenResponse);\n}\n\n/**\n * Exchange a PKCE authorization `code` for tokens via the realm's token\n * endpoint (`grant_type=authorization_code`).\n *\n * @throws Error when the HTTP call returns non-2xx or the body is missing\n * `access_token`.\n */\nexport async function exchangeAuthorizationCode(\n input: ExchangeAuthorizationCodeInput,\n): Promise<TokenResponse> {\n const url = buildTokenEndpoint(input.baseUrl, input.realm);\n const body = buildAuthorizationCodeBody({\n clientId: input.clientId,\n code: input.code,\n redirectUri: input.redirectUri,\n codeVerifier: input.codeVerifier,\n });\n return postTokenEndpoint(input.http, url, body);\n}\n\n/**\n * Swap a refresh token for a fresh access/refresh-token pair via the realm's\n * token endpoint (`grant_type=refresh_token`).\n *\n * @throws Error when the HTTP call returns non-2xx or the body is missing\n * `access_token`.\n */\nexport async function refreshAccessToken(\n input: RefreshAccessTokenInput,\n): Promise<TokenResponse> {\n const url = buildTokenEndpoint(input.baseUrl, input.realm);\n const body = buildRefreshTokenBody({\n clientId: input.clientId,\n refreshToken: input.refreshToken,\n });\n return postTokenEndpoint(input.http, url, body);\n}\n","/**\n * Roles emitted by Keycloak realms in the dloizides.com portfolio.\n *\n * Lives in its own file per the project convention: each exported `const enum`\n * sits alone so it can be imported without dragging the rest of the type tree.\n */\nexport const enum KeycloakRoles {\n SuperUser = 'superUser',\n Admin = 'admin',\n User = 'user',\n}\n\nconst KEYCLOAK_ROLE_VALUES: readonly string[] = [\n KeycloakRoles.SuperUser,\n KeycloakRoles.Admin,\n KeycloakRoles.User,\n];\n\n/**\n * Type guard that narrows a string to a known {@link KeycloakRoles} value.\n *\n * Use this when ingesting role claims from the network, where the wire payload\n * is `string[]` but downstream code wants `KeycloakRoles[]`.\n */\nexport function isKeycloakRole(value: string): value is KeycloakRoles {\n return KEYCLOAK_ROLE_VALUES.includes(value);\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * Subset of `Storage` we actually use. Lets callers inject `localStorage`,\n * `sessionStorage`, or any compatible polyfill.\n */\nexport interface StorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nexport interface BrowserStorageTokenStorageOptions {\n storage: StorageLike;\n /** Storage key. Defaults to `auth.tokens`. */\n key?: string;\n}\n\nconst DEFAULT_KEY = 'auth.tokens';\n\nfunction isAuthTokens(value: unknown): value is AuthTokens {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const candidate = value as Record<string, unknown>;\n return typeof candidate.accessToken === 'string' && typeof candidate.expiresAt === 'number';\n}\n\n/**\n * Persist tokens in any `Storage`-shaped backend (`localStorage`, `sessionStorage`,\n * AsyncStorage shim, etc.). The class is sync-aware but exposes a Promise-based\n * API to match {@link TokenStorage}.\n *\n * Errors during read are swallowed and surfaced as `null` (corrupt JSON, denied\n * access in some private-mode browsers, etc.). Errors during write/clear are\n * propagated so callers can decide whether to retry or fall back.\n */\nexport class BrowserStorageTokenStorage implements TokenStorage {\n private readonly storage: StorageLike;\n private readonly key: string;\n\n constructor(options: BrowserStorageTokenStorageOptions) {\n this.storage = options.storage;\n this.key = options.key ?? DEFAULT_KEY;\n }\n\n read(): Promise<AuthTokens | null> {\n return Promise.resolve(this.readSync());\n }\n\n write(tokens: AuthTokens): Promise<void> {\n this.storage.setItem(this.key, JSON.stringify(tokens));\n return Promise.resolve();\n }\n\n clear(): Promise<void> {\n this.storage.removeItem(this.key);\n return Promise.resolve();\n }\n\n private readSync(): AuthTokens | null {\n try {\n const raw = this.storage.getItem(this.key);\n if (raw === null || raw === '') {\n return null;\n }\n const parsed: unknown = JSON.parse(raw);\n return isAuthTokens(parsed) ? parsed : null;\n } catch {\n return null;\n }\n }\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * In-memory storage backed by a single instance variable.\n *\n * Useful for tests, server-side rendering, and as a default fallback when no\n * platform-specific storage is available. Tokens are lost on process exit.\n */\nexport class InMemoryTokenStorage implements TokenStorage {\n private tokens: AuthTokens | null = null;\n\n read(): Promise<AuthTokens | null> {\n return Promise.resolve(this.tokens);\n }\n\n write(tokens: AuthTokens): Promise<void> {\n this.tokens = tokens;\n return Promise.resolve();\n }\n\n clear(): Promise<void> {\n this.tokens = null;\n return Promise.resolve();\n }\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * Web token storage that pairs an in-memory access token with a backend-managed\n * httpOnly + Secure + SameSite=Lax refresh-token cookie.\n *\n * The browser handles the refresh cookie (`__Host-refresh` by default — set by\n * the IdentityService on login and rotated on every `/auth/refresh-cookie`\n * call). JavaScript MUST NOT have access to it, so this adapter intentionally\n * does NOT persist `refreshToken` into the cookie itself; that's the backend's\n * job. The adapter just keeps the access token in memory and exposes the same\n * `TokenStorage` interface as `BrowserStorageTokenStorage` so the rest of the\n * library doesn't need to know which transport is in use.\n *\n * Page reloads drop the access token (memory clears), but the refresh cookie\n * survives — `RefreshInterceptor` swaps it for a new access token via\n * `/auth/refresh-cookie` with `credentials: 'include'`.\n */\nexport class CookieTokenStorage implements TokenStorage {\n private accessToken: string | null = null;\n private idToken: string | undefined = undefined;\n private expiresAt: number = 0;\n\n read(): Promise<AuthTokens | null> {\n if (this.accessToken === null) {\n return Promise.resolve(null);\n }\n const tokens: AuthTokens = {\n accessToken: this.accessToken,\n idToken: this.idToken,\n expiresAt: this.expiresAt,\n };\n return Promise.resolve(tokens);\n }\n\n write(tokens: AuthTokens): Promise<void> {\n this.accessToken = tokens.accessToken;\n this.idToken = tokens.idToken;\n this.expiresAt = tokens.expiresAt;\n // Intentionally drop tokens.refreshToken — the backend cookie is the\n // source of truth for refresh material, and we never want it on JS heap.\n return Promise.resolve();\n }\n\n clear(): Promise<void> {\n this.accessToken = null;\n this.idToken = undefined;\n this.expiresAt = 0;\n return Promise.resolve();\n }\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * Subset of `expo-secure-store` we use, abstracted so the package itself never\n * imports `expo-secure-store` (and so web bundles never pull it in).\n *\n * Mobile consumers wire this up at the edge:\n *\n * ```ts\n * import * as SecureStore from 'expo-secure-store';\n * const adapter: SecureStoreLike = {\n * getItemAsync: SecureStore.getItemAsync,\n * setItemAsync: SecureStore.setItemAsync,\n * deleteItemAsync: SecureStore.deleteItemAsync,\n * };\n * ```\n */\nexport interface SecureStoreLike {\n getItemAsync(key: string, options?: { requireAuthentication?: boolean }): Promise<string | null>;\n setItemAsync(key: string, value: string, options?: { requireAuthentication?: boolean }): Promise<void>;\n deleteItemAsync(key: string, options?: { requireAuthentication?: boolean }): Promise<void>;\n}\n\n/**\n * Optional biometric gate. When provided AND `requireBiometric` is `true`, the\n * gate's `unlock()` is called before reading the refresh token. Used by mobile\n * consumers that opt in to biometric-protected sessions.\n */\nexport interface BiometricGateLike {\n unlock(): Promise<void>;\n isEnabled(): boolean;\n}\n\nexport interface SecureStoreTokenStorageOptions {\n secureStore: SecureStoreLike;\n /** Defaults applied to every key — usually `'auth'`. */\n keyPrefix?: string;\n /**\n * When true, secure-store reads use `requireAuthentication: true`, prompting\n * the OS biometric / device-passcode dialog (iOS Keychain access control,\n * Android Keystore strongbox).\n */\n requireAuthentication?: boolean;\n /**\n * Optional biometric gate run BEFORE the secure-store read. Belt-and-braces\n * with `requireAuthentication`: the gate enforces our own retry/lockout\n * semantics, while `requireAuthentication` enforces the OS keychain ACL.\n */\n biometricGate?: BiometricGateLike;\n}\n\nconst DEFAULT_PREFIX = 'auth';\nconst ACCESS_KEY = 'access';\nconst REFRESH_KEY = 'refresh';\nconst ID_KEY = 'id';\nconst EXPIRES_KEY = 'expiresAt';\n\n/**\n * Persist tokens in iOS Keychain / Android Keystore via `expo-secure-store`.\n *\n * Keys are split (access / refresh / id / expiresAt) rather than stored as a\n * single JSON blob so the OS-level ACL on the refresh token slot can be\n * tightened independently. With `requireAuthentication: true`, reads of any\n * key trigger the OS biometric prompt — that's why we keep it OFF for writes\n * (login flows must not prompt) and ON for reads (boot-time session restore).\n *\n * Storage key shape: `{prefix}.{slot}` (e.g. `auth.refresh`).\n */\nexport class SecureStoreTokenStorage implements TokenStorage {\n private readonly secureStore: SecureStoreLike;\n private readonly prefix: string;\n private readonly requireAuthentication: boolean;\n private readonly biometricGate: BiometricGateLike | undefined;\n\n constructor(options: SecureStoreTokenStorageOptions) {\n this.secureStore = options.secureStore;\n this.prefix = options.keyPrefix ?? DEFAULT_PREFIX;\n this.requireAuthentication = options.requireAuthentication ?? false;\n this.biometricGate = options.biometricGate;\n }\n\n async read(): Promise<AuthTokens | null> {\n if (this.shouldRunBiometricGate()) {\n // Biometric gate may throw — let it propagate. Caller (RefreshInterceptor\n // / AuthClient.init) will treat that as a failed restore and clear.\n await (this.biometricGate as BiometricGateLike).unlock();\n }\n const readOptions = this.requireAuthentication ? { requireAuthentication: true } : undefined;\n const accessToken = await this.secureStore.getItemAsync(this.fullKey(ACCESS_KEY), readOptions);\n if (accessToken === null) {\n return null;\n }\n const refreshTokenRaw = await this.secureStore.getItemAsync(this.fullKey(REFRESH_KEY), readOptions);\n const idTokenRaw = await this.secureStore.getItemAsync(this.fullKey(ID_KEY));\n const expiresAtRaw = await this.secureStore.getItemAsync(this.fullKey(EXPIRES_KEY));\n const expiresAt = parseExpiresAt(expiresAtRaw);\n return {\n accessToken,\n refreshToken: refreshTokenRaw === null ? undefined : refreshTokenRaw,\n idToken: idTokenRaw === null ? undefined : idTokenRaw,\n expiresAt,\n };\n }\n\n async write(tokens: AuthTokens): Promise<void> {\n await this.secureStore.setItemAsync(this.fullKey(ACCESS_KEY), tokens.accessToken);\n if (tokens.refreshToken !== undefined && tokens.refreshToken !== '') {\n await this.secureStore.setItemAsync(this.fullKey(REFRESH_KEY), tokens.refreshToken);\n } else {\n await this.secureStore.deleteItemAsync(this.fullKey(REFRESH_KEY));\n }\n if (tokens.idToken !== undefined && tokens.idToken !== '') {\n await this.secureStore.setItemAsync(this.fullKey(ID_KEY), tokens.idToken);\n } else {\n await this.secureStore.deleteItemAsync(this.fullKey(ID_KEY));\n }\n await this.secureStore.setItemAsync(this.fullKey(EXPIRES_KEY), String(tokens.expiresAt));\n }\n\n async clear(): Promise<void> {\n await this.secureStore.deleteItemAsync(this.fullKey(ACCESS_KEY));\n await this.secureStore.deleteItemAsync(this.fullKey(REFRESH_KEY));\n await this.secureStore.deleteItemAsync(this.fullKey(ID_KEY));\n await this.secureStore.deleteItemAsync(this.fullKey(EXPIRES_KEY));\n }\n\n private shouldRunBiometricGate(): boolean {\n return this.biometricGate !== undefined && this.biometricGate.isEnabled();\n }\n\n private fullKey(slot: string): string {\n return `${this.prefix}.${slot}`;\n }\n}\n\nfunction parseExpiresAt(raw: string | null): number {\n if (raw === null || raw === '') {\n return 0;\n }\n const parsed = Number(raw);\n return Number.isFinite(parsed) ? parsed : 0;\n}\n","/**\n * Subset of `expo-local-authentication` we use, abstracted so the package\n * itself never imports `expo-local-authentication`.\n *\n * Mobile consumers wire this up at the edge:\n *\n * ```ts\n * import * as LocalAuthentication from 'expo-local-authentication';\n * const adapter: LocalAuthLike = {\n * hasHardwareAsync: LocalAuthentication.hasHardwareAsync,\n * isEnrolledAsync: LocalAuthentication.isEnrolledAsync,\n * authenticateAsync: (opts) => LocalAuthentication.authenticateAsync(opts),\n * };\n * ```\n */\nexport interface LocalAuthLike {\n hasHardwareAsync(): Promise<boolean>;\n isEnrolledAsync(): Promise<boolean>;\n authenticateAsync(options?: {\n promptMessage?: string;\n cancelLabel?: string;\n disableDeviceFallback?: boolean;\n }): Promise<{ success: boolean; error?: string }>;\n}\n\n/**\n * Optional persistence so the \"enabled\" flag survives app restart. Backed by\n * any `TokenStorage`-shaped key/value store via the calling consumer (or the\n * app's settings store). We keep it pluggable to avoid coupling\n * `BiometricGate` to a specific storage adapter.\n */\nexport interface BiometricFlagStore {\n read(): Promise<boolean>;\n write(enabled: boolean): Promise<void>;\n}\n\nexport interface BiometricGateOptions {\n localAuth: LocalAuthLike;\n /** Optional persistence for the user's opt-in choice. */\n flagStore?: BiometricFlagStore;\n /** Default prompt message; consumers usually override. */\n promptMessage?: string;\n /** Max consecutive prompt failures before {@link unlock} throws. Default 3. */\n maxFailures?: number;\n}\n\nconst DEFAULT_PROMPT = 'Unlock to continue';\nconst DEFAULT_MAX_FAILURES = 3;\n\n/**\n * Biometric gate wrapping `expo-local-authentication`.\n *\n * Lifecycle:\n *\n * 1. `isAvailable()` — checks hardware + enrolment. Pure read.\n * 2. `setEnabled(true|false)` — consumer's settings UI flips this. Persisted\n * via the optional flag store. Default = disabled (opt-in).\n * 3. `unlock()` — called by `SecureStoreTokenStorage` (when wired) or by\n * consumer code before sensitive operations. Counts consecutive failures;\n * after `maxFailures` (default 3), throws `BiometricLockedOutError` and\n * consumers MUST navigate to login.\n * 4. `prompt()` — one-shot biometric prompt that doesn't change the failure\n * counter. Useful for re-confirming an action mid-session.\n *\n * The failure counter resets on success.\n */\nexport class BiometricGate {\n private readonly localAuth: LocalAuthLike;\n private readonly flagStore: BiometricFlagStore | undefined;\n private readonly promptMessage: string;\n private readonly maxFailures: number;\n private enabled: boolean = false;\n private failureCount: number = 0;\n private hydrated: boolean = false;\n\n constructor(options: BiometricGateOptions) {\n this.localAuth = options.localAuth;\n this.flagStore = options.flagStore;\n this.promptMessage = options.promptMessage ?? DEFAULT_PROMPT;\n this.maxFailures = options.maxFailures ?? DEFAULT_MAX_FAILURES;\n }\n\n /** Hardware present AND a fingerprint/face ID is enrolled. */\n async isAvailable(): Promise<boolean> {\n const hasHardware = await this.localAuth.hasHardwareAsync();\n if (!hasHardware) {\n return false;\n }\n return this.localAuth.isEnrolledAsync();\n }\n\n /** Synchronous read of the current enabled flag (post-hydration). */\n isEnabled(): boolean {\n return this.enabled;\n }\n\n /** Read the persisted opt-in flag once at app boot. Idempotent. */\n async hydrate(): Promise<void> {\n if (this.hydrated) {\n return;\n }\n this.hydrated = true;\n if (this.flagStore !== undefined) {\n this.enabled = await this.flagStore.read();\n }\n }\n\n /**\n * Toggle biometric requirement. Persists via {@link BiometricFlagStore} when\n * configured. Resets the failure counter so a re-enable starts fresh.\n */\n async setEnabled(enabled: boolean): Promise<void> {\n this.enabled = enabled;\n this.failureCount = 0;\n if (this.flagStore !== undefined) {\n await this.flagStore.write(enabled);\n }\n }\n\n /** Reset the failure counter. Tests + consumer recovery flows. */\n resetFailures(): void {\n this.failureCount = 0;\n }\n\n /**\n * One-shot biometric prompt. Returns `true` on success. Does NOT throw on\n * failure or update the failure counter — useful for action confirmation.\n */\n async prompt(): Promise<boolean> {\n const result = await this.localAuth.authenticateAsync({\n promptMessage: this.promptMessage,\n });\n return result.success;\n }\n\n /**\n * Required pre-condition for sensitive token reads. No-op when disabled.\n *\n * @throws Error after {@link maxFailures} consecutive failures.\n * @throws Error on a single failure (lower in the count, but still throws so\n * `SecureStoreTokenStorage.read()` short-circuits).\n */\n async unlock(): Promise<void> {\n if (!this.enabled) {\n return;\n }\n const result = await this.localAuth.authenticateAsync({\n promptMessage: this.promptMessage,\n });\n if (result.success) {\n this.failureCount = 0;\n return;\n }\n this.failureCount += 1;\n if (this.failureCount >= this.maxFailures) {\n throw new Error('Biometric authentication failed; locked out');\n }\n throw new Error('Biometric authentication failed');\n }\n}\n","import type { AuthEventEmitter } from '../events/AuthEventEmitter';\nimport type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * The pluggable refresh function the interceptor calls when an access token\n * is missing or expired. Implementations differ per transport:\n *\n * - **Mobile (SecureStore)**: posts to `/auth/refresh` with the refresh token\n * from `AuthTokens.refreshToken`.\n * - **Web (Cookie)**: posts to `/auth/refresh-cookie` with `credentials:\n * 'include'` — the refresh token rides on the httpOnly cookie; `current`\n * carries only the access token (refresh token will be undefined).\n *\n * Returns the new token bundle, or `null` when refresh failed in a way that\n * means \"session over\" (e.g., 401 from the auth server).\n */\nexport type RefreshFn = (current: AuthTokens | null) => Promise<AuthTokens | null>;\n\nexport interface RefreshInterceptorOptions {\n storage: TokenStorage;\n refresh: RefreshFn;\n events: AuthEventEmitter;\n /**\n * Optional callback fired AFTER tokens are persisted on a successful\n * refresh. Used by `AuthClient` to update the inactivity tracker.\n */\n onRefreshSuccess?: (tokens: AuthTokens) => Promise<void> | void;\n}\n\n/**\n * Coordinates refresh-token swaps so concurrent 401s don't trigger N parallel\n * refreshes. The first caller to hit `refreshTokens()` while no refresh is\n * already in flight wins the role of \"refresher\"; everyone else awaits the\n * same promise.\n *\n * On failure, storage is cleared and `sessionExpired` is emitted exactly once\n * per refresh attempt.\n */\nexport class RefreshInterceptor {\n private readonly storage: TokenStorage;\n private readonly refresh: RefreshFn;\n private readonly events: AuthEventEmitter;\n private readonly onRefreshSuccess: ((tokens: AuthTokens) => Promise<void> | void) | undefined;\n private inflight: Promise<AuthTokens | null> | null = null;\n\n constructor(options: RefreshInterceptorOptions) {\n this.storage = options.storage;\n this.refresh = options.refresh;\n this.events = options.events;\n this.onRefreshSuccess = options.onRefreshSuccess;\n }\n\n /**\n * Trigger (or join) a refresh. Returns the new tokens, or `null` if the\n * refresh failed — in which case storage has already been cleared and\n * `sessionExpired` already fired.\n */\n async refreshTokens(): Promise<AuthTokens | null> {\n if (this.inflight !== null) {\n return this.inflight;\n }\n this.inflight = this.runRefresh();\n try {\n return await this.inflight;\n } finally {\n this.inflight = null;\n }\n }\n\n /**\n * Whether a refresh is currently in flight. Exposed for tests / debug.\n */\n get isRefreshing(): boolean {\n return this.inflight !== null;\n }\n\n private async runRefresh(): Promise<AuthTokens | null> {\n const current = await this.storage.read();\n let next: AuthTokens | null;\n try {\n next = await this.refresh(current);\n } catch {\n await this.failHard();\n return null;\n }\n if (next === null) {\n await this.failHard();\n return null;\n }\n await this.storage.write(next);\n if (this.onRefreshSuccess !== undefined) {\n await this.onRefreshSuccess(next);\n }\n return next;\n }\n\n private async failHard(): Promise<void> {\n await this.storage.clear();\n this.events.emit('sessionExpired');\n }\n}\n","/**\n * Pluggable persistence for the `lastRefreshedAt` timestamp.\n *\n * Decoupled from `TokenStorage` so consumers can pick a different backend\n * (e.g., write through `AsyncStorage` on RN where the secure store would\n * gate every read on biometric).\n */\nexport interface InactivityStore {\n read(): Promise<number | null>;\n write(timestamp: number): Promise<void>;\n clear(): Promise<void>;\n}\n\nexport interface InactivityTrackerOptions {\n store: InactivityStore;\n /**\n * Maximum days the user can be inactive (no successful refresh) before\n * sessions are forcibly cleared. Default 90 (matches mobile decision).\n */\n maxInactivityDays?: number;\n /** Inject for tests; defaults to `Date.now`. */\n now?: () => number;\n}\n\nconst DEFAULT_MAX_DAYS = 90;\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\n\n/**\n * Tracks the last time a refresh succeeded and decides whether the session\n * has aged past its inactivity threshold.\n *\n * - `markActive(now?)` is called by `RefreshInterceptor` after every\n * successful token swap.\n * - `isExpired()` is called from `AuthClient.init()` at boot. If true,\n * consumers clear tokens and emit `sessionExpired`.\n *\n * Choosing days (not e.g. minutes) makes the policy match what users\n * understand: a session left untouched for 90 days needs re-auth.\n */\nexport class InactivityTracker {\n private readonly store: InactivityStore;\n private readonly maxInactivityMs: number;\n private readonly now: () => number;\n\n constructor(options: InactivityTrackerOptions) {\n this.store = options.store;\n const days = options.maxInactivityDays ?? DEFAULT_MAX_DAYS;\n this.maxInactivityMs = days * MS_PER_DAY;\n this.now = options.now ?? Date.now;\n }\n\n async markActive(timestamp?: number): Promise<void> {\n await this.store.write(timestamp ?? this.now());\n }\n\n async getLastActive(): Promise<number | null> {\n return this.store.read();\n }\n\n async isExpired(): Promise<boolean> {\n const last = await this.store.read();\n if (last === null) {\n // Never refreshed — treat as not expired so a fresh login isn't punished.\n return false;\n }\n return this.now() - last > this.maxInactivityMs;\n }\n\n async clear(): Promise<void> {\n await this.store.clear();\n }\n}\n","/**\n * Minimal HTTP transport the package depends on.\n *\n * `AuthClient` orchestrates token-related HTTP calls (login, refresh, logout,\n * password reset) but doesn't import `fetch` directly — keeping the package\n * runtime-agnostic. Consumers wire native fetch, axios, ky, or whatever their\n * platform exposes.\n */\nexport interface HttpRequest {\n url: string;\n method: 'GET' | 'POST' | 'DELETE';\n headers?: Record<string, string>;\n /** When set, body is sent as the request body; serialization is the caller's job. */\n body?: string;\n /** Browser fetch only — pass through `credentials: 'include'` for cookie auth. */\n credentials?: 'include' | 'same-origin' | 'omit';\n}\n\nexport interface HttpResponse {\n status: number;\n ok: boolean;\n /** Parsed body (already JSON-decoded). `undefined` for 204 / empty bodies. */\n data?: unknown;\n}\n\nexport type HttpClient = (request: HttpRequest) => Promise<HttpResponse>;\n\n/**\n * Wrap the platform's native `fetch` into the package's `HttpClient` shape.\n * Decoded JSON when `Content-Type` is JSON, otherwise leaves data undefined.\n *\n * Errors thrown by `fetch` (network / abort) are NOT swallowed — callers\n * decide whether to treat them as session-ending.\n */\nexport function createFetchHttpClient(fetchImpl: typeof fetch): HttpClient {\n return async (request: HttpRequest): Promise<HttpResponse> => {\n const init: RequestInit = {\n method: request.method,\n headers: request.headers,\n body: request.body,\n };\n if (request.credentials !== undefined) {\n init.credentials = request.credentials;\n }\n const response = await fetchImpl(request.url, init);\n let data: unknown;\n if (response.status !== 204) {\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.includes('application/json')) {\n data = (await response.json()) as unknown;\n }\n }\n return {\n status: response.status,\n ok: response.ok,\n data,\n };\n };\n}\n","import type { HttpClient } from '../http/HttpClient';\n\n/**\n * Backend session record returned by `GET /me/sessions`.\n *\n * Shape mirrors the existing IdentityService response — see\n * `Services/Identity/.../GetSessions.cs`. Defined as a permissive interface\n * so newer fields (added server-side) flow through without a package bump.\n */\nexport interface AuthSessionInfo {\n id: string;\n isCurrent?: boolean;\n ipAddress?: string;\n userAgent?: string;\n createdAt?: string;\n lastSeenAt?: string;\n [key: string]: unknown;\n}\n\nexport interface AuthApiClientOptions {\n http: HttpClient;\n /** API base, e.g. `https://api.dloizides.com`. No trailing slash needed. */\n baseUrl: string;\n /**\n * Optional supplier of the current access token, used as a Bearer header on\n * authenticated calls (sessions list, revoke, logout). When omitted those\n * calls send no Authorization header — typical for cookie-based web auth.\n */\n getAccessToken?: () => Promise<string | null>;\n /**\n * When true, every request adds `credentials: 'include'`. Required for\n * cookie-based web auth (`__Host-refresh` lives in an httpOnly cookie).\n */\n useCredentials?: boolean;\n}\n\nexport interface OtpLoginRequest {\n email: string;\n otp: string;\n tenantId?: string;\n offlineAccess?: boolean;\n}\n\nexport interface PasswordLoginRequest {\n email: string;\n password: string;\n tenantId?: string;\n offlineAccess?: boolean;\n}\n\nexport interface ForgotPasswordRequest {\n email: string;\n tenantId?: string;\n}\n\nexport interface ResetPasswordRequest {\n token: string;\n newPassword: string;\n}\n\nexport interface RawAuthLoginResponse {\n access_token?: string;\n refresh_token?: string;\n id_token?: string;\n expires_in?: number;\n token_type?: string;\n scope?: string;\n [key: string]: unknown;\n}\n\n/**\n * Thin HTTP client for the IdentityService auth surface.\n *\n * Endpoint paths match the backend task `auth-password-reset-backend.md`:\n *\n * - `POST /auth/verify-otp`\n * - `POST /auth/login` (password)\n * - `POST /auth/logout` and `POST /auth/logout?everywhere=true`\n * - `POST /auth/refresh-cookie` (web cookie flow)\n * - `POST /auth/forgot-password`\n * - `POST /auth/reset-password`\n * - `GET /me/sessions`\n * - `POST /me/sessions/{id}/revoke`\n *\n * Doesn't touch token storage — that's `AuthClient`'s job. Doesn't decide\n * what to do with errors — callers handle them. Just builds requests and\n * deserialises responses.\n */\nexport class AuthApiClient {\n private readonly http: HttpClient;\n private readonly baseUrl: string;\n private readonly getAccessToken: (() => Promise<string | null>) | undefined;\n private readonly useCredentials: boolean;\n\n constructor(options: AuthApiClientOptions) {\n this.http = options.http;\n this.baseUrl = options.baseUrl.replace(/\\/$/, '');\n this.getAccessToken = options.getAccessToken;\n this.useCredentials = options.useCredentials ?? false;\n }\n\n loginWithOtp(request: OtpLoginRequest): Promise<RawAuthLoginResponse> {\n return this.postLogin('/auth/verify-otp', request);\n }\n\n loginWithPassword(request: PasswordLoginRequest): Promise<RawAuthLoginResponse> {\n return this.postLogin('/auth/login', request);\n }\n\n /** Web cookie-flow refresh. Sends no body; cookie travels via `credentials`. */\n async refreshCookie(): Promise<RawAuthLoginResponse> {\n const response = await this.http({\n url: `${this.baseUrl}/auth/refresh-cookie`,\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n credentials: this.useCredentials ? 'include' : undefined,\n });\n if (!response.ok) {\n throw new Error(`refresh-cookie failed with status ${response.status}`);\n }\n return (response.data ?? {}) as RawAuthLoginResponse;\n }\n\n async logout(everywhere: boolean = false): Promise<void> {\n const url = everywhere ? `${this.baseUrl}/auth/logout?everywhere=true` : `${this.baseUrl}/auth/logout`;\n const response = await this.http({\n url,\n method: 'POST',\n headers: await this.authHeaders(),\n credentials: this.useCredentials ? 'include' : undefined,\n });\n if (!response.ok) {\n throw new Error(`logout failed with status ${response.status}`);\n }\n }\n\n async forgotPassword(request: ForgotPasswordRequest): Promise<void> {\n const response = await this.http({\n url: `${this.baseUrl}/auth/forgot-password`,\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(request),\n });\n // Backend returns 200 unconditionally (no enumeration). Anything else is\n // a real failure.\n if (!response.ok) {\n throw new Error(`forgot-password failed with status ${response.status}`);\n }\n }\n\n async resetPassword(request: ResetPasswordRequest): Promise<void> {\n const response = await this.http({\n url: `${this.baseUrl}/auth/reset-password`,\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(request),\n });\n if (!response.ok) {\n throw new Error(`reset-password failed with status ${response.status}`);\n }\n }\n\n async listSessions(): Promise<AuthSessionInfo[]> {\n const response = await this.http({\n url: `${this.baseUrl}/me/sessions`,\n method: 'GET',\n headers: await this.authHeaders(),\n credentials: this.useCredentials ? 'include' : undefined,\n });\n if (!response.ok) {\n throw new Error(`listSessions failed with status ${response.status}`);\n }\n return Array.isArray(response.data) ? (response.data as AuthSessionInfo[]) : [];\n }\n\n async revokeSession(sessionId: string): Promise<void> {\n const response = await this.http({\n url: `${this.baseUrl}/me/sessions/${encodeURIComponent(sessionId)}/revoke`,\n method: 'POST',\n headers: await this.authHeaders(),\n credentials: this.useCredentials ? 'include' : undefined,\n });\n if (!response.ok) {\n throw new Error(`revokeSession failed with status ${response.status}`);\n }\n }\n\n private async postLogin(path: string, body: object): Promise<RawAuthLoginResponse> {\n const response = await this.http({\n url: `${this.baseUrl}${path}`,\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n credentials: this.useCredentials ? 'include' : undefined,\n });\n if (!response.ok) {\n throw new Error(`login failed with status ${response.status}`);\n }\n return (response.data ?? {}) as RawAuthLoginResponse;\n }\n\n private async authHeaders(): Promise<Record<string, string>> {\n const headers: Record<string, string> = { 'content-type': 'application/json' };\n if (this.getAccessToken === undefined) {\n return headers;\n }\n const token = await this.getAccessToken();\n if (token !== null && token !== '') {\n headers.authorization = `Bearer ${token}`;\n }\n return headers;\n }\n}\n","import type { HttpClient } from '../http/HttpClient';\n\n/**\n * Same-origin client for a per-app **Backend-For-Frontend** (`bff-katalogos`,\n * `bff-erevna`, ...). The BFF terminates authentication server-side: the\n * browser only ever holds an opaque httpOnly session cookie, never a token.\n *\n * `BffAuthClient` therefore does **no token handling at all**. Every call is a\n * same-origin `fetch` with `credentials: 'include'` so the session cookie\n * travels automatically. State-changing calls carry the `X-BFF-Csrf` header\n * the `Bff.AspNetCore` anti-forgery middleware requires.\n *\n * This is the BFF-era replacement for the direct-KC `AuthClient` / ROPC\n * adapters. It is shared so every per-app SPA wires it identically — Phase 1's\n * lesson was that copy-pasted auth adapters ship the same bug N times.\n *\n * @see `NuGetPackages/Bff.AspNetCore/README.md` — \"The SPA contract\".\n */\n\n/** The `X-BFF-Csrf` header value the BFF anti-forgery middleware checks for. */\nconst CSRF_HEADER = 'X-BFF-Csrf';\nconst CSRF_HEADER_VALUE = '1';\nconst JSON_CONTENT_TYPE = 'application/json';\n\n/** Endpoint paths under the BFF — all relative to the SPA's own origin. */\nconst ENDPOINTS = {\n login: '/bff/login',\n logout: '/bff/logout',\n me: '/bff/me',\n register: '/bff/register',\n forgotPassword: '/bff/forgot-password',\n resetPassword: '/bff/reset-password',\n} as const;\n\n/** Credentials posted to `POST /bff/login`. */\nexport interface BffLoginRequest {\n username: string;\n password: string;\n}\n\n/** Payload for `POST /bff/register` — proxied by the BFF to TenantService. */\nexport interface BffRegisterRequest {\n firstName: string;\n lastName: string;\n username: string;\n email: string;\n password: string;\n tenantName: string;\n [key: string]: unknown;\n}\n\n/** Payload for `POST /bff/forgot-password` — proxied to TenantService. */\nexport interface BffForgotPasswordRequest {\n email: string;\n /** Full URL with a `{token}` placeholder; the backend substitutes the token. */\n resetUrlTemplate?: string;\n [key: string]: unknown;\n}\n\n/** Payload for `POST /bff/reset-password` — proxied to TenantService. */\nexport interface BffResetPasswordRequest {\n token: string;\n newPassword: string;\n}\n\n/**\n * The user object returned by `GET /bff/me` and `POST /bff/login`. The BFF\n * returns the sanitised KC claims under a `user` envelope and **never** a\n * token. Kept permissive so server-added claims flow through without a bump.\n */\nexport interface BffUser {\n sub?: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n preferred_username?: string;\n given_name?: string;\n family_name?: string;\n tenantId?: string;\n roles?: string[];\n [key: string]: unknown;\n}\n\n/** Envelope shape the BFF auth endpoints respond with: `{ user: {...} }`. */\ninterface BffUserEnvelope {\n user?: BffUser;\n}\n\nexport interface BffAuthClientOptions {\n /** Runtime-agnostic HTTP transport (wrap native `fetch` with `createFetchHttpClient`). */\n http: HttpClient;\n /**\n * BFF origin. Defaults to `''` (same-origin) — the production wiring. An\n * explicit origin is only useful for tests or a non-same-origin BFF.\n */\n baseUrl?: string;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** Pull the `user` object out of a `{ user: {...} }` envelope, or `null`. */\nfunction extractUser(data: unknown): BffUser | null {\n if (!isRecord(data)) {\n return null;\n }\n const envelope = data as BffUserEnvelope;\n return isRecord(envelope.user) ? envelope.user : null;\n}\n\n/**\n * Same-origin client for a per-app BFF.\n *\n * No token storage, no refresh logic, no realm awareness — the BFF owns all of\n * that server-side. The browser's only auth artefact is the httpOnly cookie.\n */\nexport class BffAuthClient {\n private readonly http: HttpClient;\n private readonly baseUrl: string;\n\n constructor(options: BffAuthClientOptions) {\n this.http = options.http;\n this.baseUrl = (options.baseUrl ?? '').replace(/\\/$/, '');\n }\n\n /**\n * `POST /bff/login` — the BFF does ROPC against Keycloak server-side, stores\n * the tokens in its Redis vault, and sets the httpOnly session cookie.\n * Returns the sanitised user. Throws on a non-2xx response.\n */\n async login(request: BffLoginRequest): Promise<BffUser> {\n const data = await this.postState(ENDPOINTS.login, request, 'login');\n const user = extractUser(data);\n if (user === null) {\n throw new Error('login: BFF response missing user');\n }\n return user;\n }\n\n /**\n * `POST /bff/logout` — the BFF calls KC end-session, deletes the Redis\n * session, and clears the cookie. Non-fatal: a failed logout still leaves\n * the SPA logged out client-side. Throws only on a non-2xx response.\n */\n async logout(): Promise<void> {\n await this.postState(ENDPOINTS.logout, undefined, 'logout');\n }\n\n /**\n * `GET /bff/me` — the live session's sanitised user, or `null` when there is\n * no session (the BFF answers `401`). Used at app load to bootstrap auth\n * state in place of the old token-in-storage check.\n */\n async getCurrentUser(): Promise<BffUser | null> {\n const response = await this.http({\n url: `${this.baseUrl}${ENDPOINTS.me}`,\n method: 'GET',\n headers: { Accept: JSON_CONTENT_TYPE },\n credentials: 'include',\n });\n if (!response.ok) {\n return null;\n }\n return extractUser(response.data);\n }\n\n /**\n * `POST /bff/register` — the BFF proxies registration to TenantService and,\n * on success, establishes a session exactly like `login`. Returns the user.\n */\n async register(request: BffRegisterRequest): Promise<BffUser> {\n const data = await this.postState(ENDPOINTS.register, request, 'register');\n const user = extractUser(data);\n if (user === null) {\n throw new Error('register: BFF response missing user');\n }\n return user;\n }\n\n /**\n * `POST /bff/forgot-password` — proxied to TenantService. The backend\n * returns 200 unconditionally (no email enumeration); anything else throws.\n */\n async forgotPassword(request: BffForgotPasswordRequest): Promise<void> {\n await this.postState(ENDPOINTS.forgotPassword, request, 'forgot-password');\n }\n\n /**\n * `POST /bff/reset-password` — proxied to TenantService. Throws on a non-2xx\n * response (e.g. `400` for an invalid / expired token).\n */\n async resetPassword(request: BffResetPasswordRequest): Promise<void> {\n await this.postState(ENDPOINTS.resetPassword, request, 'reset-password');\n }\n\n /**\n * Shared POST for every state-changing `/bff/*` call: same-origin, cookie\n * included, `X-BFF-Csrf` header attached. Throws a labelled error on non-2xx.\n */\n private async postState(path: string, body: object | undefined, label: string): Promise<unknown> {\n const headers: Record<string, string> = {\n 'Content-Type': JSON_CONTENT_TYPE,\n Accept: JSON_CONTENT_TYPE,\n [CSRF_HEADER]: CSRF_HEADER_VALUE,\n };\n const response = await this.http({\n url: `${this.baseUrl}${path}`,\n method: 'POST',\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n credentials: 'include',\n });\n if (!response.ok) {\n throw new Error(`${label} failed with status ${String(response.status)}`);\n }\n return response.data;\n }\n}\n","import { KeycloakRoles } from '../types/KeycloakRoles';\n\nimport type { KeycloakUserInfo } from '../types/KeycloakUserInfo';\nimport type { NormalizedUser } from '../types/NormalizedUser';\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value !== '';\n}\n\nfunction firstNonEmptyString(...values: unknown[]): string | undefined {\n for (const v of values) {\n if (isNonEmptyString(v)) {\n return v;\n }\n }\n return undefined;\n}\n\nfunction collectRoles(u: KeycloakUserInfo): KeycloakRoles[] {\n const roles: KeycloakRoles[] = [];\n const realmAccessRoles = u.realm_access?.roles;\n if (Array.isArray(realmAccessRoles)) {\n roles.push(...realmAccessRoles);\n }\n\n const resourceAccess = u.resource_access;\n if (resourceAccess !== undefined) {\n for (const v of Object.values(resourceAccess)) {\n const resourceRoles = v.roles;\n if (Array.isArray(resourceRoles)) {\n roles.push(...resourceRoles);\n }\n }\n }\n return roles;\n}\n\n/**\n * Convert a Keycloak `/userinfo` payload into a flat, app-friendly user object.\n *\n * - Aggregates `realm_access.roles` and every `resource_access[*].roles` into a\n * deduplicated `roles` array.\n * - Picks a sensible `displayName` / `username` from whatever claims are\n * present (Keycloak realms vary in which fields they emit).\n * - Returns a safe default (`{ roles: [] }`) when input is undefined.\n */\nexport function normalizeKeycloakUser(u?: KeycloakUserInfo): NormalizedUser {\n if (!u) {\n return { roles: [] };\n }\n const roles = collectRoles(u);\n const fullName = [u.given_name, u.family_name].filter(isNonEmptyString).join(' ');\n const username = firstNonEmptyString(u.preferred_username, u.name, u.email, u.sub);\n const displayName = firstNonEmptyString(u.name, fullName, u.preferred_username, u.email, u.sub);\n\n return {\n id: u.sub,\n username,\n email: u.email,\n displayName,\n firstName: u.given_name,\n lastName: u.family_name,\n emailVerified: Boolean(u.email_verified),\n roles: Array.from(new Set(roles)),\n raw: u,\n };\n}\n","/**\n * Loose shape of an `expo-auth-session` (or browser-side) authorization response.\n *\n * Kept as a structural type rather than importing from `expo-auth-session` so\n * the package stays usable in plain web apps and Node tests.\n */\nexport interface AuthorizationResponseLike {\n type?: string;\n params?: { code?: string; error?: string };\n}\n\n/**\n * Pull the authorization `code` out of a successful redirect response.\n *\n * Returns `undefined` when the response is missing, indicates an error type,\n * or doesn't carry a non-empty `code` query param.\n */\nexport function extractAuthCode(\n response: AuthorizationResponseLike | null | undefined,\n): string | undefined {\n if (!response) {\n return undefined;\n }\n if (response.type !== 'success') {\n return undefined;\n }\n const code = response.params?.code;\n if (typeof code !== 'string' || code === '') {\n return undefined;\n }\n return code;\n}\n","/**\n * Decode the payload segment of a compact JWT.\n *\n * No signature verification — that responsibility belongs to the backend that\n * accepts the token. This helper is for UI concerns: reading `exp` to schedule\n * refresh, reading custom claims for routing decisions, etc.\n *\n * Returns `null` when the input is malformed, base64url-decodes incorrectly, or\n * does not produce a JSON object payload.\n *\n * Runtime requirement: a global `atob` function. Available in browsers, in\n * Node ≥ 16, and in modern bundler test envs (jsdom, node-jest).\n */\nexport function decodeJwt<T = Record<string, unknown>>(token: string | null | undefined): T | null {\n if (typeof token !== 'string' || token === '') {\n return null;\n }\n const parts = token.split('.');\n if (parts.length !== 3) {\n return null;\n }\n const payload = parts[1];\n if (payload === undefined || payload === '') {\n return null;\n }\n try {\n const json = base64UrlDecode(payload);\n const parsed: unknown = JSON.parse(json);\n if (typeof parsed !== 'object' || parsed === null) {\n return null;\n }\n return parsed as T;\n } catch {\n return null;\n }\n}\n\nconst BASE64_PAD_LENGTH = 4;\n\nfunction base64UrlDecode(input: string): string {\n const normalized = input.replace(/-/g, '+').replace(/_/g, '/');\n const padLength =\n (BASE64_PAD_LENGTH - (normalized.length % BASE64_PAD_LENGTH)) % BASE64_PAD_LENGTH;\n const padded = normalized + '='.repeat(padLength);\n if (typeof globalThis.atob !== 'function') {\n throw new Error('decodeJwt: globalThis.atob is unavailable in this runtime');\n }\n return decodeUtf8(globalThis.atob(padded));\n}\n\nfunction decodeUtf8(binary: string): string {\n // `atob` returns a \"binary string\" where each char code is one byte. Convert\n // to UTF-8 properly using TextDecoder when available (browsers + Node).\n if (typeof TextDecoder === 'undefined') {\n return binary;\n }\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return new TextDecoder('utf-8').decode(bytes);\n}\n"]}
1
+ {"version":3,"sources":["../src/utils/buildKeycloakUrls.ts","../src/utils/isTokenExpired.ts","../src/utils/parseRealmFromIssuer.ts","../src/utils/normalizeTokenResponse.ts","../src/events/AuthEventEmitter.ts","../src/AuthClient.ts","../src/oidc/discovery.ts","../src/oidc/pkce.ts","../src/utils/buildTokenRequestBody.ts","../src/oidc/tokenExchange.ts","../src/types/KeycloakRoles.ts","../src/storage/BrowserStorageTokenStorage.ts","../src/storage/InMemoryTokenStorage.ts","../src/storage/CookieTokenStorage.ts","../src/storage/SecureStoreTokenStorage.ts","../src/biometric/BiometricGate.ts","../src/interceptor/RefreshInterceptor.ts","../src/inactivity/InactivityTracker.ts","../src/http/HttpClient.ts","../src/api/AuthApiClient.ts","../src/bff/BffAuthClient.ts","../src/utils/normalizeKeycloakUser.ts","../src/utils/extractAuthCode.ts","../src/utils/decodeJwt.ts"],"names":["KeycloakRoles"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * URL builders for the realm-aware Keycloak surface area.\n *\n * Every helper takes `baseUrl` and `realm` explicitly — no hardcoded realm\n * names. This is the contract that Phase 2 of the product split relies on:\n * the same package serves the future Questioner-realm app and OnlineMenu-realm\n * app without code change.\n */\n\nconst REALM_PATH_PREFIX = '/realms';\nconst PROTOCOL_PATH = '/protocol/openid-connect';\n\nfunction trimTrailingSlash(value: string): string {\n return value.replace(/\\/$/, '');\n}\n\n/**\n * Compute the issuer URL: `{baseUrl}/realms/{realm}`.\n */\nexport function buildIssuerUrl(baseUrl: string, realm: string): string {\n return `${trimTrailingSlash(baseUrl)}${REALM_PATH_PREFIX}/${encodeURIComponent(realm)}`;\n}\n\n/**\n * Compute the authorization endpoint URL.\n */\nexport function buildAuthorizationEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/auth`;\n}\n\n/**\n * Compute the token endpoint URL.\n */\nexport function buildTokenEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/token`;\n}\n\n/**\n * Compute the userinfo endpoint URL.\n */\nexport function buildUserInfoEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/userinfo`;\n}\n\n/**\n * Compute the logout endpoint URL.\n */\nexport function buildLogoutEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/logout`;\n}\n\nexport interface AuthorizationUrlInput {\n baseUrl: string;\n realm: string;\n clientId: string;\n redirectUri: string;\n scope?: string;\n state?: string;\n codeChallenge?: string;\n codeChallengeMethod?: 'S256' | 'plain';\n}\n\n/**\n * Build a complete authorization URL the user agent can navigate to.\n *\n * All PKCE-related fields are optional so this helper also serves\n * non-PKCE flows (e.g. confidential server-side clients) — but PKCE is\n * the recommended path for SPA / native consumers.\n */\nexport function buildAuthorizationUrl(input: AuthorizationUrlInput): string {\n const params = new URLSearchParams({\n client_id: input.clientId,\n redirect_uri: input.redirectUri,\n response_type: 'code',\n });\n if (typeof input.scope === 'string' && input.scope !== '') {\n params.set('scope', input.scope);\n }\n if (typeof input.state === 'string' && input.state !== '') {\n params.set('state', input.state);\n }\n if (typeof input.codeChallenge === 'string' && input.codeChallenge !== '') {\n params.set('code_challenge', input.codeChallenge);\n params.set('code_challenge_method', input.codeChallengeMethod ?? 'S256');\n }\n return `${buildAuthorizationEndpoint(input.baseUrl, input.realm)}?${params.toString()}`;\n}\n","import type { AuthTokens } from '../types/AuthTokens';\n\nconst SECONDS_TO_MILLIS = 1000;\nconst DEFAULT_LEEWAY_MS = 30 * SECONDS_TO_MILLIS;\n\n/**\n * Determine whether a token bundle is expired.\n *\n * `expiresAt` is interpreted as an absolute UNIX millisecond timestamp.\n *\n * `leewayMs` (default 30 s) shaves a small window off the expiry to compensate\n * for clock skew and round-trip latency: a token that expires at exactly\n * `Date.now()` is essentially useless because by the time the request arrives\n * at the API it will have expired.\n */\nexport function isTokenExpired(\n tokens: Pick<AuthTokens, 'expiresAt'> | null | undefined,\n leewayMs: number = DEFAULT_LEEWAY_MS,\n now: number = Date.now(),\n): boolean {\n if (!tokens) {\n return true;\n }\n if (typeof tokens.expiresAt !== 'number' || tokens.expiresAt <= 0) {\n return true;\n }\n return tokens.expiresAt - leewayMs <= now;\n}\n\n/**\n * Compute the absolute expiry timestamp from a token endpoint `expires_in` value.\n *\n * Returns `0` when `expiresIn` is missing or non-positive — signalling \"unknown\n * expiry, treat as expired\" downstream.\n */\nexport function computeExpiresAt(\n expiresInSeconds: number | undefined,\n now: number = Date.now(),\n): number {\n if (typeof expiresInSeconds !== 'number' || expiresInSeconds <= 0) {\n return 0;\n }\n return now + expiresInSeconds * SECONDS_TO_MILLIS;\n}\n","/**\n * Extract the realm name from a Keycloak issuer URL.\n *\n * Keycloak issuer URLs follow the shape `{baseUrl}/realms/{realm}` (with optional\n * `/protocol/openid-connect` suffix on token endpoints). This helper reverses\n * that convention so existing apps that store only the issuer URL can derive\n * `realm` for the realm-aware {@link AuthClient} constructor.\n *\n * @returns the realm name, or `null` if the URL doesn't match the convention.\n */\nexport function parseRealmFromIssuer(issuerUrl: string | null | undefined): string | null {\n if (typeof issuerUrl !== 'string' || issuerUrl === '') {\n return null;\n }\n const match = /\\/realms\\/([^/?#]+)/i.exec(issuerUrl);\n if (!match || match[1] === undefined || match[1] === '') {\n return null;\n }\n return decodeURIComponent(match[1]);\n}\n\n/**\n * Extract the base URL (scheme + host + optional path prefix) from a\n * Keycloak issuer URL by stripping the `/realms/{realm}...` suffix.\n *\n * Returns the original input unchanged when no `/realms/` segment is found —\n * callers that need strict validation should pair this with `parseRealmFromIssuer`.\n */\nexport function parseBaseUrlFromIssuer(issuerUrl: string | null | undefined): string | null {\n if (typeof issuerUrl !== 'string' || issuerUrl === '') {\n return null;\n }\n const idx = issuerUrl.search(/\\/realms\\//i);\n if (idx === -1) {\n return issuerUrl.replace(/\\/$/, '');\n }\n return issuerUrl.substring(0, idx).replace(/\\/$/, '');\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { RawTokenResponse, TokenResponse } from '../types/TokenResponse';\nimport { computeExpiresAt } from './isTokenExpired';\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/**\n * Map a raw OIDC token endpoint response (snake_case) to camelCase.\n *\n * Throws when `access_token` is missing or empty — callers should let this\n * propagate to the auth state machine, which treats it as a login failure.\n */\nexport function normalizeTokenResponse(raw: RawTokenResponse): TokenResponse {\n const accessToken = asString(raw.access_token);\n if (accessToken === undefined) {\n throw new Error('Token response missing access_token');\n }\n return {\n accessToken,\n refreshToken: asString(raw.refresh_token),\n idToken: asString(raw.id_token),\n expiresIn: asNumber(raw.expires_in),\n tokenType: asString(raw.token_type),\n scope: asString(raw.scope),\n };\n}\n\n/**\n * Convert a normalized {@link TokenResponse} into a persistable\n * {@link AuthTokens} bundle by computing `expiresAt` from `expiresIn`.\n */\nexport function tokenResponseToAuthTokens(\n response: TokenResponse,\n now: number = Date.now(),\n): AuthTokens {\n return {\n accessToken: response.accessToken,\n refreshToken: response.refreshToken,\n idToken: response.idToken,\n expiresAt: computeExpiresAt(response.expiresIn, now),\n };\n}\n","/**\n * Tiny dependency-free event emitter for auth lifecycle events.\n *\n * Consumers subscribe to `onSessionExpired` to navigate to the login screen\n * when refresh fails or the inactivity timeout fires. We don't reach for\n * `EventTarget`/`EventEmitter` because we want one consistent API across web,\n * React Native, and node test environments without polyfills.\n */\nexport type AuthEventName = 'sessionExpired';\n\nexport type AuthEventListener = () => void;\n\nexport interface AuthEventUnsubscribe {\n (): void;\n}\n\nexport class AuthEventEmitter {\n private readonly listeners: Map<AuthEventName, Set<AuthEventListener>> = new Map();\n\n on(event: AuthEventName, listener: AuthEventListener): AuthEventUnsubscribe {\n let bucket = this.listeners.get(event);\n if (bucket === undefined) {\n bucket = new Set();\n this.listeners.set(event, bucket);\n }\n bucket.add(listener);\n return (): void => {\n const current = this.listeners.get(event);\n if (current !== undefined) {\n current.delete(listener);\n }\n };\n }\n\n emit(event: AuthEventName): void {\n const bucket = this.listeners.get(event);\n if (bucket === undefined) {\n return;\n }\n // Snapshot so listeners can unsubscribe during dispatch without skipping siblings.\n const snapshot = Array.from(bucket);\n for (const listener of snapshot) {\n listener();\n }\n }\n\n /** Remove all listeners. Useful for `AuthClient.dispose()` and tests. */\n clear(): void {\n this.listeners.clear();\n }\n}\n","import {\n buildAuthorizationEndpoint,\n buildAuthorizationUrl,\n buildIssuerUrl,\n buildLogoutEndpoint,\n buildTokenEndpoint,\n buildUserInfoEndpoint,\n} from './utils/buildKeycloakUrls';\nimport { isTokenExpired } from './utils/isTokenExpired';\nimport { parseBaseUrlFromIssuer, parseRealmFromIssuer } from './utils/parseRealmFromIssuer';\nimport { normalizeTokenResponse, tokenResponseToAuthTokens } from './utils/normalizeTokenResponse';\nimport { AuthEventEmitter, type AuthEventListener, type AuthEventName, type AuthEventUnsubscribe } from './events/AuthEventEmitter';\n\nimport type { AuthApiClient, RawAuthLoginResponse } from './api/AuthApiClient';\nimport type { AuthClientConfig } from './types/AuthClientConfig';\nimport type { AuthTokens } from './types/AuthTokens';\nimport type { TokenStorage } from './types/TokenStorage';\nimport type { InactivityTracker } from './inactivity/InactivityTracker';\nimport type { RefreshInterceptor } from './interceptor/RefreshInterceptor';\nimport type { TokenResponse } from './types/TokenResponse';\n\nconst DEFAULT_SCOPE = 'openid profile email';\nconst OFFLINE_ACCESS_SCOPE = 'offline_access';\n\n/**\n * Inputs to {@link AuthClient.fromIssuerUrl}.\n *\n * Used by consumers that store only an issuer URL and want to derive `realm`\n * + `baseUrl` rather than configure them separately.\n */\nexport interface AuthClientFromIssuerInput {\n issuerUrl: string;\n clientId: string;\n redirectUri?: string;\n scope?: string;\n}\n\n/**\n * Optional collaborators wired into {@link AuthClient} for the v2 surface.\n *\n * - `api` enables `loginWith*`, `logout`, `requestPasswordReset`,\n * `confirmPasswordReset`. Without it, those methods throw.\n * - `interceptor` enables `init()` to silently refresh tokens at boot, and is\n * used by `loginWithOtp/Password` to mark inactivity-active.\n * - `inactivityTracker` enforces the 90-day timeout at `init()`.\n *\n * Consumers can omit any/all of these — the v1 PKCE / token-storage surface\n * keeps working unchanged.\n */\nexport interface AuthClientCollaborators {\n api?: AuthApiClient;\n interceptor?: RefreshInterceptor;\n inactivityTracker?: InactivityTracker;\n events?: AuthEventEmitter;\n /**\n * Observability hook fired when a fresh token bundle has been acquired\n * (any login path: OTP, password, or direct-KC PKCE). For app-side\n * analytics/logging only — NOT for BFF integration (Phase 2 designs that\n * fresh).\n */\n onTokenAcquired?: (tokens: AuthTokens) => void;\n /**\n * Observability hook fired when an existing token bundle has been\n * refreshed. For app-side analytics/logging only.\n */\n onTokenRefreshed?: (tokens: AuthTokens) => void;\n}\n\n/**\n * Direct-to-KC (PKCE) routing flag added in v2.1.0.\n *\n * When `true`, `AuthClient` consumers can route their PKCE auth code through\n * the shared OIDC primitives (`exchangeAuthorizationCodeViaOidc`,\n * `refreshTokensViaOidc`) instead of the proxied identity-api `/auth/login`\n * + `/auth/refresh` flow.\n *\n * Default `false` — v2.0 behavior unchanged.\n *\n * The flag is read-only at runtime (`isDirectMode()`) so apps can render\n * conditionally on whether they've opted in.\n */\nexport interface DirectKcOptions {\n useDirectKcAuth?: boolean;\n}\n\nexport interface LoginOptions {\n /** When true, request `offline_access` scope so the IdP issues a long-lived refresh token. */\n offlineAccess?: boolean;\n}\n\nexport interface LogoutOptions {\n /** Revoke all sessions on the IdP, not just the current one. */\n everywhere?: boolean;\n}\n\nexport class AuthClient {\n private readonly config: AuthClientConfig;\n private readonly directKcAuth: boolean;\n private readonly tokenStorage: TokenStorage;\n private readonly api: AuthApiClient | undefined;\n private readonly interceptor: RefreshInterceptor | undefined;\n private readonly inactivityTracker: InactivityTracker | undefined;\n private readonly events: AuthEventEmitter;\n private readonly onTokenAcquired: ((tokens: AuthTokens) => void) | undefined;\n private readonly onTokenRefreshed: ((tokens: AuthTokens) => void) | undefined;\n\n /**\n * @throws Error when `baseUrl`, `realm`, or `clientId` is missing or empty.\n */\n constructor(\n config: AuthClientConfig & DirectKcOptions,\n storage: TokenStorage,\n collaborators: AuthClientCollaborators = {},\n ) {\n AuthClient.validateConfig(config);\n this.config = {\n ...config,\n scope: config.scope ?? DEFAULT_SCOPE,\n };\n this.directKcAuth = config.useDirectKcAuth === true;\n this.tokenStorage = storage;\n this.api = collaborators.api;\n this.interceptor = collaborators.interceptor;\n this.inactivityTracker = collaborators.inactivityTracker;\n this.events = collaborators.events ?? new AuthEventEmitter();\n this.onTokenAcquired = collaborators.onTokenAcquired;\n this.onTokenRefreshed = collaborators.onTokenRefreshed;\n }\n\n /**\n * Whether this client is configured to route auth flows directly to\n * Keycloak (v2.1.0 direct-KC path) instead of through the proxied\n * identity-api `/auth/*` endpoints.\n *\n * Apps can render conditionally on this — e.g. to swap a login form for\n * a \"Sign in with Keycloak\" redirect button.\n */\n isDirectMode(): boolean {\n return this.directKcAuth;\n }\n\n /**\n * Persist a token bundle produced by an external flow (e.g. the\n * app-side `useKeycloakExchange` hook that consumes the shared\n * `exchangeAuthorizationCode` primitive). Fires `onTokenAcquired` after\n * persistence and marks the inactivity tracker active.\n *\n * Designed for the v2.1.0 direct-KC path where the PKCE code exchange\n * happens in the app's React-Query hook (which needs `useDispatch`/etc.)\n * but the token persistence + observability should still flow through\n * the shared client.\n */\n async acceptDirectKcTokens(response: TokenResponse): Promise<AuthTokens> {\n const tokens = tokenResponseToAuthTokens(response);\n await this.tokenStorage.write(tokens);\n if (this.inactivityTracker !== undefined) {\n await this.inactivityTracker.markActive();\n }\n if (this.onTokenAcquired !== undefined) {\n this.onTokenAcquired(tokens);\n }\n return tokens;\n }\n\n /**\n * Same as {@link acceptDirectKcTokens} but fires `onTokenRefreshed`.\n * Use after a `refreshAccessToken()` swap to keep observability counts\n * separated between \"fresh login\" and \"silent refresh\".\n */\n async acceptDirectKcRefresh(response: TokenResponse): Promise<AuthTokens> {\n const tokens = tokenResponseToAuthTokens(response);\n await this.tokenStorage.write(tokens);\n if (this.inactivityTracker !== undefined) {\n await this.inactivityTracker.markActive();\n }\n if (this.onTokenRefreshed !== undefined) {\n this.onTokenRefreshed(tokens);\n }\n return tokens;\n }\n\n /**\n * Build an {@link AuthClient} from a standalone issuer URL by parsing the\n * realm and base URL. Useful when migrating from the legacy\n * `KEYCLOAK_ISSUER` env var convention.\n *\n * @throws Error when the issuer URL doesn't match `{base}/realms/{realm}`.\n */\n static fromIssuerUrl(\n input: AuthClientFromIssuerInput,\n storage: TokenStorage,\n collaborators: AuthClientCollaborators = {},\n ): AuthClient {\n const realm = parseRealmFromIssuer(input.issuerUrl);\n const baseUrl = parseBaseUrlFromIssuer(input.issuerUrl);\n if (realm === null || baseUrl === null || baseUrl === '') {\n throw new Error(`AuthClient.fromIssuerUrl: cannot parse realm from \"${input.issuerUrl}\"`);\n }\n return new AuthClient(\n {\n baseUrl,\n realm,\n clientId: input.clientId,\n redirectUri: input.redirectUri,\n scope: input.scope,\n },\n storage,\n collaborators,\n );\n }\n\n private static validateConfig(config: AuthClientConfig): void {\n if (typeof config.baseUrl !== 'string' || config.baseUrl === '') {\n throw new Error('AuthClient: baseUrl is required');\n }\n if (typeof config.realm !== 'string' || config.realm === '') {\n throw new Error('AuthClient: realm is required');\n }\n if (typeof config.clientId !== 'string' || config.clientId === '') {\n throw new Error('AuthClient: clientId is required');\n }\n }\n\n get realm(): string {\n return this.config.realm;\n }\n\n get clientId(): string {\n return this.config.clientId;\n }\n\n get baseUrl(): string {\n return this.config.baseUrl.replace(/\\/$/, '');\n }\n\n get scope(): string {\n // Constructor always materialises a scope (either user-supplied or DEFAULT_SCOPE).\n return this.config.scope as string;\n }\n\n get redirectUri(): string | undefined {\n return this.config.redirectUri;\n }\n\n /** Issuer URL: `{baseUrl}/realms/{realm}`. */\n get issuerUrl(): string {\n return buildIssuerUrl(this.baseUrl, this.realm);\n }\n\n get authorizationEndpoint(): string {\n return buildAuthorizationEndpoint(this.baseUrl, this.realm);\n }\n\n get tokenEndpoint(): string {\n return buildTokenEndpoint(this.baseUrl, this.realm);\n }\n\n get userInfoEndpoint(): string {\n return buildUserInfoEndpoint(this.baseUrl, this.realm);\n }\n\n get logoutEndpoint(): string {\n return buildLogoutEndpoint(this.baseUrl, this.realm);\n }\n\n /**\n * Build a fully-formed authorization URL the user agent can navigate to.\n *\n * @throws Error when `redirectUri` is not configured.\n */\n buildAuthorizationUrl(input: {\n state?: string;\n codeChallenge?: string;\n codeChallengeMethod?: 'S256' | 'plain';\n offlineAccess?: boolean;\n } = {}): string {\n if (typeof this.config.redirectUri !== 'string' || this.config.redirectUri === '') {\n throw new Error('AuthClient.buildAuthorizationUrl: redirectUri is required');\n }\n return buildAuthorizationUrl({\n baseUrl: this.baseUrl,\n realm: this.realm,\n clientId: this.clientId,\n redirectUri: this.config.redirectUri,\n scope: this.resolveScope(input.offlineAccess),\n state: input.state,\n codeChallenge: input.codeChallenge,\n codeChallengeMethod: input.codeChallengeMethod,\n });\n }\n\n async getTokens(): Promise<AuthTokens | null> {\n return this.tokenStorage.read();\n }\n\n async setTokens(tokens: AuthTokens): Promise<void> {\n return this.tokenStorage.write(tokens);\n }\n\n async clearTokens(): Promise<void> {\n return this.tokenStorage.clear();\n }\n\n /**\n * Read the current access token if it exists and is not expired.\n * Returns `null` for \"no usable token\".\n */\n async getAccessToken(now: number = Date.now()): Promise<string | null> {\n const tokens = await this.tokenStorage.read();\n if (tokens === null) {\n return null;\n }\n if (isTokenExpired(tokens, undefined, now)) {\n return null;\n }\n return tokens.accessToken;\n }\n\n /** Subscribe to lifecycle events (currently `sessionExpired` only). */\n on(event: AuthEventName, listener: AuthEventListener): AuthEventUnsubscribe {\n return this.events.on(event, listener);\n }\n\n /**\n * Boot-time wiring. Checks the inactivity tracker; if expired, clears\n * tokens and emits `sessionExpired`. Returns whether a usable session\n * survived.\n */\n async init(): Promise<{ hasSession: boolean }> {\n if (this.inactivityTracker !== undefined) {\n const expired = await this.inactivityTracker.isExpired();\n if (expired) {\n await this.tokenStorage.clear();\n await this.inactivityTracker.clear();\n this.events.emit('sessionExpired');\n return { hasSession: false };\n }\n }\n const tokens = await this.tokenStorage.read();\n return { hasSession: tokens !== null };\n }\n\n /**\n * Trigger a refresh via the configured interceptor. Returns the new tokens\n * or `null` when the refresh failed (in which case `sessionExpired` has\n * already fired).\n *\n * @throws Error when no interceptor is configured.\n */\n async refresh(): Promise<AuthTokens | null> {\n if (this.interceptor === undefined) {\n throw new Error('AuthClient.refresh: no RefreshInterceptor configured');\n }\n const tokens = await this.interceptor.refreshTokens();\n if (tokens !== null && this.onTokenRefreshed !== undefined) {\n this.onTokenRefreshed(tokens);\n }\n return tokens;\n }\n\n async loginWithOtp(input: { email: string; otp: string; tenantId?: string } & LoginOptions): Promise<AuthTokens> {\n return this.runLogin(this.requireApi().loginWithOtp({\n email: input.email,\n otp: input.otp,\n tenantId: input.tenantId,\n offlineAccess: input.offlineAccess ?? false,\n }));\n }\n\n async loginWithPassword(\n input: { email: string; password: string; tenantId?: string } & LoginOptions,\n ): Promise<AuthTokens> {\n return this.runLogin(this.requireApi().loginWithPassword({\n email: input.email,\n password: input.password,\n tenantId: input.tenantId,\n offlineAccess: input.offlineAccess ?? false,\n }));\n }\n\n async logout(options: LogoutOptions = {}): Promise<void> {\n const api = this.requireApi();\n try {\n await api.logout(options.everywhere ?? false);\n } finally {\n await this.tokenStorage.clear();\n if (this.inactivityTracker !== undefined) {\n await this.inactivityTracker.clear();\n }\n }\n }\n\n async requestPasswordReset(input: { email: string; tenantId?: string }): Promise<void> {\n return this.requireApi().forgotPassword({ email: input.email, tenantId: input.tenantId });\n }\n\n async confirmPasswordReset(input: { token: string; newPassword: string }): Promise<void> {\n return this.requireApi().resetPassword({ token: input.token, newPassword: input.newPassword });\n }\n\n /** Internal: run a login HTTP call, persist tokens, mark inactivity-active. */\n private async runLogin(promise: Promise<RawAuthLoginResponse>): Promise<AuthTokens> {\n const raw = await promise;\n if (typeof raw.access_token !== 'string' || raw.access_token === '') {\n throw new Error('AuthClient: login response missing access_token');\n }\n // After the guard above, raw.access_token is `string`; widen the optional-shaped\n // RawAuthLoginResponse into the strict RawTokenResponse the normaliser expects.\n const normalized = normalizeTokenResponse({ ...raw, access_token: raw.access_token });\n const tokens = tokenResponseToAuthTokens(normalized);\n await this.tokenStorage.write(tokens);\n if (this.inactivityTracker !== undefined) {\n await this.inactivityTracker.markActive();\n }\n if (this.onTokenAcquired !== undefined) {\n this.onTokenAcquired(tokens);\n }\n return tokens;\n }\n\n private requireApi(): AuthApiClient {\n if (this.api === undefined) {\n throw new Error('AuthClient: no AuthApiClient configured');\n }\n return this.api;\n }\n\n private resolveScope(offlineAccess?: boolean): string {\n if (offlineAccess !== true) {\n return this.scope;\n }\n if (this.scope.includes(OFFLINE_ACCESS_SCOPE)) {\n return this.scope;\n }\n return `${this.scope} ${OFFLINE_ACCESS_SCOPE}`.trim();\n }\n}\n","/**\n * OIDC discovery document fetcher.\n *\n * Fetches `{issuer}/.well-known/openid-configuration` and caches the result\n * per-issuer for the lifetime of the process. Discovery responses are stable\n * for hours; the cache prevents the auth flow from hitting KC on every login.\n *\n * Pure (no React, no hooks). Consumed by app-side hooks that orchestrate the\n * PKCE flow.\n */\n\nimport type { HttpClient } from '../http/HttpClient';\n\n/**\n * Subset of the OIDC Discovery 1.0 metadata the auth-client needs.\n *\n * The KC discovery doc carries many more fields; we type only what the PKCE\n * flow consumes to keep the surface small and to fail loudly when KC ever\n * stops returning one of these.\n */\nexport interface OidcDiscoveryDocument {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n end_session_endpoint?: string;\n userinfo_endpoint?: string;\n jwks_uri?: string;\n}\n\nexport interface FetchDiscoveryDocumentInput {\n /** Issuer URL — `{baseUrl}/realms/{realm}`. Trailing slash tolerated. */\n issuerUrl: string;\n /** Transport. Pass `createFetchHttpClient(fetch)` in browser/Node18+. */\n http: HttpClient;\n}\n\nconst cache = new Map<string, OidcDiscoveryDocument>();\n\nfunction normalizeIssuer(issuerUrl: string): string {\n return issuerUrl.replace(/\\/$/, '');\n}\n\nfunction isOidcDiscoveryDocument(data: unknown): data is OidcDiscoveryDocument {\n if (data === null || typeof data !== 'object') {\n return false;\n }\n const d = data as Record<string, unknown>;\n return (\n typeof d.issuer === 'string'\n && d.issuer !== ''\n && typeof d.authorization_endpoint === 'string'\n && d.authorization_endpoint !== ''\n && typeof d.token_endpoint === 'string'\n && d.token_endpoint !== ''\n );\n}\n\n/**\n * Fetch + cache the OIDC discovery document for an issuer.\n *\n * Cache key = normalized issuer URL (trailing slash stripped).\n *\n * @throws Error when the HTTP call fails, returns non-2xx, or returns a body\n * missing required OIDC metadata fields.\n */\nexport async function fetchDiscoveryDocument(\n input: FetchDiscoveryDocumentInput,\n): Promise<OidcDiscoveryDocument> {\n const key = normalizeIssuer(input.issuerUrl);\n const cached = cache.get(key);\n if (cached !== undefined) {\n return cached;\n }\n const response = await input.http({\n url: `${key}/.well-known/openid-configuration`,\n method: 'GET',\n });\n if (!response.ok) {\n throw new Error(\n `OIDC discovery failed: ${String(response.status)} for ${key}`,\n );\n }\n if (!isOidcDiscoveryDocument(response.data)) {\n throw new Error(`OIDC discovery returned invalid metadata for ${key}`);\n }\n cache.set(key, response.data);\n return response.data;\n}\n\n/**\n * Clear the per-issuer discovery cache. Test-only — production code does not\n * call this. Useful when a test mocks different metadata across cases.\n */\nexport function clearDiscoveryCache(): void {\n cache.clear();\n}\n","/**\n * PKCE (RFC 7636) primitives for the OIDC authorization-code flow.\n *\n * Pure (no React). Browser-compatible — uses `crypto.subtle` for SHA-256 and\n * `crypto.getRandomValues` for the verifier. Node 16+ exposes both via\n * `globalThis.crypto`.\n */\n\n/** RFC 7636 §4.1: code_verifier MUST be 43..128 chars from the unreserved set. */\nconst VERIFIER_MIN_LENGTH = 43;\nconst VERIFIER_MAX_LENGTH = 128;\nconst DEFAULT_VERIFIER_LENGTH = 64;\nconst RANDOM_BYTES_PER_CHAR = 1;\n\nconst UNRESERVED_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n\nfunction getCrypto(): Crypto {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n // Runtime check: in some Node test environments `crypto.subtle` may not\n // exist even though the TS lib types mark it as non-optional.\n // eslint-disable-next-line sonarjs/different-types-comparison, @typescript-eslint/no-unnecessary-condition\n if (c === undefined || c.subtle === undefined) {\n throw new Error('pkce: globalThis.crypto.subtle is required (Node 16+ / modern browser)');\n }\n return c;\n}\n\nfunction assertVerifierLength(length: number): void {\n if (length < VERIFIER_MIN_LENGTH || length > VERIFIER_MAX_LENGTH) {\n throw new Error(`pkce: code_verifier length must be ${String(VERIFIER_MIN_LENGTH)}-${String(VERIFIER_MAX_LENGTH)} chars (RFC 7636)`);\n }\n}\n\n/**\n * Base64-URL encode an ArrayBuffer (no padding, `-` and `_` substitutions).\n *\n * Required for the S256 challenge — RFC 7636 §4.2.\n */\nfunction base64UrlEncode(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i] as number);\n }\n const b64 = (globalThis as { btoa?: (s: string) => string }).btoa?.(binary)\n ?? Buffer.from(binary, 'binary').toString('base64');\n // Strip trailing '=' padding by slicing — avoids the sonarjs/slow-regex\n // warning on /=+$/ even though base64 padding is bounded to 0..2 chars.\n let end = b64.length;\n while (end > 0 && b64.charCodeAt(end - 1) === '='.charCodeAt(0)) {\n end -= 1;\n }\n return b64.slice(0, end).replace(/\\+/g, '-').replace(/\\//g, '_');\n}\n\n/**\n * Generate a cryptographically random PKCE code_verifier.\n *\n * Default length 64 sits well inside the RFC 7636 43..128 band.\n *\n * @throws Error when `length` falls outside the RFC band.\n */\nexport function generateCodeVerifier(length: number = DEFAULT_VERIFIER_LENGTH): string {\n assertVerifierLength(length);\n const crypto = getCrypto();\n const bytes = new Uint8Array(length * RANDOM_BYTES_PER_CHAR);\n crypto.getRandomValues(bytes);\n let out = '';\n for (let i = 0; i < length; i++) {\n const byte = bytes[i] as number;\n out += UNRESERVED_CHARS[byte % UNRESERVED_CHARS.length];\n }\n return out;\n}\n\n/**\n * Derive the S256 code_challenge from a code_verifier.\n *\n * `code_challenge = BASE64URL(SHA256(code_verifier))` — RFC 7636 §4.2.\n *\n * @throws Error when `verifier` is shorter than 43 or longer than 128 chars.\n */\nexport async function deriveCodeChallenge(verifier: string): Promise<string> {\n assertVerifierLength(verifier.length);\n const crypto = getCrypto();\n const data = new TextEncoder().encode(verifier);\n const digest = await crypto.subtle.digest('SHA-256', data);\n return base64UrlEncode(digest);\n}\n\nexport interface PkcePair {\n codeVerifier: string;\n codeChallenge: string;\n codeChallengeMethod: 'S256';\n}\n\n/**\n * Convenience: produce a fresh verifier + matching challenge in one call.\n */\nexport async function generatePkcePair(length?: number): Promise<PkcePair> {\n const codeVerifier = generateCodeVerifier(length);\n const codeChallenge = await deriveCodeChallenge(codeVerifier);\n return { codeVerifier, codeChallenge, codeChallengeMethod: 'S256' };\n}\n","/**\n * Inputs for the OAuth `authorization_code` token request.\n */\nexport interface AuthorizationCodeBodyInput {\n clientId: string;\n code: string;\n redirectUri: string;\n codeVerifier: string;\n}\n\n/**\n * Inputs for the OAuth `refresh_token` token request.\n */\nexport interface RefreshTokenBodyInput {\n clientId: string;\n refreshToken: string;\n}\n\n/**\n * Build the `application/x-www-form-urlencoded` body for the\n * `grant_type=authorization_code` token endpoint call (PKCE flow).\n */\nexport function buildAuthorizationCodeBody(input: AuthorizationCodeBodyInput): string {\n return new URLSearchParams({\n client_id: input.clientId,\n grant_type: 'authorization_code',\n code: input.code,\n redirect_uri: input.redirectUri,\n code_verifier: input.codeVerifier,\n }).toString();\n}\n\n/**\n * Build the `application/x-www-form-urlencoded` body for the\n * `grant_type=refresh_token` token endpoint call.\n */\nexport function buildRefreshTokenBody(input: RefreshTokenBodyInput): string {\n return new URLSearchParams({\n client_id: input.clientId,\n grant_type: 'refresh_token',\n refresh_token: input.refreshToken,\n }).toString();\n}\n","/**\n * OIDC token-endpoint helpers.\n *\n * Pure (no React, no hooks). Wraps the realm-aware token endpoint with the\n * PKCE `authorization_code` and `refresh_token` grants. The transport is\n * injected so callers can use the HTTP client of their choice.\n *\n * Use these from app-side hooks (e.g. `useKeycloakExchange`) instead of\n * duplicating the body-builder + POST + normalise dance.\n */\n\nimport { buildTokenEndpoint } from '../utils/buildKeycloakUrls';\nimport {\n buildAuthorizationCodeBody,\n buildRefreshTokenBody,\n} from '../utils/buildTokenRequestBody';\nimport { normalizeTokenResponse } from '../utils/normalizeTokenResponse';\n\nimport type { HttpClient } from '../http/HttpClient';\nimport type { RawTokenResponse, TokenResponse } from '../types/TokenResponse';\n\nconst FORM_HEADERS: Record<string, string> = {\n 'Content-Type': 'application/x-www-form-urlencoded',\n};\n\nexport interface ExchangeAuthorizationCodeInput {\n http: HttpClient;\n baseUrl: string;\n realm: string;\n clientId: string;\n code: string;\n redirectUri: string;\n codeVerifier: string;\n}\n\nexport interface RefreshAccessTokenInput {\n http: HttpClient;\n baseUrl: string;\n realm: string;\n clientId: string;\n refreshToken: string;\n}\n\nasync function postTokenEndpoint(\n http: HttpClient,\n url: string,\n body: string,\n): Promise<TokenResponse> {\n const response = await http({\n url,\n method: 'POST',\n headers: FORM_HEADERS,\n body,\n });\n if (!response.ok) {\n throw new Error(`token endpoint POST failed: ${String(response.status)}`);\n }\n return normalizeTokenResponse(response.data as RawTokenResponse);\n}\n\n/**\n * Exchange a PKCE authorization `code` for tokens via the realm's token\n * endpoint (`grant_type=authorization_code`).\n *\n * @throws Error when the HTTP call returns non-2xx or the body is missing\n * `access_token`.\n */\nexport async function exchangeAuthorizationCode(\n input: ExchangeAuthorizationCodeInput,\n): Promise<TokenResponse> {\n const url = buildTokenEndpoint(input.baseUrl, input.realm);\n const body = buildAuthorizationCodeBody({\n clientId: input.clientId,\n code: input.code,\n redirectUri: input.redirectUri,\n codeVerifier: input.codeVerifier,\n });\n return postTokenEndpoint(input.http, url, body);\n}\n\n/**\n * Swap a refresh token for a fresh access/refresh-token pair via the realm's\n * token endpoint (`grant_type=refresh_token`).\n *\n * @throws Error when the HTTP call returns non-2xx or the body is missing\n * `access_token`.\n */\nexport async function refreshAccessToken(\n input: RefreshAccessTokenInput,\n): Promise<TokenResponse> {\n const url = buildTokenEndpoint(input.baseUrl, input.realm);\n const body = buildRefreshTokenBody({\n clientId: input.clientId,\n refreshToken: input.refreshToken,\n });\n return postTokenEndpoint(input.http, url, body);\n}\n","/**\n * Roles emitted by Keycloak realms in the dloizides.com portfolio.\n *\n * Lives in its own file per the project convention: each exported `const enum`\n * sits alone so it can be imported without dragging the rest of the type tree.\n */\nexport const enum KeycloakRoles {\n SuperUser = 'superUser',\n Admin = 'admin',\n User = 'user',\n}\n\nconst KEYCLOAK_ROLE_VALUES: readonly string[] = [\n KeycloakRoles.SuperUser,\n KeycloakRoles.Admin,\n KeycloakRoles.User,\n];\n\n/**\n * Type guard that narrows a string to a known {@link KeycloakRoles} value.\n *\n * Use this when ingesting role claims from the network, where the wire payload\n * is `string[]` but downstream code wants `KeycloakRoles[]`.\n */\nexport function isKeycloakRole(value: string): value is KeycloakRoles {\n return KEYCLOAK_ROLE_VALUES.includes(value);\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * Subset of `Storage` we actually use. Lets callers inject `localStorage`,\n * `sessionStorage`, or any compatible polyfill.\n */\nexport interface StorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nexport interface BrowserStorageTokenStorageOptions {\n storage: StorageLike;\n /** Storage key. Defaults to `auth.tokens`. */\n key?: string;\n}\n\nconst DEFAULT_KEY = 'auth.tokens';\n\nfunction isAuthTokens(value: unknown): value is AuthTokens {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const candidate = value as Record<string, unknown>;\n return typeof candidate.accessToken === 'string' && typeof candidate.expiresAt === 'number';\n}\n\n/**\n * Persist tokens in any `Storage`-shaped backend (`localStorage`, `sessionStorage`,\n * AsyncStorage shim, etc.). The class is sync-aware but exposes a Promise-based\n * API to match {@link TokenStorage}.\n *\n * Errors during read are swallowed and surfaced as `null` (corrupt JSON, denied\n * access in some private-mode browsers, etc.). Errors during write/clear are\n * propagated so callers can decide whether to retry or fall back.\n */\nexport class BrowserStorageTokenStorage implements TokenStorage {\n private readonly storage: StorageLike;\n private readonly key: string;\n\n constructor(options: BrowserStorageTokenStorageOptions) {\n this.storage = options.storage;\n this.key = options.key ?? DEFAULT_KEY;\n }\n\n read(): Promise<AuthTokens | null> {\n return Promise.resolve(this.readSync());\n }\n\n write(tokens: AuthTokens): Promise<void> {\n this.storage.setItem(this.key, JSON.stringify(tokens));\n return Promise.resolve();\n }\n\n clear(): Promise<void> {\n this.storage.removeItem(this.key);\n return Promise.resolve();\n }\n\n private readSync(): AuthTokens | null {\n try {\n const raw = this.storage.getItem(this.key);\n if (raw === null || raw === '') {\n return null;\n }\n const parsed: unknown = JSON.parse(raw);\n return isAuthTokens(parsed) ? parsed : null;\n } catch {\n return null;\n }\n }\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * In-memory storage backed by a single instance variable.\n *\n * Useful for tests, server-side rendering, and as a default fallback when no\n * platform-specific storage is available. Tokens are lost on process exit.\n */\nexport class InMemoryTokenStorage implements TokenStorage {\n private tokens: AuthTokens | null = null;\n\n read(): Promise<AuthTokens | null> {\n return Promise.resolve(this.tokens);\n }\n\n write(tokens: AuthTokens): Promise<void> {\n this.tokens = tokens;\n return Promise.resolve();\n }\n\n clear(): Promise<void> {\n this.tokens = null;\n return Promise.resolve();\n }\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * Web token storage that pairs an in-memory access token with a backend-managed\n * httpOnly + Secure + SameSite=Lax refresh-token cookie.\n *\n * The browser handles the refresh cookie (`__Host-refresh` by default — set by\n * the IdentityService on login and rotated on every `/auth/refresh-cookie`\n * call). JavaScript MUST NOT have access to it, so this adapter intentionally\n * does NOT persist `refreshToken` into the cookie itself; that's the backend's\n * job. The adapter just keeps the access token in memory and exposes the same\n * `TokenStorage` interface as `BrowserStorageTokenStorage` so the rest of the\n * library doesn't need to know which transport is in use.\n *\n * Page reloads drop the access token (memory clears), but the refresh cookie\n * survives — `RefreshInterceptor` swaps it for a new access token via\n * `/auth/refresh-cookie` with `credentials: 'include'`.\n */\nexport class CookieTokenStorage implements TokenStorage {\n private accessToken: string | null = null;\n private idToken: string | undefined = undefined;\n private expiresAt: number = 0;\n\n read(): Promise<AuthTokens | null> {\n if (this.accessToken === null) {\n return Promise.resolve(null);\n }\n const tokens: AuthTokens = {\n accessToken: this.accessToken,\n idToken: this.idToken,\n expiresAt: this.expiresAt,\n };\n return Promise.resolve(tokens);\n }\n\n write(tokens: AuthTokens): Promise<void> {\n this.accessToken = tokens.accessToken;\n this.idToken = tokens.idToken;\n this.expiresAt = tokens.expiresAt;\n // Intentionally drop tokens.refreshToken — the backend cookie is the\n // source of truth for refresh material, and we never want it on JS heap.\n return Promise.resolve();\n }\n\n clear(): Promise<void> {\n this.accessToken = null;\n this.idToken = undefined;\n this.expiresAt = 0;\n return Promise.resolve();\n }\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * Subset of `expo-secure-store` we use, abstracted so the package itself never\n * imports `expo-secure-store` (and so web bundles never pull it in).\n *\n * Mobile consumers wire this up at the edge:\n *\n * ```ts\n * import * as SecureStore from 'expo-secure-store';\n * const adapter: SecureStoreLike = {\n * getItemAsync: SecureStore.getItemAsync,\n * setItemAsync: SecureStore.setItemAsync,\n * deleteItemAsync: SecureStore.deleteItemAsync,\n * };\n * ```\n */\nexport interface SecureStoreLike {\n getItemAsync(key: string, options?: { requireAuthentication?: boolean }): Promise<string | null>;\n setItemAsync(key: string, value: string, options?: { requireAuthentication?: boolean }): Promise<void>;\n deleteItemAsync(key: string, options?: { requireAuthentication?: boolean }): Promise<void>;\n}\n\n/**\n * Optional biometric gate. When provided AND `requireBiometric` is `true`, the\n * gate's `unlock()` is called before reading the refresh token. Used by mobile\n * consumers that opt in to biometric-protected sessions.\n */\nexport interface BiometricGateLike {\n unlock(): Promise<void>;\n isEnabled(): boolean;\n}\n\nexport interface SecureStoreTokenStorageOptions {\n secureStore: SecureStoreLike;\n /** Defaults applied to every key — usually `'auth'`. */\n keyPrefix?: string;\n /**\n * When true, secure-store reads use `requireAuthentication: true`, prompting\n * the OS biometric / device-passcode dialog (iOS Keychain access control,\n * Android Keystore strongbox).\n */\n requireAuthentication?: boolean;\n /**\n * Optional biometric gate run BEFORE the secure-store read. Belt-and-braces\n * with `requireAuthentication`: the gate enforces our own retry/lockout\n * semantics, while `requireAuthentication` enforces the OS keychain ACL.\n */\n biometricGate?: BiometricGateLike;\n}\n\nconst DEFAULT_PREFIX = 'auth';\nconst ACCESS_KEY = 'access';\nconst REFRESH_KEY = 'refresh';\nconst ID_KEY = 'id';\nconst EXPIRES_KEY = 'expiresAt';\n\n/**\n * Persist tokens in iOS Keychain / Android Keystore via `expo-secure-store`.\n *\n * Keys are split (access / refresh / id / expiresAt) rather than stored as a\n * single JSON blob so the OS-level ACL on the refresh token slot can be\n * tightened independently. With `requireAuthentication: true`, reads of any\n * key trigger the OS biometric prompt — that's why we keep it OFF for writes\n * (login flows must not prompt) and ON for reads (boot-time session restore).\n *\n * Storage key shape: `{prefix}.{slot}` (e.g. `auth.refresh`).\n */\nexport class SecureStoreTokenStorage implements TokenStorage {\n private readonly secureStore: SecureStoreLike;\n private readonly prefix: string;\n private readonly requireAuthentication: boolean;\n private readonly biometricGate: BiometricGateLike | undefined;\n\n constructor(options: SecureStoreTokenStorageOptions) {\n this.secureStore = options.secureStore;\n this.prefix = options.keyPrefix ?? DEFAULT_PREFIX;\n this.requireAuthentication = options.requireAuthentication ?? false;\n this.biometricGate = options.biometricGate;\n }\n\n async read(): Promise<AuthTokens | null> {\n if (this.shouldRunBiometricGate()) {\n // Biometric gate may throw — let it propagate. Caller (RefreshInterceptor\n // / AuthClient.init) will treat that as a failed restore and clear.\n await (this.biometricGate as BiometricGateLike).unlock();\n }\n const readOptions = this.requireAuthentication ? { requireAuthentication: true } : undefined;\n const accessToken = await this.secureStore.getItemAsync(this.fullKey(ACCESS_KEY), readOptions);\n if (accessToken === null) {\n return null;\n }\n const refreshTokenRaw = await this.secureStore.getItemAsync(this.fullKey(REFRESH_KEY), readOptions);\n const idTokenRaw = await this.secureStore.getItemAsync(this.fullKey(ID_KEY));\n const expiresAtRaw = await this.secureStore.getItemAsync(this.fullKey(EXPIRES_KEY));\n const expiresAt = parseExpiresAt(expiresAtRaw);\n return {\n accessToken,\n refreshToken: refreshTokenRaw === null ? undefined : refreshTokenRaw,\n idToken: idTokenRaw === null ? undefined : idTokenRaw,\n expiresAt,\n };\n }\n\n async write(tokens: AuthTokens): Promise<void> {\n await this.secureStore.setItemAsync(this.fullKey(ACCESS_KEY), tokens.accessToken);\n if (tokens.refreshToken !== undefined && tokens.refreshToken !== '') {\n await this.secureStore.setItemAsync(this.fullKey(REFRESH_KEY), tokens.refreshToken);\n } else {\n await this.secureStore.deleteItemAsync(this.fullKey(REFRESH_KEY));\n }\n if (tokens.idToken !== undefined && tokens.idToken !== '') {\n await this.secureStore.setItemAsync(this.fullKey(ID_KEY), tokens.idToken);\n } else {\n await this.secureStore.deleteItemAsync(this.fullKey(ID_KEY));\n }\n await this.secureStore.setItemAsync(this.fullKey(EXPIRES_KEY), String(tokens.expiresAt));\n }\n\n async clear(): Promise<void> {\n await this.secureStore.deleteItemAsync(this.fullKey(ACCESS_KEY));\n await this.secureStore.deleteItemAsync(this.fullKey(REFRESH_KEY));\n await this.secureStore.deleteItemAsync(this.fullKey(ID_KEY));\n await this.secureStore.deleteItemAsync(this.fullKey(EXPIRES_KEY));\n }\n\n private shouldRunBiometricGate(): boolean {\n return this.biometricGate !== undefined && this.biometricGate.isEnabled();\n }\n\n private fullKey(slot: string): string {\n return `${this.prefix}.${slot}`;\n }\n}\n\nfunction parseExpiresAt(raw: string | null): number {\n if (raw === null || raw === '') {\n return 0;\n }\n const parsed = Number(raw);\n return Number.isFinite(parsed) ? parsed : 0;\n}\n","/**\n * Subset of `expo-local-authentication` we use, abstracted so the package\n * itself never imports `expo-local-authentication`.\n *\n * Mobile consumers wire this up at the edge:\n *\n * ```ts\n * import * as LocalAuthentication from 'expo-local-authentication';\n * const adapter: LocalAuthLike = {\n * hasHardwareAsync: LocalAuthentication.hasHardwareAsync,\n * isEnrolledAsync: LocalAuthentication.isEnrolledAsync,\n * authenticateAsync: (opts) => LocalAuthentication.authenticateAsync(opts),\n * };\n * ```\n */\nexport interface LocalAuthLike {\n hasHardwareAsync(): Promise<boolean>;\n isEnrolledAsync(): Promise<boolean>;\n authenticateAsync(options?: {\n promptMessage?: string;\n cancelLabel?: string;\n disableDeviceFallback?: boolean;\n }): Promise<{ success: boolean; error?: string }>;\n}\n\n/**\n * Optional persistence so the \"enabled\" flag survives app restart. Backed by\n * any `TokenStorage`-shaped key/value store via the calling consumer (or the\n * app's settings store). We keep it pluggable to avoid coupling\n * `BiometricGate` to a specific storage adapter.\n */\nexport interface BiometricFlagStore {\n read(): Promise<boolean>;\n write(enabled: boolean): Promise<void>;\n}\n\nexport interface BiometricGateOptions {\n localAuth: LocalAuthLike;\n /** Optional persistence for the user's opt-in choice. */\n flagStore?: BiometricFlagStore;\n /** Default prompt message; consumers usually override. */\n promptMessage?: string;\n /** Max consecutive prompt failures before {@link unlock} throws. Default 3. */\n maxFailures?: number;\n}\n\nconst DEFAULT_PROMPT = 'Unlock to continue';\nconst DEFAULT_MAX_FAILURES = 3;\n\n/**\n * Biometric gate wrapping `expo-local-authentication`.\n *\n * Lifecycle:\n *\n * 1. `isAvailable()` — checks hardware + enrolment. Pure read.\n * 2. `setEnabled(true|false)` — consumer's settings UI flips this. Persisted\n * via the optional flag store. Default = disabled (opt-in).\n * 3. `unlock()` — called by `SecureStoreTokenStorage` (when wired) or by\n * consumer code before sensitive operations. Counts consecutive failures;\n * after `maxFailures` (default 3), throws `BiometricLockedOutError` and\n * consumers MUST navigate to login.\n * 4. `prompt()` — one-shot biometric prompt that doesn't change the failure\n * counter. Useful for re-confirming an action mid-session.\n *\n * The failure counter resets on success.\n */\nexport class BiometricGate {\n private readonly localAuth: LocalAuthLike;\n private readonly flagStore: BiometricFlagStore | undefined;\n private readonly promptMessage: string;\n private readonly maxFailures: number;\n private enabled: boolean = false;\n private failureCount: number = 0;\n private hydrated: boolean = false;\n\n constructor(options: BiometricGateOptions) {\n this.localAuth = options.localAuth;\n this.flagStore = options.flagStore;\n this.promptMessage = options.promptMessage ?? DEFAULT_PROMPT;\n this.maxFailures = options.maxFailures ?? DEFAULT_MAX_FAILURES;\n }\n\n /** Hardware present AND a fingerprint/face ID is enrolled. */\n async isAvailable(): Promise<boolean> {\n const hasHardware = await this.localAuth.hasHardwareAsync();\n if (!hasHardware) {\n return false;\n }\n return this.localAuth.isEnrolledAsync();\n }\n\n /** Synchronous read of the current enabled flag (post-hydration). */\n isEnabled(): boolean {\n return this.enabled;\n }\n\n /** Read the persisted opt-in flag once at app boot. Idempotent. */\n async hydrate(): Promise<void> {\n if (this.hydrated) {\n return;\n }\n this.hydrated = true;\n if (this.flagStore !== undefined) {\n this.enabled = await this.flagStore.read();\n }\n }\n\n /**\n * Toggle biometric requirement. Persists via {@link BiometricFlagStore} when\n * configured. Resets the failure counter so a re-enable starts fresh.\n */\n async setEnabled(enabled: boolean): Promise<void> {\n this.enabled = enabled;\n this.failureCount = 0;\n if (this.flagStore !== undefined) {\n await this.flagStore.write(enabled);\n }\n }\n\n /** Reset the failure counter. Tests + consumer recovery flows. */\n resetFailures(): void {\n this.failureCount = 0;\n }\n\n /**\n * One-shot biometric prompt. Returns `true` on success. Does NOT throw on\n * failure or update the failure counter — useful for action confirmation.\n */\n async prompt(): Promise<boolean> {\n const result = await this.localAuth.authenticateAsync({\n promptMessage: this.promptMessage,\n });\n return result.success;\n }\n\n /**\n * Required pre-condition for sensitive token reads. No-op when disabled.\n *\n * @throws Error after {@link maxFailures} consecutive failures.\n * @throws Error on a single failure (lower in the count, but still throws so\n * `SecureStoreTokenStorage.read()` short-circuits).\n */\n async unlock(): Promise<void> {\n if (!this.enabled) {\n return;\n }\n const result = await this.localAuth.authenticateAsync({\n promptMessage: this.promptMessage,\n });\n if (result.success) {\n this.failureCount = 0;\n return;\n }\n this.failureCount += 1;\n if (this.failureCount >= this.maxFailures) {\n throw new Error('Biometric authentication failed; locked out');\n }\n throw new Error('Biometric authentication failed');\n }\n}\n","import type { AuthEventEmitter } from '../events/AuthEventEmitter';\nimport type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * The pluggable refresh function the interceptor calls when an access token\n * is missing or expired. Implementations differ per transport:\n *\n * - **Mobile (SecureStore)**: posts to `/auth/refresh` with the refresh token\n * from `AuthTokens.refreshToken`.\n * - **Web (Cookie)**: posts to `/auth/refresh-cookie` with `credentials:\n * 'include'` — the refresh token rides on the httpOnly cookie; `current`\n * carries only the access token (refresh token will be undefined).\n *\n * Returns the new token bundle, or `null` when refresh failed in a way that\n * means \"session over\" (e.g., 401 from the auth server).\n */\nexport type RefreshFn = (current: AuthTokens | null) => Promise<AuthTokens | null>;\n\nexport interface RefreshInterceptorOptions {\n storage: TokenStorage;\n refresh: RefreshFn;\n events: AuthEventEmitter;\n /**\n * Optional callback fired AFTER tokens are persisted on a successful\n * refresh. Used by `AuthClient` to update the inactivity tracker.\n */\n onRefreshSuccess?: (tokens: AuthTokens) => Promise<void> | void;\n}\n\n/**\n * Coordinates refresh-token swaps so concurrent 401s don't trigger N parallel\n * refreshes. The first caller to hit `refreshTokens()` while no refresh is\n * already in flight wins the role of \"refresher\"; everyone else awaits the\n * same promise.\n *\n * On failure, storage is cleared and `sessionExpired` is emitted exactly once\n * per refresh attempt.\n */\nexport class RefreshInterceptor {\n private readonly storage: TokenStorage;\n private readonly refresh: RefreshFn;\n private readonly events: AuthEventEmitter;\n private readonly onRefreshSuccess: ((tokens: AuthTokens) => Promise<void> | void) | undefined;\n private inflight: Promise<AuthTokens | null> | null = null;\n\n constructor(options: RefreshInterceptorOptions) {\n this.storage = options.storage;\n this.refresh = options.refresh;\n this.events = options.events;\n this.onRefreshSuccess = options.onRefreshSuccess;\n }\n\n /**\n * Trigger (or join) a refresh. Returns the new tokens, or `null` if the\n * refresh failed — in which case storage has already been cleared and\n * `sessionExpired` already fired.\n */\n async refreshTokens(): Promise<AuthTokens | null> {\n if (this.inflight !== null) {\n return this.inflight;\n }\n this.inflight = this.runRefresh();\n try {\n return await this.inflight;\n } finally {\n this.inflight = null;\n }\n }\n\n /**\n * Whether a refresh is currently in flight. Exposed for tests / debug.\n */\n get isRefreshing(): boolean {\n return this.inflight !== null;\n }\n\n private async runRefresh(): Promise<AuthTokens | null> {\n const current = await this.storage.read();\n let next: AuthTokens | null;\n try {\n next = await this.refresh(current);\n } catch {\n await this.failHard();\n return null;\n }\n if (next === null) {\n await this.failHard();\n return null;\n }\n await this.storage.write(next);\n if (this.onRefreshSuccess !== undefined) {\n await this.onRefreshSuccess(next);\n }\n return next;\n }\n\n private async failHard(): Promise<void> {\n await this.storage.clear();\n this.events.emit('sessionExpired');\n }\n}\n","/**\n * Pluggable persistence for the `lastRefreshedAt` timestamp.\n *\n * Decoupled from `TokenStorage` so consumers can pick a different backend\n * (e.g., write through `AsyncStorage` on RN where the secure store would\n * gate every read on biometric).\n */\nexport interface InactivityStore {\n read(): Promise<number | null>;\n write(timestamp: number): Promise<void>;\n clear(): Promise<void>;\n}\n\nexport interface InactivityTrackerOptions {\n store: InactivityStore;\n /**\n * Maximum days the user can be inactive (no successful refresh) before\n * sessions are forcibly cleared. Default 90 (matches mobile decision).\n */\n maxInactivityDays?: number;\n /** Inject for tests; defaults to `Date.now`. */\n now?: () => number;\n}\n\nconst DEFAULT_MAX_DAYS = 90;\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\n\n/**\n * Tracks the last time a refresh succeeded and decides whether the session\n * has aged past its inactivity threshold.\n *\n * - `markActive(now?)` is called by `RefreshInterceptor` after every\n * successful token swap.\n * - `isExpired()` is called from `AuthClient.init()` at boot. If true,\n * consumers clear tokens and emit `sessionExpired`.\n *\n * Choosing days (not e.g. minutes) makes the policy match what users\n * understand: a session left untouched for 90 days needs re-auth.\n */\nexport class InactivityTracker {\n private readonly store: InactivityStore;\n private readonly maxInactivityMs: number;\n private readonly now: () => number;\n\n constructor(options: InactivityTrackerOptions) {\n this.store = options.store;\n const days = options.maxInactivityDays ?? DEFAULT_MAX_DAYS;\n this.maxInactivityMs = days * MS_PER_DAY;\n this.now = options.now ?? Date.now;\n }\n\n async markActive(timestamp?: number): Promise<void> {\n await this.store.write(timestamp ?? this.now());\n }\n\n async getLastActive(): Promise<number | null> {\n return this.store.read();\n }\n\n async isExpired(): Promise<boolean> {\n const last = await this.store.read();\n if (last === null) {\n // Never refreshed — treat as not expired so a fresh login isn't punished.\n return false;\n }\n return this.now() - last > this.maxInactivityMs;\n }\n\n async clear(): Promise<void> {\n await this.store.clear();\n }\n}\n","/**\n * Minimal HTTP transport the package depends on.\n *\n * `AuthClient` orchestrates token-related HTTP calls (login, refresh, logout,\n * password reset) but doesn't import `fetch` directly — keeping the package\n * runtime-agnostic. Consumers wire native fetch, axios, ky, or whatever their\n * platform exposes.\n */\nexport interface HttpRequest {\n url: string;\n method: 'GET' | 'POST' | 'DELETE';\n headers?: Record<string, string>;\n /** When set, body is sent as the request body; serialization is the caller's job. */\n body?: string;\n /** Browser fetch only — pass through `credentials: 'include'` for cookie auth. */\n credentials?: 'include' | 'same-origin' | 'omit';\n}\n\nexport interface HttpResponse {\n status: number;\n ok: boolean;\n /** Parsed body (already JSON-decoded). `undefined` for 204 / empty bodies. */\n data?: unknown;\n}\n\nexport type HttpClient = (request: HttpRequest) => Promise<HttpResponse>;\n\n/**\n * Wrap the platform's native `fetch` into the package's `HttpClient` shape.\n * Decoded JSON when `Content-Type` is JSON, otherwise leaves data undefined.\n *\n * Errors thrown by `fetch` (network / abort) are NOT swallowed — callers\n * decide whether to treat them as session-ending.\n */\nexport function createFetchHttpClient(fetchImpl: typeof fetch): HttpClient {\n return async (request: HttpRequest): Promise<HttpResponse> => {\n const init: RequestInit = {\n method: request.method,\n headers: request.headers,\n body: request.body,\n };\n if (request.credentials !== undefined) {\n init.credentials = request.credentials;\n }\n const response = await fetchImpl(request.url, init);\n let data: unknown;\n if (response.status !== 204) {\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.includes('application/json')) {\n data = (await response.json()) as unknown;\n }\n }\n return {\n status: response.status,\n ok: response.ok,\n data,\n };\n };\n}\n","import type { HttpClient } from '../http/HttpClient';\n\n/**\n * Backend session record returned by `GET /me/sessions`.\n *\n * Shape mirrors the existing IdentityService response — see\n * `Services/Identity/.../GetSessions.cs`. Defined as a permissive interface\n * so newer fields (added server-side) flow through without a package bump.\n */\nexport interface AuthSessionInfo {\n id: string;\n isCurrent?: boolean;\n ipAddress?: string;\n userAgent?: string;\n createdAt?: string;\n lastSeenAt?: string;\n [key: string]: unknown;\n}\n\nexport interface AuthApiClientOptions {\n http: HttpClient;\n /** API base, e.g. `https://api.dloizides.com`. No trailing slash needed. */\n baseUrl: string;\n /**\n * Optional supplier of the current access token, used as a Bearer header on\n * authenticated calls (sessions list, revoke, logout). When omitted those\n * calls send no Authorization header — typical for cookie-based web auth.\n */\n getAccessToken?: () => Promise<string | null>;\n /**\n * When true, every request adds `credentials: 'include'`. Required for\n * cookie-based web auth (`__Host-refresh` lives in an httpOnly cookie).\n */\n useCredentials?: boolean;\n}\n\nexport interface OtpLoginRequest {\n email: string;\n otp: string;\n tenantId?: string;\n offlineAccess?: boolean;\n}\n\nexport interface PasswordLoginRequest {\n email: string;\n password: string;\n tenantId?: string;\n offlineAccess?: boolean;\n}\n\nexport interface ForgotPasswordRequest {\n email: string;\n tenantId?: string;\n}\n\nexport interface ResetPasswordRequest {\n token: string;\n newPassword: string;\n}\n\nexport interface RawAuthLoginResponse {\n access_token?: string;\n refresh_token?: string;\n id_token?: string;\n expires_in?: number;\n token_type?: string;\n scope?: string;\n [key: string]: unknown;\n}\n\n/**\n * Thin HTTP client for the IdentityService auth surface.\n *\n * Endpoint paths match the backend task `auth-password-reset-backend.md`:\n *\n * - `POST /auth/verify-otp`\n * - `POST /auth/login` (password)\n * - `POST /auth/logout` and `POST /auth/logout?everywhere=true`\n * - `POST /auth/refresh-cookie` (web cookie flow)\n * - `POST /auth/forgot-password`\n * - `POST /auth/reset-password`\n * - `GET /me/sessions`\n * - `POST /me/sessions/{id}/revoke`\n *\n * Doesn't touch token storage — that's `AuthClient`'s job. Doesn't decide\n * what to do with errors — callers handle them. Just builds requests and\n * deserialises responses.\n */\nexport class AuthApiClient {\n private readonly http: HttpClient;\n private readonly baseUrl: string;\n private readonly getAccessToken: (() => Promise<string | null>) | undefined;\n private readonly useCredentials: boolean;\n\n constructor(options: AuthApiClientOptions) {\n this.http = options.http;\n this.baseUrl = options.baseUrl.replace(/\\/$/, '');\n this.getAccessToken = options.getAccessToken;\n this.useCredentials = options.useCredentials ?? false;\n }\n\n loginWithOtp(request: OtpLoginRequest): Promise<RawAuthLoginResponse> {\n return this.postLogin('/auth/verify-otp', request);\n }\n\n loginWithPassword(request: PasswordLoginRequest): Promise<RawAuthLoginResponse> {\n return this.postLogin('/auth/login', request);\n }\n\n /** Web cookie-flow refresh. Sends no body; cookie travels via `credentials`. */\n async refreshCookie(): Promise<RawAuthLoginResponse> {\n const response = await this.http({\n url: `${this.baseUrl}/auth/refresh-cookie`,\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n credentials: this.useCredentials ? 'include' : undefined,\n });\n if (!response.ok) {\n throw new Error(`refresh-cookie failed with status ${response.status}`);\n }\n return (response.data ?? {}) as RawAuthLoginResponse;\n }\n\n async logout(everywhere: boolean = false): Promise<void> {\n const url = everywhere ? `${this.baseUrl}/auth/logout?everywhere=true` : `${this.baseUrl}/auth/logout`;\n const response = await this.http({\n url,\n method: 'POST',\n headers: await this.authHeaders(),\n credentials: this.useCredentials ? 'include' : undefined,\n });\n if (!response.ok) {\n throw new Error(`logout failed with status ${response.status}`);\n }\n }\n\n async forgotPassword(request: ForgotPasswordRequest): Promise<void> {\n const response = await this.http({\n url: `${this.baseUrl}/auth/forgot-password`,\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(request),\n });\n // Backend returns 200 unconditionally (no enumeration). Anything else is\n // a real failure.\n if (!response.ok) {\n throw new Error(`forgot-password failed with status ${response.status}`);\n }\n }\n\n async resetPassword(request: ResetPasswordRequest): Promise<void> {\n const response = await this.http({\n url: `${this.baseUrl}/auth/reset-password`,\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(request),\n });\n if (!response.ok) {\n throw new Error(`reset-password failed with status ${response.status}`);\n }\n }\n\n async listSessions(): Promise<AuthSessionInfo[]> {\n const response = await this.http({\n url: `${this.baseUrl}/me/sessions`,\n method: 'GET',\n headers: await this.authHeaders(),\n credentials: this.useCredentials ? 'include' : undefined,\n });\n if (!response.ok) {\n throw new Error(`listSessions failed with status ${response.status}`);\n }\n return Array.isArray(response.data) ? (response.data as AuthSessionInfo[]) : [];\n }\n\n async revokeSession(sessionId: string): Promise<void> {\n const response = await this.http({\n url: `${this.baseUrl}/me/sessions/${encodeURIComponent(sessionId)}/revoke`,\n method: 'POST',\n headers: await this.authHeaders(),\n credentials: this.useCredentials ? 'include' : undefined,\n });\n if (!response.ok) {\n throw new Error(`revokeSession failed with status ${response.status}`);\n }\n }\n\n private async postLogin(path: string, body: object): Promise<RawAuthLoginResponse> {\n const response = await this.http({\n url: `${this.baseUrl}${path}`,\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n credentials: this.useCredentials ? 'include' : undefined,\n });\n if (!response.ok) {\n throw new Error(`login failed with status ${response.status}`);\n }\n return (response.data ?? {}) as RawAuthLoginResponse;\n }\n\n private async authHeaders(): Promise<Record<string, string>> {\n const headers: Record<string, string> = { 'content-type': 'application/json' };\n if (this.getAccessToken === undefined) {\n return headers;\n }\n const token = await this.getAccessToken();\n if (token !== null && token !== '') {\n headers.authorization = `Bearer ${token}`;\n }\n return headers;\n }\n}\n","import type { HttpClient } from '../http/HttpClient';\n\n/**\n * Same-origin client for a per-app **Backend-For-Frontend** (`bff-katalogos`,\n * `bff-erevna`, ...). The BFF terminates authentication server-side: the\n * browser only ever holds an opaque httpOnly session cookie, never a token.\n *\n * `BffAuthClient` therefore does **no token handling at all**. Every call is a\n * same-origin `fetch` with `credentials: 'include'` so the session cookie\n * travels automatically. State-changing calls carry the `X-BFF-Csrf` header\n * the `Bff.AspNetCore` anti-forgery middleware requires.\n *\n * This is the BFF-era replacement for the direct-KC `AuthClient` / ROPC\n * adapters. It is shared so every per-app SPA wires it identically — Phase 1's\n * lesson was that copy-pasted auth adapters ship the same bug N times.\n *\n * @see `NuGetPackages/Bff.AspNetCore/README.md` — \"The SPA contract\".\n */\n\n/** The `X-BFF-Csrf` header value the BFF anti-forgery middleware checks for. */\nconst CSRF_HEADER = 'X-BFF-Csrf';\nconst CSRF_HEADER_VALUE = '1';\nconst JSON_CONTENT_TYPE = 'application/json';\n\n/** Endpoint paths under the BFF — all relative to the SPA's own origin. */\nconst ENDPOINTS = {\n login: '/bff/login',\n logout: '/bff/logout',\n me: '/bff/me',\n register: '/bff/register',\n forgotPassword: '/bff/forgot-password',\n resetPassword: '/bff/reset-password',\n otpRequest: '/bff/otp/request',\n otpVerify: '/bff/otp/verify',\n pinLogin: '/bff/pin/login',\n} as const;\n\n/** Credentials posted to `POST /bff/login`. */\nexport interface BffLoginRequest {\n username: string;\n password: string;\n}\n\n/** Payload for `POST /bff/register` — proxied by the BFF to TenantService. */\nexport interface BffRegisterRequest {\n firstName: string;\n lastName: string;\n username: string;\n email: string;\n password: string;\n tenantName: string;\n [key: string]: unknown;\n}\n\n/** Payload for `POST /bff/forgot-password` — proxied to TenantService. */\nexport interface BffForgotPasswordRequest {\n email: string;\n /** Full URL with a `{token}` placeholder; the backend substitutes the token. */\n resetUrlTemplate?: string;\n [key: string]: unknown;\n}\n\n/** Payload for `POST /bff/reset-password` — proxied to TenantService. */\nexport interface BffResetPasswordRequest {\n token: string;\n newPassword: string;\n}\n\n/**\n * Payload for `POST /bff/otp/request` — the BFF proxies it to TenantService,\n * which generates a short-TTL code and emails it.\n */\nexport interface BffOtpRequestRequest {\n /** The email address (or username) the one-time code is sent to. */\n identifier: string;\n}\n\n/** Payload for `POST /bff/otp/verify` — the BFF exchanges it for a session. */\nexport interface BffOtpVerifyRequest {\n /** The email / username the code was requested for. */\n username: string;\n /** The one-time code the user entered. */\n otp: string;\n}\n\n/**\n * Payload for `POST /bff/pin/login` — the BFF exchanges an event-scoped PIN\n * for a session.\n *\n * The `(event, pin)` pair alone identifies the staff member: no `username` /\n * `password` ever leaves the browser. A PIN entered in an event's context\n * grants that staff member their event-scoped role for that event only\n * (the unified-auth plan §4.4 — event-scoped, per-individual PINs).\n */\nexport interface BffPinLoginRequest {\n /** The numeric PIN the staff member entered. */\n pin: string;\n /** External id of the event the PIN is scoped to (supplied by the page/route). */\n eventExternalId: string;\n}\n\n/**\n * The body `POST /bff/otp/request` relays from TenantService.\n *\n * Anti-enumeration: the shape is identical whether or not the identifier is\n * registered. `code` is non-null only outside production (a dev convenience);\n * the UI must never depend on it being present.\n */\nexport interface BffOtpRequestResult {\n /** Always `true` on a relayed 200 — the request was accepted. */\n success: boolean;\n /** Seconds until the emitted code expires — drives a countdown in the UI. */\n expiresIn: number;\n /** The code itself, non-production only; `null` (or absent) in production. */\n code: string | null;\n}\n\n/**\n * The user object returned by `GET /bff/me` and `POST /bff/login`. The BFF\n * returns the sanitised KC claims under a `user` envelope and **never** a\n * token. Kept permissive so server-added claims flow through without a bump.\n */\nexport interface BffUser {\n sub?: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n preferred_username?: string;\n given_name?: string;\n family_name?: string;\n tenantId?: string;\n roles?: string[];\n [key: string]: unknown;\n}\n\n/** Envelope shape the BFF auth endpoints respond with: `{ user: {...} }`. */\ninterface BffUserEnvelope {\n user?: BffUser;\n}\n\nexport interface BffAuthClientOptions {\n /** Runtime-agnostic HTTP transport (wrap native `fetch` with `createFetchHttpClient`). */\n http: HttpClient;\n /**\n * BFF origin. Defaults to `''` (same-origin) — the production wiring. An\n * explicit origin is only useful for tests or a non-same-origin BFF.\n */\n baseUrl?: string;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** Pull the `user` object out of a `{ user: {...} }` envelope, or `null`. */\nfunction extractUser(data: unknown): BffUser | null {\n if (!isRecord(data)) {\n return null;\n }\n const envelope = data as BffUserEnvelope;\n return isRecord(envelope.user) ? envelope.user : null;\n}\n\n/**\n * Normalise the `POST /bff/otp/request` response body into a `BffOtpRequestResult`.\n *\n * The endpoint is anti-enumeration — the body shape is fixed — but it is read\n * defensively so a missing / malformed field degrades gracefully rather than\n * throwing: `success` defaults to `true` (a relayed 200 means accepted),\n * `expiresIn` to `0`, `code` to `null`.\n */\nfunction toOtpRequestResult(data: unknown): BffOtpRequestResult {\n if (!isRecord(data)) {\n return { success: true, expiresIn: 0, code: null };\n }\n return {\n success: typeof data.success === 'boolean' ? data.success : true,\n expiresIn: typeof data.expiresIn === 'number' ? data.expiresIn : 0,\n code: typeof data.code === 'string' ? data.code : null,\n };\n}\n\n/**\n * Same-origin client for a per-app BFF.\n *\n * No token storage, no refresh logic, no realm awareness — the BFF owns all of\n * that server-side. The browser's only auth artefact is the httpOnly cookie.\n */\nexport class BffAuthClient {\n private readonly http: HttpClient;\n private readonly baseUrl: string;\n\n constructor(options: BffAuthClientOptions) {\n this.http = options.http;\n this.baseUrl = (options.baseUrl ?? '').replace(/\\/$/, '');\n }\n\n /**\n * `POST /bff/login` — the BFF does ROPC against Keycloak server-side, stores\n * the tokens in its Redis vault, and sets the httpOnly session cookie.\n * Returns the sanitised user. Throws on a non-2xx response.\n */\n async login(request: BffLoginRequest): Promise<BffUser> {\n const data = await this.postState(ENDPOINTS.login, request, 'login');\n const user = extractUser(data);\n if (user === null) {\n throw new Error('login: BFF response missing user');\n }\n return user;\n }\n\n /**\n * `POST /bff/logout` — the BFF calls KC end-session, deletes the Redis\n * session, and clears the cookie. Non-fatal: a failed logout still leaves\n * the SPA logged out client-side. Throws only on a non-2xx response.\n */\n async logout(): Promise<void> {\n await this.postState(ENDPOINTS.logout, undefined, 'logout');\n }\n\n /**\n * `GET /bff/me` — the live session's sanitised user, or `null` when there is\n * no session (the BFF answers `401`). Used at app load to bootstrap auth\n * state in place of the old token-in-storage check.\n */\n async getCurrentUser(): Promise<BffUser | null> {\n const response = await this.http({\n url: `${this.baseUrl}${ENDPOINTS.me}`,\n method: 'GET',\n headers: { Accept: JSON_CONTENT_TYPE },\n credentials: 'include',\n });\n if (!response.ok) {\n return null;\n }\n return extractUser(response.data);\n }\n\n /**\n * `POST /bff/register` — the BFF proxies registration to TenantService and,\n * on success, establishes a session exactly like `login`. Returns the user.\n */\n async register(request: BffRegisterRequest): Promise<BffUser> {\n const data = await this.postState(ENDPOINTS.register, request, 'register');\n const user = extractUser(data);\n if (user === null) {\n throw new Error('register: BFF response missing user');\n }\n return user;\n }\n\n /**\n * `POST /bff/forgot-password` — proxied to TenantService. The backend\n * returns 200 unconditionally (no email enumeration); anything else throws.\n */\n async forgotPassword(request: BffForgotPasswordRequest): Promise<void> {\n await this.postState(ENDPOINTS.forgotPassword, request, 'forgot-password');\n }\n\n /**\n * `POST /bff/reset-password` — proxied to TenantService. Throws on a non-2xx\n * response (e.g. `400` for an invalid / expired token).\n */\n async resetPassword(request: BffResetPasswordRequest): Promise<void> {\n await this.postState(ENDPOINTS.resetPassword, request, 'reset-password');\n }\n\n /**\n * `POST /bff/otp/request` — the BFF proxies to TenantService, which generates\n * a short-TTL code and emails it.\n *\n * The endpoint is anti-enumeration: a `200` is the normal path whether or not\n * the identifier is registered. This method therefore **returns** the relayed\n * `{ success, expiresIn, code }` body (so the UI can show the expiry) rather\n * than treating a 200 as opaque. It still throws on a non-2xx — a `501`\n * (OTP not enabled) or `502` (upstream down) is a real failure to surface.\n */\n async requestOtp(request: BffOtpRequestRequest): Promise<BffOtpRequestResult> {\n const data = await this.postState(ENDPOINTS.otpRequest, request, 'otp-request');\n return toOtpRequestResult(data);\n }\n\n /**\n * `POST /bff/otp/verify` — the BFF runs the OTP direct-grant against Keycloak\n * server-side, stores the tokens in its Redis vault, and sets the httpOnly\n * session cookie. Returns the sanitised user, exactly like `login`. Throws on\n * a non-2xx (e.g. `401` for a bad / expired code).\n */\n async verifyOtp(request: BffOtpVerifyRequest): Promise<BffUser> {\n const data = await this.postState(ENDPOINTS.otpVerify, request, 'otp-verify');\n const user = extractUser(data);\n if (user === null) {\n throw new Error('otp-verify: BFF response missing user');\n }\n return user;\n }\n\n /**\n * `POST /bff/pin/login` — the BFF runs the event-scoped PIN direct-grant\n * against Keycloak server-side (the `(event, pin)` pair resolves to the\n * staff member's KC account + event-scoped role), stores the tokens in its\n * Redis vault, and sets the httpOnly session cookie. Returns the sanitised\n * user, exactly like `login` / `verifyOtp`. Throws on a non-2xx — `401` for\n * a bad / expired / locked-out PIN or an unknown event, `501` when PIN login\n * is not an enabled method for this BFF.\n */\n async pinLogin(request: BffPinLoginRequest): Promise<BffUser> {\n const data = await this.postState(ENDPOINTS.pinLogin, request, 'pin-login');\n const user = extractUser(data);\n if (user === null) {\n throw new Error('pin-login: BFF response missing user');\n }\n return user;\n }\n\n /**\n * Shared POST for every state-changing `/bff/*` call: same-origin, cookie\n * included, `X-BFF-Csrf` header attached. Throws a labelled error on non-2xx.\n */\n private async postState(path: string, body: object | undefined, label: string): Promise<unknown> {\n const headers: Record<string, string> = {\n 'Content-Type': JSON_CONTENT_TYPE,\n Accept: JSON_CONTENT_TYPE,\n [CSRF_HEADER]: CSRF_HEADER_VALUE,\n };\n const response = await this.http({\n url: `${this.baseUrl}${path}`,\n method: 'POST',\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n credentials: 'include',\n });\n if (!response.ok) {\n throw new Error(`${label} failed with status ${String(response.status)}`);\n }\n return response.data;\n }\n}\n","import { KeycloakRoles } from '../types/KeycloakRoles';\n\nimport type { KeycloakUserInfo } from '../types/KeycloakUserInfo';\nimport type { NormalizedUser } from '../types/NormalizedUser';\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value !== '';\n}\n\nfunction firstNonEmptyString(...values: unknown[]): string | undefined {\n for (const v of values) {\n if (isNonEmptyString(v)) {\n return v;\n }\n }\n return undefined;\n}\n\nfunction collectRoles(u: KeycloakUserInfo): KeycloakRoles[] {\n const roles: KeycloakRoles[] = [];\n const realmAccessRoles = u.realm_access?.roles;\n if (Array.isArray(realmAccessRoles)) {\n roles.push(...realmAccessRoles);\n }\n\n const resourceAccess = u.resource_access;\n if (resourceAccess !== undefined) {\n for (const v of Object.values(resourceAccess)) {\n const resourceRoles = v.roles;\n if (Array.isArray(resourceRoles)) {\n roles.push(...resourceRoles);\n }\n }\n }\n return roles;\n}\n\n/**\n * Convert a Keycloak `/userinfo` payload into a flat, app-friendly user object.\n *\n * - Aggregates `realm_access.roles` and every `resource_access[*].roles` into a\n * deduplicated `roles` array.\n * - Picks a sensible `displayName` / `username` from whatever claims are\n * present (Keycloak realms vary in which fields they emit).\n * - Returns a safe default (`{ roles: [] }`) when input is undefined.\n */\nexport function normalizeKeycloakUser(u?: KeycloakUserInfo): NormalizedUser {\n if (!u) {\n return { roles: [] };\n }\n const roles = collectRoles(u);\n const fullName = [u.given_name, u.family_name].filter(isNonEmptyString).join(' ');\n const username = firstNonEmptyString(u.preferred_username, u.name, u.email, u.sub);\n const displayName = firstNonEmptyString(u.name, fullName, u.preferred_username, u.email, u.sub);\n\n return {\n id: u.sub,\n username,\n email: u.email,\n displayName,\n firstName: u.given_name,\n lastName: u.family_name,\n emailVerified: Boolean(u.email_verified),\n roles: Array.from(new Set(roles)),\n raw: u,\n };\n}\n","/**\n * Loose shape of an `expo-auth-session` (or browser-side) authorization response.\n *\n * Kept as a structural type rather than importing from `expo-auth-session` so\n * the package stays usable in plain web apps and Node tests.\n */\nexport interface AuthorizationResponseLike {\n type?: string;\n params?: { code?: string; error?: string };\n}\n\n/**\n * Pull the authorization `code` out of a successful redirect response.\n *\n * Returns `undefined` when the response is missing, indicates an error type,\n * or doesn't carry a non-empty `code` query param.\n */\nexport function extractAuthCode(\n response: AuthorizationResponseLike | null | undefined,\n): string | undefined {\n if (!response) {\n return undefined;\n }\n if (response.type !== 'success') {\n return undefined;\n }\n const code = response.params?.code;\n if (typeof code !== 'string' || code === '') {\n return undefined;\n }\n return code;\n}\n","/**\n * Decode the payload segment of a compact JWT.\n *\n * No signature verification — that responsibility belongs to the backend that\n * accepts the token. This helper is for UI concerns: reading `exp` to schedule\n * refresh, reading custom claims for routing decisions, etc.\n *\n * Returns `null` when the input is malformed, base64url-decodes incorrectly, or\n * does not produce a JSON object payload.\n *\n * Runtime requirement: a global `atob` function. Available in browsers, in\n * Node ≥ 16, and in modern bundler test envs (jsdom, node-jest).\n */\nexport function decodeJwt<T = Record<string, unknown>>(token: string | null | undefined): T | null {\n if (typeof token !== 'string' || token === '') {\n return null;\n }\n const parts = token.split('.');\n if (parts.length !== 3) {\n return null;\n }\n const payload = parts[1];\n if (payload === undefined || payload === '') {\n return null;\n }\n try {\n const json = base64UrlDecode(payload);\n const parsed: unknown = JSON.parse(json);\n if (typeof parsed !== 'object' || parsed === null) {\n return null;\n }\n return parsed as T;\n } catch {\n return null;\n }\n}\n\nconst BASE64_PAD_LENGTH = 4;\n\nfunction base64UrlDecode(input: string): string {\n const normalized = input.replace(/-/g, '+').replace(/_/g, '/');\n const padLength =\n (BASE64_PAD_LENGTH - (normalized.length % BASE64_PAD_LENGTH)) % BASE64_PAD_LENGTH;\n const padded = normalized + '='.repeat(padLength);\n if (typeof globalThis.atob !== 'function') {\n throw new Error('decodeJwt: globalThis.atob is unavailable in this runtime');\n }\n return decodeUtf8(globalThis.atob(padded));\n}\n\nfunction decodeUtf8(binary: string): string {\n // `atob` returns a \"binary string\" where each char code is one byte. Convert\n // to UTF-8 properly using TextDecoder when available (browsers + Node).\n if (typeof TextDecoder === 'undefined') {\n return binary;\n }\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return new TextDecoder('utf-8').decode(bytes);\n}\n"]}