@mosano-product-framework/sdk 0.2.0
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.
- package/README.md +827 -0
- package/README.react.md +348 -0
- package/dist/auth/claims-types.d.ts +89 -0
- package/dist/auth/claims.d.ts +125 -0
- package/dist/auth/cross-tab.d.ts +114 -0
- package/dist/auth/errors.d.ts +40 -0
- package/dist/auth/index.d.ts +18 -0
- package/dist/auth/index.js +5 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/oauth-state.d.ts +93 -0
- package/dist/auth/session-manager.d.ts +253 -0
- package/dist/auth/storage.d.ts +36 -0
- package/dist/auth/tenant-directory.d.ts +59 -0
- package/dist/auth/tenant-selection.d.ts +92 -0
- package/dist/chunk-7WAV52EO.js +621 -0
- package/dist/chunk-7WAV52EO.js.map +1 -0
- package/dist/chunk-AJWM5MDZ.js +410 -0
- package/dist/chunk-AJWM5MDZ.js.map +1 -0
- package/dist/chunk-EXPYHNPV.js +212 -0
- package/dist/chunk-EXPYHNPV.js.map +1 -0
- package/dist/chunk-GPWGOYCA.js +85 -0
- package/dist/chunk-GPWGOYCA.js.map +1 -0
- package/dist/chunk-GQJ3QQPH.js +339 -0
- package/dist/chunk-GQJ3QQPH.js.map +1 -0
- package/dist/chunk-K2ELAI2X.js +64 -0
- package/dist/chunk-K2ELAI2X.js.map +1 -0
- package/dist/chunk-LRM6JJ63.js +616 -0
- package/dist/chunk-LRM6JJ63.js.map +1 -0
- package/dist/chunk-XAXFIIRT.js +959 -0
- package/dist/chunk-XAXFIIRT.js.map +1 -0
- package/dist/client/core/client-factory.d.ts +61 -0
- package/dist/client/core/client.d.ts +144 -0
- package/dist/client/core/errors.d.ts +105 -0
- package/dist/client/core/index.d.ts +9 -0
- package/dist/client/core/middleware.d.ts +67 -0
- package/dist/client/core/types.d.ts +99 -0
- package/dist/client/graphql/client.d.ts +66 -0
- package/dist/client/graphql/factory.d.ts +84 -0
- package/dist/client/graphql/operation.d.ts +24 -0
- package/dist/client/graphql/types.d.ts +60 -0
- package/dist/client/graphql/ws-client.d.ts +116 -0
- package/dist/client/index.d.ts +17 -0
- package/dist/client/index.js +227 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/middlewares/admin-auth.d.ts +90 -0
- package/dist/client/middlewares/auth.d.ts +81 -0
- package/dist/client/middlewares/index.d.ts +12 -0
- package/dist/client/middlewares/logging.d.ts +102 -0
- package/dist/client/middlewares/retry.d.ts +138 -0
- package/dist/client/middlewares/tenant.d.ts +60 -0
- package/dist/client/middlewares/turnstile.d.ts +41 -0
- package/dist/client/peer-free.d.ts +25 -0
- package/dist/client/utils/url.d.ts +19 -0
- package/dist/identity/index.d.ts +85 -0
- package/dist/identity/index.js +6 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/types.d.ts +690 -0
- package/dist/identity/v0.d.ts +594 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/react/context.d.ts +47 -0
- package/dist/react/hooks.d.ts +120 -0
- package/dist/react/index.d.ts +19 -0
- package/dist/react/index.js +308 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/provider.d.ts +68 -0
- package/dist/react/store.d.ts +85 -0
- package/dist/storage/index.d.ts +31 -0
- package/dist/storage/index.js +5 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/types.d.ts +107 -0
- package/dist/storage/v0.d.ts +120 -0
- package/package.json +99 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auth/tenant-selection.ts","../src/auth/storage.ts","../src/auth/cross-tab.ts","../src/auth/session-manager.ts"],"names":[],"mappings":"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","file":"chunk-7WAV52EO.js","sourcesContent":["/**\n * Tenant selection — a dependency-free mutable leaf.\n *\n * Deliberately imports nothing but its own types, so it can be constructed\n * BEFORE any client and then shared by the identity, storage and GraphQL\n * clients without an import cycle:\n *\n * ```ts\n * const tenants = createTenantSelection();\n * const auth = createAuthClient({ baseUrl, tokenProvider, tenantProvider: tenants });\n * const storage = createStorageClient({ baseUrl, tokenProvider, tenantProvider: tenants });\n * tenants.set(tenantId); // both clients now scope to it, no reconstruction\n * ```\n */\n\nimport type { MPFRole } from './claims-types.js';\nimport type { MPFAuthStorage } from './storage.js';\n\n/**\n * The currently selected tenant.\n */\nexport interface TenantSelection {\n /** Selected tenant id, or `null` for the no-tenant (`drl`) path. */\n tenant: string | null;\n /**\n * Selected role. `undefined` lets the server apply that tenant's default\n * role (`tnts[].dfr`, else `tnts[].rls[0]`).\n */\n role?: MPFRole;\n}\n\n/**\n * A mutable, observable tenant selection.\n */\nexport interface TenantSelectionStore {\n /** Current selection. */\n get(): TenantSelection;\n /**\n * Select a tenant.\n *\n * Synchronous by design — selecting a tenant is a header change, so there is\n * no renew, no await and no network call.\n *\n * @param tenant - Tenant id, or `null` for the no-tenant path.\n * @param role - Optional role; omit to let the server apply the default.\n */\n set(tenant: string | null, role?: MPFRole): void;\n /** Reset to the no-tenant path. */\n clear(): void;\n /**\n * Observe changes. Returns an unsubscribe function.\n *\n * The listener is called only when the selection actually changes.\n */\n subscribe(listener: (selection: TenantSelection) => void): () => void;\n}\n\n/**\n * Persistence options for the tenant selection.\n *\n * The selected tenant IS persisted (unlike the access token). A tenant uuid the\n * user demonstrably has access to is not credential material, and losing the\n * selection on reload is a real UX regression: pick a tenant, refresh, land back\n * in the no-tenant state. Reading it synchronously at construction is also what\n * makes the provider's anti-flash hydration possible.\n *\n * Note this is a UX convenience only. A tampered value grants nothing — the\n * server re-derives tenant and role from the verified token on every request and\n * rejects a tenant the token does not hold.\n */\nexport interface TenantSelectionPersistence {\n /** Where to store it. Omit to disable persistence. */\n storage?: MPFAuthStorage;\n /** Storage key. @default 'mpf.tenant_selection' */\n storageKey?: string;\n}\n\nexport interface CreateTenantSelectionOptions extends TenantSelectionPersistence {\n /**\n * Initial selection. Takes precedence over a persisted value, so an app can\n * force a tenant (e.g. from a URL) without the stored one overriding it.\n */\n initial?: TenantSelection;\n}\n\nconst DEFAULT_SELECTION_KEY = 'mpf.tenant_selection';\n\nfunction readPersisted(\n storage: MPFAuthStorage | undefined,\n key: string,\n): TenantSelection | null {\n if (!storage) {\n return null;\n }\n\n try {\n const raw = storage.getItem(key);\n if (!raw) {\n return null;\n }\n const parsed = JSON.parse(raw) as Partial<TenantSelection>;\n if (typeof parsed.tenant !== 'string' || parsed.tenant === '') {\n return null;\n }\n return {\n tenant: parsed.tenant,\n ...(typeof parsed.role === 'string' && parsed.role !== '' ? { role: parsed.role } : {}),\n };\n } catch {\n // Corrupt or unreadable storage must never break client construction.\n return null;\n }\n}\n\n/**\n * Create a tenant selection store.\n *\n * Accepts either a bare initial selection (back-compatible) or an options\n * object enabling persistence.\n *\n * @example\n * ```ts\n * const tenants = createTenantSelection({ storage: createDefaultStorage() });\n * // survives a page reload\n * ```\n */\nexport function createTenantSelection(\n initialOrOptions?: TenantSelection | CreateTenantSelectionOptions,\n): TenantSelectionStore {\n const isOptions =\n initialOrOptions !== undefined &&\n !('tenant' in initialOrOptions);\n\n const options: CreateTenantSelectionOptions = isOptions\n ? (initialOrOptions as CreateTenantSelectionOptions)\n : { ...(initialOrOptions ? { initial: initialOrOptions as TenantSelection } : {}) };\n\n const storage = options.storage;\n const storageKey = options.storageKey ?? DEFAULT_SELECTION_KEY;\n const initial = options.initial ?? readPersisted(storage, storageKey) ?? undefined;\n\n let current: TenantSelection = {\n tenant: initial?.tenant ?? null,\n ...(initial?.role !== undefined ? { role: initial.role } : {}),\n };\n\n function persist(): void {\n if (!storage) {\n return;\n }\n try {\n if (current.tenant === null) {\n storage.removeItem(storageKey);\n } else {\n storage.setItem(storageKey, JSON.stringify(current));\n }\n } catch {\n // A full or blocked quota must not break tenant selection.\n }\n }\n\n const listeners = new Set<(selection: TenantSelection) => void>();\n\n function emit(): void {\n for (const listener of [...listeners]) {\n listener(current);\n }\n }\n\n return {\n get() {\n return current;\n },\n\n set(tenant, role) {\n if (current.tenant === tenant && current.role === role) {\n return;\n }\n current = { tenant, ...(role !== undefined ? { role } : {}) };\n persist();\n emit();\n },\n\n clear() {\n if (current.tenant === null && current.role === undefined) {\n return;\n }\n current = { tenant: null };\n persist();\n emit();\n },\n\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n","/**\n * Pluggable persistence for the session manager.\n *\n * Only the REFRESH token is persisted. The access token stays in memory: at a\n * 15-minute TTL, persisting it buys almost nothing and it is the more dangerous\n * of the two to leave sitting in `localStorage`.\n */\n\n/**\n * Minimal synchronous key/value store.\n *\n * Matches the `localStorage` shape so the browser default is a pass-through.\n */\nexport interface MPFAuthStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/**\n * An in-memory store. Used automatically when `window` is absent (Node, SSR,\n * the e2e suite) and available explicitly for tests.\n */\nexport function createMemoryStorage(): MPFAuthStorage {\n const map = new Map<string, string>();\n return {\n getItem: (key) => map.get(key) ?? null,\n setItem: (key, value) => {\n map.set(key, value);\n },\n removeItem: (key) => {\n map.delete(key);\n },\n };\n}\n\n/**\n * `sessionStorage` when it is usable, an in-memory store otherwise.\n *\n * Per-tab and cleared when the tab closes, which is what a redirect flow wants:\n * see `createOAuthStateStore`.\n */\nexport function createSessionStorage(): MPFAuthStorage {\n if (typeof window === 'undefined') {\n return createMemoryStorage();\n }\n\n try {\n const probe = '__mpf_probe__';\n window.sessionStorage.setItem(probe, probe);\n window.sessionStorage.removeItem(probe);\n return window.sessionStorage;\n } catch {\n return createMemoryStorage();\n }\n}\n\n/**\n * `localStorage` when it is usable, an in-memory store otherwise.\n *\n * Access is probed rather than assumed: Safari in private mode and blocked\n * third-party contexts expose `localStorage` but throw on write.\n */\nexport function createDefaultStorage(): MPFAuthStorage {\n if (typeof window === 'undefined') {\n return createMemoryStorage();\n }\n\n try {\n const probe = '__mpf_probe__';\n window.localStorage.setItem(probe, probe);\n window.localStorage.removeItem(probe);\n return window.localStorage;\n } catch {\n return createMemoryStorage();\n }\n}\n","/**\n * Cross-tab refresh coordination.\n *\n * ## The problem, precisely\n *\n * Multiple tabs of ONE browser share one login and read the same refresh token\n * from origin-scoped `localStorage`. The refresh token is single-use and\n * rotates, so if two tabs renew concurrently one of them loses and its token is\n * dead. Separate browsers, machines or devices get separate session rows and\n * separate refresh tokens, so they are not a concern here.\n *\n * With proactive refresh every tab's timer fires at roughly the same moment, so\n * this is contended by N tabs at once, not two.\n *\n * ## Mechanism\n *\n * `navigator.locks` (Web Locks) is a standardized cross-tab mutex and needs no\n * dependency on the primary path. Locks auto-release when a tab is destroyed,\n * so a closed tab cannot deadlock the others. A polyfill is loaded only where\n * the API is missing (pre-15.4 Safari, non-secure contexts).\n *\n * A lock ALONE is insufficient: N tabs would queue and then each renew in turn,\n * which is the exact problem the lock was supposed to solve. The session manager\n * therefore does double-checked locking — re-reading shared state INSIDE the\n * lock and adopting instead of renewing when another tab got there first.\n *\n * ## Fanout: BroadcastChannel, not localStorage\n *\n * The winner's fresh ACCESS token is broadcast in memory over a\n * `BroadcastChannel` rather than persisted, so losing tabs can adopt it without\n * a network call while the access token still never touches `localStorage`.\n * Where `BroadcastChannel` is unavailable, losers fall back to renewing with the\n * rotated refresh token — correct, just less efficient.\n *\n * Everything here no-ops when `window` is absent, so Node and the e2e suite are\n * unaffected.\n */\n\n/** A token pair shared between tabs. */\nexport interface TokenBroadcast {\n accessToken: string;\n refreshToken: string;\n}\n\n/** The subset of `LockManager` this module uses. */\nexport interface LockManagerLike {\n request(\n name: string,\n options: { signal?: AbortSignal },\n callback: () => Promise<unknown>,\n ): Promise<unknown>;\n}\n\n/** The subset of `BroadcastChannel` this module uses. */\nexport interface BroadcastChannelLike {\n postMessage(message: unknown): void;\n addEventListener(type: 'message', listener: (event: { data: unknown }) => void): void;\n removeEventListener(type: 'message', listener: (event: { data: unknown }) => void): void;\n close(): void;\n}\n\n/** How the coordinator is providing mutual exclusion. */\nexport type CrossTabMode = 'native' | 'polyfill' | 'none';\n\nexport interface CrossTabCoordinator {\n /** How mutual exclusion is being provided. */\n readonly mode: CrossTabMode;\n /** Run `fn` holding the cross-tab refresh lock. */\n withLock<T>(fn: () => Promise<T>): Promise<T>;\n /** Tell other tabs about a fresh token pair. */\n publish(message: TokenBroadcast): void;\n /** Observe other tabs' fresh token pairs. Returns an unsubscribe function. */\n subscribe(listener: (message: TokenBroadcast) => void): () => void;\n /** Release resources. */\n close(): void;\n}\n\nexport interface CrossTabOptions {\n /** Lock name. @default 'mpf.refresh' */\n lockName?: string;\n /** BroadcastChannel name. @default 'mpf.auth' */\n channelName?: string;\n /**\n * How long to wait for the lock before giving up and proceeding without it.\n * @default 5000\n */\n lockTimeoutMs?: number;\n /** Injectable lock manager. Defaults to `navigator.locks`. For tests. */\n locks?: LockManagerLike | null;\n /**\n * Loads a Web Locks polyfill when the native API is absent. Resolve `null` to\n * decline. The default declines — no polyfill is bundled, keeping the primary\n * path dependency-free; a consumer targeting old Safari supplies one here.\n */\n loadPolyfill?: () => Promise<LockManagerLike | null>;\n /** Injectable channel factory. Defaults to `BroadcastChannel`. For tests. */\n channelFactory?: (name: string) => BroadcastChannelLike | null;\n}\n\nfunction defaultLocks(): LockManagerLike | null {\n if (typeof navigator === 'undefined') {\n return null;\n }\n const locks = (navigator as unknown as { locks?: LockManagerLike }).locks;\n return locks ?? null;\n}\n\nfunction defaultChannel(name: string): BroadcastChannelLike | null {\n if (typeof BroadcastChannel === 'undefined') {\n return null;\n }\n try {\n return new BroadcastChannel(name) as unknown as BroadcastChannelLike;\n } catch {\n return null;\n }\n}\n\n/**\n * An abort signal that fires after `ms`.\n *\n * Uses `AbortSignal.timeout` where available and falls back to a controller,\n * because the fallback also has to work under fake timers in tests.\n */\nfunction timeoutSignal(ms: number): AbortSignal {\n const controller = new AbortController();\n setTimeout(() => controller.abort(new Error(`Lock wait timed out after ${ms}ms`)), ms);\n return controller.signal;\n}\n\n/**\n * Apply symmetric jitter to a delay.\n *\n * Without this, N tabs computed the same proactive-renew deadline from the same\n * token and all wake on the same millisecond, then serialize through the lock\n * for no reason. Result is clamped at 0.\n *\n * @param baseMs - The unjittered delay.\n * @param jitterMs - Maximum deviation in either direction.\n * @param random - Injectable `Math.random` for tests.\n */\nexport function applyJitter(baseMs: number, jitterMs: number, random: () => number = Math.random): number {\n if (jitterMs <= 0) {\n return Math.max(0, baseMs);\n }\n const offset = (random() * 2 - 1) * jitterMs;\n return Math.max(0, baseMs + offset);\n}\n\n/**\n * Create a cross-tab coordinator.\n *\n * Resolution order for the lock: injected `locks` → `navigator.locks` →\n * `loadPolyfill()` → none (run without coordination, correct in a\n * single-context environment like Node).\n */\nexport function createCrossTabCoordinator(options: CrossTabOptions = {}): CrossTabCoordinator {\n const {\n lockName = 'mpf.refresh',\n channelName = 'mpf.auth',\n lockTimeoutMs = 5000,\n loadPolyfill = async () => null,\n channelFactory = defaultChannel,\n } = options;\n\n const browser = typeof window !== 'undefined';\n\n // `locks: null` explicitly declines; `undefined` means \"detect\".\n const injected = options.locks;\n let locks: LockManagerLike | null = injected !== undefined ? injected : browser ? defaultLocks() : null;\n let mode: CrossTabMode = locks ? (injected !== undefined ? 'native' : 'native') : 'none';\n\n // Polyfill resolution is async, so it is kicked off once and awaited by the\n // first withLock() call that needs it.\n let polyfillPromise: Promise<void> | null = null;\n if (!locks && browser && injected === undefined) {\n polyfillPromise = loadPolyfill()\n .then((loaded) => {\n if (loaded) {\n locks = loaded;\n mode = 'polyfill';\n }\n })\n .catch(() => {\n // No polyfill available; stay uncoordinated rather than failing auth.\n });\n }\n\n const channel = browser ? channelFactory(channelName) : null;\n const listeners = new Set<(message: TokenBroadcast) => void>();\n\n const onMessage = (event: { data: unknown }): void => {\n const data = event.data as Partial<TokenBroadcast> | null;\n if (\n !data ||\n typeof data.accessToken !== 'string' ||\n typeof data.refreshToken !== 'string'\n ) {\n return;\n }\n const message: TokenBroadcast = {\n accessToken: data.accessToken,\n refreshToken: data.refreshToken,\n };\n for (const listener of [...listeners]) {\n listener(message);\n }\n };\n\n channel?.addEventListener('message', onMessage);\n\n return {\n get mode() {\n return mode;\n },\n\n async withLock<T>(fn: () => Promise<T>): Promise<T> {\n if (polyfillPromise) {\n await polyfillPromise;\n polyfillPromise = null;\n }\n\n if (!locks) {\n // No cross-tab contention possible (or no API to use). Run directly.\n return fn();\n }\n\n let result: T;\n let ran = false;\n\n try {\n await locks.request(lockName, { signal: timeoutSignal(lockTimeoutMs) }, async () => {\n result = await fn();\n ran = true;\n });\n } catch (error) {\n if (ran) {\n // fn() itself threw, or the lock released oddly after running it.\n throw error;\n }\n // We never got the lock — most likely a frozen tab is holding it\n // mid-renew. Proceeding unlocked risks a duplicate renew, which the\n // server reports as REFRESH_TOKEN_SUPERSEDED and the session manager\n // recovers from. Hanging here would leave the app with no token at all,\n // which is strictly worse.\n return fn();\n }\n\n // `ran` is true whenever the callback completed; TS cannot see that.\n return result!;\n },\n\n publish(message: TokenBroadcast) {\n channel?.postMessage(message);\n },\n\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n\n close() {\n listeners.clear();\n channel?.removeEventListener('message', onMessage);\n channel?.close();\n },\n };\n}\n","/**\n * Session manager — single access token, single refresh token.\n *\n * There is deliberately no per-tenant token map, no `renewScoped`, and no\n * scoped/general token split: tenant is a request header now, so all of that\n * machinery is gone by construction rather than by choice.\n *\n * What remains, and why:\n * - **Coalescing** — the refresh token is single-use, so concurrent renewals\n * must serialize into one network call or they kill each other.\n * - **Proactive timer** — renew before expiry so requests never pay for it.\n * - **Wake handling** — `setTimeout` does not fire in frozen or discarded tabs,\n * so the timer alone is not a safety net. See `handleWake`.\n * - **Cross-tab lock** — N tabs of one browser share one refresh token.\n */\n\nimport { MPFAuthError } from '../client/core/errors.js';\nimport { tryDecodeAccessToken, isExpired, tokenLifetimeSeconds } from './claims.js';\nimport type { MPFClaims } from './claims-types.js';\nimport { DeadSessionError } from './errors.js';\nimport { createDefaultStorage, type MPFAuthStorage } from './storage.js';\nimport {\n applyJitter,\n createCrossTabCoordinator,\n type CrossTabCoordinator,\n type TokenBroadcast,\n} from './cross-tab.js';\n\n/** Outcome of {@link SessionManager.signOut}. */\nexport interface SignOutResult {\n /** Whether the server-side revoke succeeded. */\n revoked: boolean;\n /**\n * Why the revoke failed, when it did. Local state is cleared regardless, so\n * this is informational — surface it if you want to tell the user the session\n * may still be live elsewhere.\n */\n error?: Error;\n}\n\n/** A token pair as returned by `POST /v0/sessions/renew`. */\nexport interface TokenPair {\n access_token: string;\n refresh_token: string;\n}\n\n/**\n * The server's code when a renew loses the rotation race — another tab renewed\n * with the same refresh token first.\n *\n * This is recoverable: the winner's token is valid, so adopt it instead of\n * declaring the session dead.\n */\nexport const REFRESH_TOKEN_SUPERSEDED = 'REFRESH_TOKEN_SUPERSEDED';\n\n/** Seconds of headroom before expiry at which a token counts as stale. */\nconst DEFAULT_BUFFER_SECONDS = 120;\n\n/** Floor between two network renewals. */\nconst DEFAULT_MIN_RENEW_INTERVAL_MS = 5_000;\n\n/** Proactive-timer jitter, in milliseconds, applied symmetrically. */\nconst DEFAULT_JITTER_MS = 30_000;\n\nexport interface SessionManagerOptions {\n /**\n * Performs the network renewal. Injected rather than taking an `AuthClientV0`\n * so this module stays a leaf and cannot form an import cycle with the\n * identity client that consumes it.\n */\n renew: (refreshToken: string) => Promise<TokenPair>;\n\n /**\n * Revokes the session server-side. Wire to\n * `(refreshToken) => authClient.revokeSession({ refresh_token: refreshToken })`.\n *\n * Injected for the same reason as {@link renew}: it keeps `src/auth/` a leaf.\n *\n * Supplying it is what makes {@link SessionManager.signOut} revoke rather than\n * only clearing locally. Without it, `signOut()` still clears — a sign-out\n * button must always sign the user out — but the refresh token stays valid\n * server-side until it expires.\n */\n revoke?: (refreshToken: string) => Promise<unknown>;\n\n /** Where the refresh token is persisted. @default localStorage, else memory */\n storage?: MPFAuthStorage;\n\n /** Storage key for the refresh token. @default 'mpf.refresh_token' */\n storageKey?: string;\n\n /**\n * Seconds before expiry at which a token is considered stale.\n *\n * @default 120\n *\n * 120 rather than 60: at a 900s TTL, a request *starting* at `exp − 61s`\n * passes a 60s freshness check and then carries a token that dies mid-flight,\n * which storage uploads routinely will. The buffer must cover renew latency\n * plus the longest in-flight request, and browser clock skew is unbounded.\n *\n * Clamped per token — see {@link SessionManager.effectiveBufferSeconds}.\n */\n bufferSeconds?: number;\n\n /**\n * Minimum gap between two network renewals. Second line of defence against a\n * renew loop caused by a misconfigured server TTL.\n * @default 5000\n */\n minRenewIntervalMs?: number;\n\n /** Symmetric jitter on the proactive timer. @default 30000 */\n jitterMs?: number;\n\n /** Called when the session cannot be renewed and the user must log in again. */\n onDeadSession?: (error: DeadSessionError) => void;\n\n /** Called whenever the in-memory token pair changes. */\n onTokensChanged?: (tokens: TokenPair | null) => void;\n\n /**\n * Cross-tab coordinator. Pass `null` to disable coordination entirely.\n * @default createCrossTabCoordinator()\n */\n crossTab?: CrossTabCoordinator | null;\n\n /** Injectable clock, in ms. @default Date.now */\n now?: () => number;\n\n /** Injectable randomness for jitter. @default Math.random */\n random?: () => number;\n\n /**\n * Whether to attach `visibilitychange` / `online` listeners.\n * @default true when `window` exists\n */\n listenToWake?: boolean;\n}\n\n/**\n * Manages one session's tokens.\n */\nexport class SessionManager {\n private accessToken: string | null = null;\n private refreshToken: string | null = null;\n\n /** In-flight renewal, for coalescing. */\n private inflight: Promise<string> | null = null;\n\n /**\n * Gate that `getAccessToken()` awaits. Set by the wake path so that requests\n * queued during a wake renewal block behind it instead of shipping a token\n * that is already dead.\n */\n private wakeGate: Promise<void> | null = null;\n\n private timer: ReturnType<typeof setTimeout> | null = null;\n private lastRenewAt = 0;\n private destroyed = false;\n /**\n * Incremented on every `clear()`. A renewal captures it before its network\n * call and refuses to adopt a result from a superseded generation, so signing\n * out (or clearing) mid-renew cannot be undone by the renewal completing.\n */\n private generation = 0;\n\n private readonly storage: MPFAuthStorage;\n private readonly storageKey: string;\n private readonly bufferSeconds: number;\n private readonly minRenewIntervalMs: number;\n private readonly jitterMs: number;\n private readonly now: () => number;\n private readonly random: () => number;\n private readonly crossTab: CrossTabCoordinator | null;\n private readonly unsubscribeBroadcast: (() => void) | null = null;\n private readonly wakeListening: boolean;\n\n constructor(private readonly options: SessionManagerOptions) {\n this.storage = options.storage ?? createDefaultStorage();\n this.storageKey = options.storageKey ?? 'mpf.refresh_token';\n this.bufferSeconds = options.bufferSeconds ?? DEFAULT_BUFFER_SECONDS;\n this.minRenewIntervalMs = options.minRenewIntervalMs ?? DEFAULT_MIN_RENEW_INTERVAL_MS;\n this.jitterMs = options.jitterMs ?? DEFAULT_JITTER_MS;\n this.now = options.now ?? (() => Date.now());\n this.random = options.random ?? Math.random;\n\n this.crossTab =\n options.crossTab === null ? null : (options.crossTab ?? createCrossTabCoordinator());\n\n // Adopt a sibling tab's fresh tokens without a network call.\n if (this.crossTab) {\n this.unsubscribeBroadcast = this.crossTab.subscribe((message) => {\n this.adopt(message, { rebroadcast: false });\n });\n }\n\n this.refreshToken = this.storage.getItem(this.storageKey);\n\n this.wakeListening = options.listenToWake ?? typeof window !== 'undefined';\n if (this.wakeListening && typeof window !== 'undefined') {\n window.addEventListener('visibilitychange', this.onWakeEvent);\n window.addEventListener('online', this.onWakeEvent);\n }\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n /**\n * Adopt a freshly minted token pair (after sign-in, or after any flow that\n * changes memberships).\n */\n setTokens(tokens: TokenPair): void {\n this.adopt(\n { accessToken: tokens.access_token, refreshToken: tokens.refresh_token },\n { rebroadcast: true },\n );\n }\n\n /**\n * A usable access token, renewing first if necessary.\n *\n * Returns `null` only when there is no session at all.\n */\n async getAccessToken(): Promise<string | null> {\n // Block behind a wake renewal rather than shipping a dead token.\n if (this.wakeGate) {\n await this.wakeGate;\n }\n\n if (!this.accessToken) {\n return this.refreshToken ? this.renew() : null;\n }\n\n if (this.isStale()) {\n return this.renew();\n }\n\n return this.accessToken;\n }\n\n /** The current access token without renewing. */\n peekAccessToken(): string | null {\n return this.accessToken;\n }\n\n /** Decoded (UNVERIFIED — UX only) claims of the current access token. */\n getClaims(): MPFClaims | null {\n return tryDecodeAccessToken(this.accessToken);\n }\n\n /**\n * Renew the session. Coalesced: concurrent callers share one network call,\n * which is mandatory because the refresh token is single-use.\n */\n async renew(): Promise<string> {\n if (this.inflight) {\n return this.inflight;\n }\n\n this.inflight = this.renewGuarded().finally(() => {\n this.inflight = null;\n });\n\n return this.inflight;\n }\n\n /**\n * Sign out: revoke the session server-side, then clear it locally.\n *\n * **Never throws, and always clears.** A user who clicked sign-out must end up\n * signed out locally whatever the network did — a UI that refuses to log out\n * is worse than a token that outlives its client, and the failed revoke leaves\n * that token behind either way. The outcome is reported in the return value so\n * an app can say \"signed out, but we couldn't reach the server\" rather than\n * having to guess.\n *\n * Order is load-bearing: `revokeSession` is an *authenticated* endpoint, so\n * the revoke must go out **before** the access token is cleared. Reversing it\n * silently sends an unauthenticated request that cannot succeed.\n *\n * Idempotent: with no session, it is a no-op reporting `revoked: false`.\n *\n * This exists so consumers never need the raw refresh token. Reaching into\n * storage for it — `localStorage.getItem('mpf.refresh_token')` — hardcodes the\n * default key and breaks for anyone using a custom storage or key.\n */\n async signOut(): Promise<SignOutResult> {\n const refreshToken = this.refreshToken;\n const revoker = this.options.revoke;\n\n let revoked = false;\n let error: Error | undefined;\n\n if (refreshToken && revoker) {\n try {\n await revoker(refreshToken);\n revoked = true;\n } catch (caught) {\n error = caught instanceof Error ? caught : new Error(String(caught));\n }\n }\n\n // Unconditional: see the note above.\n this.clear();\n\n return { revoked, ...(error ? { error } : {}) };\n }\n\n /**\n * Forget the session locally. Stops timers and invalidates any in-flight\n * renewal so it cannot resurrect the session after it resolves.\n *\n * Prefer {@link signOut} for a user-initiated sign-out: this leaves the\n * refresh token valid server-side.\n */\n clear(): void {\n // Bump first: a renewal already in flight checks this before adopting its\n // result, so a proactive renew that resolves after sign-out cannot put the\n // tokens back. Without it, \"timer fires, user clicks sign out, renew\n // resolves\" leaves the user signed in again.\n this.generation += 1;\n\n this.accessToken = null;\n this.refreshToken = null;\n this.storage.removeItem(this.storageKey);\n this.cancelTimer();\n this.options.onTokensChanged?.(null);\n }\n\n /** Detach listeners and release the coordinator. */\n destroy(): void {\n this.destroyed = true;\n this.cancelTimer();\n this.unsubscribeBroadcast?.();\n if (this.wakeListening && typeof window !== 'undefined') {\n window.removeEventListener('visibilitychange', this.onWakeEvent);\n window.removeEventListener('online', this.onWakeEvent);\n }\n this.crossTab?.close();\n }\n\n /**\n * The buffer actually in force for the current token.\n *\n * The configured buffer is clamped to just under half the token's lifetime:\n * if the buffer were ≥ half the lifetime, a freshly minted token would be\n * \"stale\" the instant it arrived and the manager would renew-loop, hammering\n * `/sessions/renew` forever. A 60s-TTL token therefore gets a 29s buffer, not\n * the configured 120s.\n *\n * Falls back to the configured value when the token carries no `iat` and the\n * lifetime is unknowable.\n */\n effectiveBufferSeconds(): number {\n const claims = this.getClaims();\n if (!claims) {\n return this.bufferSeconds;\n }\n\n const lifetime = tokenLifetimeSeconds(claims);\n if (lifetime === undefined) {\n return this.bufferSeconds;\n }\n\n const ceiling = Math.max(0, Math.floor(lifetime / 2) - 1);\n return Math.min(this.bufferSeconds, ceiling);\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n private isStale(): boolean {\n const claims = this.getClaims();\n if (!claims) {\n // Undecodable token: treat as stale so we renew rather than send garbage.\n return true;\n }\n return isExpired(claims, this.effectiveBufferSeconds(), this.now());\n }\n\n private adopt(tokens: TokenBroadcast, opts: { rebroadcast: boolean }): void {\n this.accessToken = tokens.accessToken;\n this.refreshToken = tokens.refreshToken;\n this.storage.setItem(this.storageKey, tokens.refreshToken);\n\n this.scheduleProactiveRenew();\n\n this.options.onTokensChanged?.({\n access_token: tokens.accessToken,\n refresh_token: tokens.refreshToken,\n });\n\n if (opts.rebroadcast) {\n this.crossTab?.publish(tokens);\n }\n }\n\n private async renewGuarded(): Promise<string> {\n if (!this.refreshToken) {\n return this.die(new DeadSessionError('No refresh token available'));\n }\n\n // Floor between network renewals. If we still hold a token, hand it back\n // rather than renewing again inside the floor.\n const sinceLast = this.now() - this.lastRenewAt;\n if (this.lastRenewAt !== 0 && sinceLast < this.minRenewIntervalMs && this.accessToken) {\n return this.accessToken;\n }\n\n const startedWith = this.refreshToken;\n\n const run = async (): Promise<string> => {\n // Double-checked locking: re-read shared state INSIDE the lock. Without\n // this, N tabs queue on the lock and then each renew in turn, which is\n // exactly what the lock was meant to prevent.\n const persisted = this.storage.getItem(this.storageKey);\n if (persisted && persisted !== startedWith) {\n // Another tab renewed while we waited. Its access token arrives over\n // the broadcast channel, so we may already hold a fresh one.\n this.refreshToken = persisted;\n if (this.accessToken && !this.isStale()) {\n return this.accessToken;\n }\n }\n\n return this.networkRenew();\n };\n\n return this.crossTab ? this.crossTab.withLock(run) : run();\n }\n\n private async networkRenew(): Promise<string> {\n const refreshToken = this.refreshToken;\n if (!refreshToken) {\n return this.die(new DeadSessionError('No refresh token available'));\n }\n\n this.lastRenewAt = this.now();\n const generation = this.generation;\n\n let pair: TokenPair;\n try {\n pair = await this.options.renew(refreshToken);\n } catch (error) {\n // Losing the rotation race is recoverable, not fatal: a sibling tab\n // renewed first and its token is valid. Adopt rather than logging out.\n if (isSupersededError(error)) {\n const adopted = await this.adoptAfterSuperseded();\n if (adopted) {\n return adopted;\n }\n }\n\n return this.die(\n new DeadSessionError(\n 'Session renewal failed',\n error instanceof Error ? error : undefined,\n ),\n );\n }\n\n if (generation !== this.generation) {\n // The session was cleared while this renewal was in flight (sign-out being\n // the realistic case). Adopting now would sign the user back in.\n return pair.access_token;\n }\n\n this.adopt(\n { accessToken: pair.access_token, refreshToken: pair.refresh_token },\n { rebroadcast: true },\n );\n\n return pair.access_token;\n }\n\n /**\n * After a REFRESH_TOKEN_SUPERSEDED, take the winner's tokens.\n *\n * The winner's access token arrives over the broadcast channel; its refresh\n * token is in storage. If neither is usable there is nothing to adopt and the\n * caller declares the session dead.\n */\n private async adoptAfterSuperseded(): Promise<string | null> {\n const persisted = this.storage.getItem(this.storageKey);\n if (persisted) {\n this.refreshToken = persisted;\n }\n\n if (this.accessToken && !this.isStale()) {\n return this.accessToken;\n }\n\n return null;\n }\n\n private die(error: DeadSessionError): never {\n this.clear();\n this.options.onDeadSession?.(error);\n throw error;\n }\n\n private cancelTimer(): void {\n if (this.timer !== null) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n }\n\n /**\n * Schedule a renewal shortly before the token goes stale.\n *\n * Jittered so that N tabs computing the same deadline from the same token do\n * not all wake on the same millisecond.\n */\n private scheduleProactiveRenew(): void {\n this.cancelTimer();\n\n if (this.destroyed) {\n return;\n }\n\n const claims = this.getClaims();\n if (!claims) {\n return;\n }\n\n const staleAtMs = (claims.exp - this.effectiveBufferSeconds()) * 1000;\n const base = Math.max(0, staleAtMs - this.now());\n const delay = Math.max(\n this.minRenewIntervalMs,\n applyJitter(base, this.jitterMs, this.random),\n );\n\n this.timer = setTimeout(() => {\n this.timer = null;\n void this.renew().catch(() => {\n // renew() already reported via onDeadSession.\n });\n }, delay);\n }\n\n private readonly onWakeEvent = (): void => {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') {\n return;\n }\n void this.handleWake();\n };\n\n /**\n * Wake path — the real safety net.\n *\n * `setTimeout` does not fire in frozen or discarded tabs (Chrome tab\n * freezing, iOS Safari), so a tab backgrounded for 20 minutes wakes holding a\n * guaranteed-dead token with its timer never having run.\n *\n * Merely rescheduling here is wrong: the delay computes negative, becomes\n * `setTimeout(0)`, and then races whatever the app fires on focus. Instead\n * renew immediately and publish a gate that `getAccessToken()` awaits, so\n * requests triggered by the same focus event queue behind the renewal rather\n * than each eating a 401.\n */\n private async handleWake(): Promise<void> {\n if (this.destroyed || !this.refreshToken) {\n return;\n }\n\n if (this.accessToken && !this.isStale()) {\n // Token survived the nap; the timer may still be stale after a freeze.\n this.scheduleProactiveRenew();\n return;\n }\n\n if (this.wakeGate) {\n return;\n }\n\n let release!: () => void;\n this.wakeGate = new Promise<void>((resolve) => {\n release = resolve;\n });\n\n try {\n await this.renew();\n } catch {\n // Reported through onDeadSession already.\n } finally {\n const gate = release;\n this.wakeGate = null;\n gate();\n }\n }\n}\n\n/**\n * Whether an error is the server's \"another tab renewed first\" signal.\n *\n * Checks `serverCode` because `MPFAuthError.code` is derived from the error\n * classification and collapses every 401 to `UNAUTHORIZED`.\n */\nexport function isSupersededError(error: unknown): boolean {\n if (error instanceof MPFAuthError) {\n return error.serverCode === REFRESH_TOKEN_SUPERSEDED;\n }\n if (error && typeof error === 'object' && 'code' in error) {\n return (error as { code?: unknown }).code === REFRESH_TOKEN_SUPERSEDED;\n }\n return false;\n}\n\n/**\n * Create a session manager.\n */\nexport function createSessionManager(options: SessionManagerOptions): SessionManager {\n return new SessionManager(options);\n}\n"]}
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { decodeJwt } from 'jose';
|
|
2
|
+
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __export = (target, all) => {
|
|
5
|
+
for (var name in all)
|
|
6
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// src/client/core/errors.ts
|
|
10
|
+
var MPFError = class extends Error {
|
|
11
|
+
/** Machine-readable error code */
|
|
12
|
+
code;
|
|
13
|
+
/** HTTP status code if applicable */
|
|
14
|
+
status;
|
|
15
|
+
/** Additional error details from the API */
|
|
16
|
+
details;
|
|
17
|
+
constructor(message, code, status, details) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "MPFError";
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.status = status;
|
|
22
|
+
this.details = details;
|
|
23
|
+
if (Error.captureStackTrace) {
|
|
24
|
+
Error.captureStackTrace(this, this.constructor);
|
|
25
|
+
}
|
|
26
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Convert error to a plain object for logging/serialization
|
|
30
|
+
*/
|
|
31
|
+
toJSON() {
|
|
32
|
+
return {
|
|
33
|
+
name: this.name,
|
|
34
|
+
message: this.message,
|
|
35
|
+
code: this.code,
|
|
36
|
+
status: this.status,
|
|
37
|
+
details: this.details
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var MPFNetworkError = class extends MPFError {
|
|
42
|
+
/** The original error that caused the network failure */
|
|
43
|
+
cause;
|
|
44
|
+
constructor(message, cause) {
|
|
45
|
+
super(message, "NETWORK_ERROR");
|
|
46
|
+
this.name = "MPFNetworkError";
|
|
47
|
+
this.cause = cause;
|
|
48
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
49
|
+
}
|
|
50
|
+
toJSON() {
|
|
51
|
+
return {
|
|
52
|
+
...super.toJSON(),
|
|
53
|
+
cause: this.cause?.message
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var MPFAPIError = class extends MPFError {
|
|
58
|
+
/** The raw response body if available */
|
|
59
|
+
body;
|
|
60
|
+
constructor(message, code, status, details, body) {
|
|
61
|
+
super(message, code, status, details);
|
|
62
|
+
this.name = "MPFAPIError";
|
|
63
|
+
this.body = body;
|
|
64
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
65
|
+
}
|
|
66
|
+
toJSON() {
|
|
67
|
+
return {
|
|
68
|
+
...super.toJSON(),
|
|
69
|
+
body: this.body
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var MPFValidationError = class extends MPFError {
|
|
74
|
+
/** Field-specific validation errors */
|
|
75
|
+
fieldErrors;
|
|
76
|
+
constructor(message, details, fieldErrors) {
|
|
77
|
+
super(message, "VALIDATION_ERROR", 400, details);
|
|
78
|
+
this.name = "MPFValidationError";
|
|
79
|
+
this.fieldErrors = fieldErrors;
|
|
80
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
81
|
+
}
|
|
82
|
+
toJSON() {
|
|
83
|
+
return {
|
|
84
|
+
...super.toJSON(),
|
|
85
|
+
fieldErrors: this.fieldErrors
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var MPFAuthError = class extends MPFError {
|
|
90
|
+
/** The type of auth error (e.g., 'unauthorized', 'forbidden', 'token_expired') */
|
|
91
|
+
authErrorType;
|
|
92
|
+
/**
|
|
93
|
+
* The machine-readable code the server actually sent, when it sent one.
|
|
94
|
+
*
|
|
95
|
+
* `code` is derived from {@link authErrorType} and therefore collapses every
|
|
96
|
+
* 401 to `UNAUTHORIZED`, which loses information the server deliberately
|
|
97
|
+
* provides — `REFRESH_TOKEN_SUPERSEDED` being the case that matters: it means
|
|
98
|
+
* "another tab renewed first", which is recoverable by adopting that tab's
|
|
99
|
+
* token, and must not be treated as a dead session.
|
|
100
|
+
*
|
|
101
|
+
* Kept as a separate field rather than overwriting `code` so that existing
|
|
102
|
+
* consumers switching on `code` are unaffected.
|
|
103
|
+
*/
|
|
104
|
+
serverCode;
|
|
105
|
+
constructor(message, authErrorType, status = 401, details, serverCode) {
|
|
106
|
+
const code = authErrorType.toUpperCase();
|
|
107
|
+
super(message, code, status, details);
|
|
108
|
+
this.name = "MPFAuthError";
|
|
109
|
+
this.authErrorType = authErrorType;
|
|
110
|
+
this.serverCode = serverCode;
|
|
111
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Check if the error is due to an expired token
|
|
115
|
+
*/
|
|
116
|
+
isTokenExpired() {
|
|
117
|
+
return this.authErrorType === "token_expired";
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check if the error is a 403 Forbidden
|
|
121
|
+
*/
|
|
122
|
+
isForbidden() {
|
|
123
|
+
return this.authErrorType === "forbidden";
|
|
124
|
+
}
|
|
125
|
+
toJSON() {
|
|
126
|
+
return {
|
|
127
|
+
...super.toJSON(),
|
|
128
|
+
authErrorType: this.authErrorType,
|
|
129
|
+
serverCode: this.serverCode
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
var MPFTimeoutError = class extends MPFError {
|
|
134
|
+
/** The timeout duration in milliseconds */
|
|
135
|
+
timeoutMs;
|
|
136
|
+
constructor(message, timeoutMs) {
|
|
137
|
+
super(message, "TIMEOUT_ERROR");
|
|
138
|
+
this.name = "MPFTimeoutError";
|
|
139
|
+
this.timeoutMs = timeoutMs;
|
|
140
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
141
|
+
}
|
|
142
|
+
toJSON() {
|
|
143
|
+
return {
|
|
144
|
+
...super.toJSON(),
|
|
145
|
+
timeoutMs: this.timeoutMs
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
function isMPFError(error) {
|
|
150
|
+
return error instanceof MPFError;
|
|
151
|
+
}
|
|
152
|
+
function isMPFAuthError(error) {
|
|
153
|
+
return error instanceof MPFAuthError;
|
|
154
|
+
}
|
|
155
|
+
function isMPFAPIError(error) {
|
|
156
|
+
return error instanceof MPFAPIError;
|
|
157
|
+
}
|
|
158
|
+
function isMPFNetworkError(error) {
|
|
159
|
+
return error instanceof MPFNetworkError;
|
|
160
|
+
}
|
|
161
|
+
function isMPFValidationError(error) {
|
|
162
|
+
return error instanceof MPFValidationError;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/auth/errors.ts
|
|
166
|
+
var MPFClaimsError = class extends MPFError {
|
|
167
|
+
constructor(message, code = "CLAIMS_INVALID", details) {
|
|
168
|
+
super(message, code, void 0, details);
|
|
169
|
+
this.name = "MPFClaimsError";
|
|
170
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
var MPFUnsupportedClaimsVersionError = class extends MPFClaimsError {
|
|
174
|
+
/** The `v` value found in the token, or null when it was absent/non-numeric. */
|
|
175
|
+
version;
|
|
176
|
+
constructor(version) {
|
|
177
|
+
super(
|
|
178
|
+
version === null ? "Access token has no claims version (`v`)" : `Unsupported access token claims version: ${version}`,
|
|
179
|
+
"CLAIMS_UNSUPPORTED_VERSION",
|
|
180
|
+
{ version }
|
|
181
|
+
);
|
|
182
|
+
this.name = "MPFUnsupportedClaimsVersionError";
|
|
183
|
+
this.version = version;
|
|
184
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
185
|
+
}
|
|
186
|
+
toJSON() {
|
|
187
|
+
return { ...super.toJSON(), version: this.version };
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
var DeadSessionError = class extends MPFError {
|
|
191
|
+
/** The underlying error that killed the session, when there was one. */
|
|
192
|
+
cause;
|
|
193
|
+
constructor(message = "Session is no longer valid", cause) {
|
|
194
|
+
super(message, "DEAD_SESSION", 401);
|
|
195
|
+
this.name = "DeadSessionError";
|
|
196
|
+
this.cause = cause;
|
|
197
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
198
|
+
}
|
|
199
|
+
toJSON() {
|
|
200
|
+
return { ...super.toJSON(), cause: this.cause?.message };
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
function isDeadSession(error) {
|
|
204
|
+
return error instanceof DeadSessionError;
|
|
205
|
+
}
|
|
206
|
+
var CLAIMS_CACHE_MAX = 5;
|
|
207
|
+
var claimsCache = /* @__PURE__ */ new Map();
|
|
208
|
+
function cacheGet(token) {
|
|
209
|
+
const hit = claimsCache.get(token);
|
|
210
|
+
if (hit === void 0) {
|
|
211
|
+
return void 0;
|
|
212
|
+
}
|
|
213
|
+
claimsCache.delete(token);
|
|
214
|
+
claimsCache.set(token, hit);
|
|
215
|
+
return hit;
|
|
216
|
+
}
|
|
217
|
+
function cachePut(token, claims) {
|
|
218
|
+
if (claimsCache.has(token)) {
|
|
219
|
+
claimsCache.delete(token);
|
|
220
|
+
}
|
|
221
|
+
claimsCache.set(token, claims);
|
|
222
|
+
while (claimsCache.size > CLAIMS_CACHE_MAX) {
|
|
223
|
+
const oldest = claimsCache.keys().next();
|
|
224
|
+
if (oldest.done) {
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
claimsCache.delete(oldest.value);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function clearClaimsCache() {
|
|
231
|
+
claimsCache.clear();
|
|
232
|
+
}
|
|
233
|
+
function requireString(payload, key) {
|
|
234
|
+
const value = payload[key];
|
|
235
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
236
|
+
throw new MPFClaimsError(`Claim "${key}" must be a non-empty string`);
|
|
237
|
+
}
|
|
238
|
+
return value;
|
|
239
|
+
}
|
|
240
|
+
function requireNumber(payload, key) {
|
|
241
|
+
const value = payload[key];
|
|
242
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
243
|
+
throw new MPFClaimsError(`Claim "${key}" must be a finite number`);
|
|
244
|
+
}
|
|
245
|
+
return value;
|
|
246
|
+
}
|
|
247
|
+
function optionalNumber(payload, key) {
|
|
248
|
+
const value = payload[key];
|
|
249
|
+
if (value === void 0) {
|
|
250
|
+
return void 0;
|
|
251
|
+
}
|
|
252
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
253
|
+
throw new MPFClaimsError(`Claim "${key}" must be a finite number when present`);
|
|
254
|
+
}
|
|
255
|
+
return value;
|
|
256
|
+
}
|
|
257
|
+
function optionalString(payload, key) {
|
|
258
|
+
const value = payload[key];
|
|
259
|
+
if (value === void 0 || value === null || value === "") {
|
|
260
|
+
return void 0;
|
|
261
|
+
}
|
|
262
|
+
if (typeof value !== "string") {
|
|
263
|
+
throw new MPFClaimsError(`Claim "${key}" must be a string when present`);
|
|
264
|
+
}
|
|
265
|
+
return value;
|
|
266
|
+
}
|
|
267
|
+
function requireTenantList(payload) {
|
|
268
|
+
const value = payload["tnts"];
|
|
269
|
+
if (value === void 0 || value === null) {
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
if (!Array.isArray(value)) {
|
|
273
|
+
throw new MPFClaimsError('Claim "tnts" must be an array when present');
|
|
274
|
+
}
|
|
275
|
+
return value.map((entry, i) => {
|
|
276
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
277
|
+
throw new MPFClaimsError(`Claim "tnts[${i}]" must be an object`);
|
|
278
|
+
}
|
|
279
|
+
const record = entry;
|
|
280
|
+
const id = record["id"];
|
|
281
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
282
|
+
throw new MPFClaimsError(`Claim "tnts[${i}].id" must be a non-empty string`);
|
|
283
|
+
}
|
|
284
|
+
const rls = record["rls"];
|
|
285
|
+
if (!Array.isArray(rls) || rls.some((role) => typeof role !== "string")) {
|
|
286
|
+
throw new MPFClaimsError(`Claim "tnts[${i}].rls" must be an array of strings`);
|
|
287
|
+
}
|
|
288
|
+
const tenant = { id, rls: [...rls] };
|
|
289
|
+
const dfr = record["dfr"];
|
|
290
|
+
if (dfr !== void 0 && dfr !== null && dfr !== "") {
|
|
291
|
+
if (typeof dfr !== "string") {
|
|
292
|
+
throw new MPFClaimsError(`Claim "tnts[${i}].dfr" must be a string when present`);
|
|
293
|
+
}
|
|
294
|
+
tenant.dfr = dfr;
|
|
295
|
+
}
|
|
296
|
+
return tenant;
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
function findTenant(claims, tenantId) {
|
|
300
|
+
return claims.tnts.find((t) => t.id === tenantId);
|
|
301
|
+
}
|
|
302
|
+
var parseV1 = (payload) => {
|
|
303
|
+
const claims = {
|
|
304
|
+
v: 1,
|
|
305
|
+
uid: requireString(payload, "uid"),
|
|
306
|
+
sid: requireString(payload, "sid"),
|
|
307
|
+
exp: requireNumber(payload, "exp"),
|
|
308
|
+
tnts: requireTenantList(payload)
|
|
309
|
+
};
|
|
310
|
+
const drl = optionalString(payload, "drl");
|
|
311
|
+
if (drl !== void 0) {
|
|
312
|
+
claims.drl = drl;
|
|
313
|
+
}
|
|
314
|
+
const iat = optionalNumber(payload, "iat");
|
|
315
|
+
if (iat !== void 0) {
|
|
316
|
+
claims.iat = iat;
|
|
317
|
+
}
|
|
318
|
+
return claims;
|
|
319
|
+
};
|
|
320
|
+
var PARSERS = /* @__PURE__ */ new Map([[1, parseV1]]);
|
|
321
|
+
function registerClaimsParser(version, parser) {
|
|
322
|
+
if (PARSERS.has(version)) {
|
|
323
|
+
throw new MPFClaimsError(`A parser is already registered for claims version ${version}`);
|
|
324
|
+
}
|
|
325
|
+
PARSERS.set(version, parser);
|
|
326
|
+
}
|
|
327
|
+
function supportedClaimsVersions() {
|
|
328
|
+
return [...PARSERS.keys()].sort((a, b) => a - b);
|
|
329
|
+
}
|
|
330
|
+
function decodeAccessToken(token) {
|
|
331
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
332
|
+
throw new MPFClaimsError("Access token must be a non-empty string");
|
|
333
|
+
}
|
|
334
|
+
const cached = cacheGet(token);
|
|
335
|
+
if (cached !== void 0) {
|
|
336
|
+
return cached;
|
|
337
|
+
}
|
|
338
|
+
let payload;
|
|
339
|
+
try {
|
|
340
|
+
payload = decodeJwt(token);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
throw new MPFClaimsError(
|
|
343
|
+
`Access token is not a decodable JWT: ${error instanceof Error ? error.message : "unknown error"}`,
|
|
344
|
+
"CLAIMS_MALFORMED"
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
const rawVersion = payload["v"];
|
|
348
|
+
if (typeof rawVersion !== "number" || !Number.isFinite(rawVersion)) {
|
|
349
|
+
throw new MPFUnsupportedClaimsVersionError(null);
|
|
350
|
+
}
|
|
351
|
+
const parser = PARSERS.get(rawVersion);
|
|
352
|
+
if (!parser) {
|
|
353
|
+
throw new MPFUnsupportedClaimsVersionError(rawVersion);
|
|
354
|
+
}
|
|
355
|
+
const claims = parser(payload);
|
|
356
|
+
cachePut(token, claims);
|
|
357
|
+
return claims;
|
|
358
|
+
}
|
|
359
|
+
function tryDecodeAccessToken(token) {
|
|
360
|
+
if (token === null || token === void 0 || token === "") {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
return decodeAccessToken(token);
|
|
365
|
+
} catch {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function listTenants(claims) {
|
|
370
|
+
return claims.tnts.map((tenant) => tenant.id);
|
|
371
|
+
}
|
|
372
|
+
function rolesForTenant(claims, tenantId) {
|
|
373
|
+
const tenant = findTenant(claims, tenantId);
|
|
374
|
+
return tenant ? [...tenant.rls] : [];
|
|
375
|
+
}
|
|
376
|
+
function defaultRoleForTenant(claims, tenantId) {
|
|
377
|
+
const tenant = findTenant(claims, tenantId);
|
|
378
|
+
if (tenant === void 0) {
|
|
379
|
+
return void 0;
|
|
380
|
+
}
|
|
381
|
+
return tenant.dfr ?? tenant.rls[0];
|
|
382
|
+
}
|
|
383
|
+
function canSelect(claims, tenantId, role) {
|
|
384
|
+
if (tenantId === null) {
|
|
385
|
+
return role === void 0 || role === claims.drl;
|
|
386
|
+
}
|
|
387
|
+
const tenant = findTenant(claims, tenantId);
|
|
388
|
+
if (!tenant || tenant.rls.length === 0) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
return role === void 0 || tenant.rls.includes(role);
|
|
392
|
+
}
|
|
393
|
+
function expiresAt(claims) {
|
|
394
|
+
return new Date(claims.exp * 1e3);
|
|
395
|
+
}
|
|
396
|
+
function isExpired(claims, skewSeconds = 0, nowMs) {
|
|
397
|
+
const now = (nowMs ?? Date.now()) / 1e3;
|
|
398
|
+
return claims.exp - skewSeconds <= now;
|
|
399
|
+
}
|
|
400
|
+
function tokenLifetimeSeconds(claims) {
|
|
401
|
+
if (claims.iat === void 0) {
|
|
402
|
+
return void 0;
|
|
403
|
+
}
|
|
404
|
+
const lifetime = claims.exp - claims.iat;
|
|
405
|
+
return lifetime > 0 ? lifetime : void 0;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export { DeadSessionError, MPFAPIError, MPFAuthError, MPFClaimsError, MPFError, MPFNetworkError, MPFTimeoutError, MPFUnsupportedClaimsVersionError, MPFValidationError, __export, canSelect, clearClaimsCache, decodeAccessToken, defaultRoleForTenant, expiresAt, isDeadSession, isExpired, isMPFAPIError, isMPFAuthError, isMPFError, isMPFNetworkError, isMPFValidationError, listTenants, registerClaimsParser, rolesForTenant, supportedClaimsVersions, tokenLifetimeSeconds, tryDecodeAccessToken };
|
|
409
|
+
//# sourceMappingURL=chunk-AJWM5MDZ.js.map
|
|
410
|
+
//# sourceMappingURL=chunk-AJWM5MDZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client/core/errors.ts","../src/auth/errors.ts","../src/auth/claims.ts"],"names":[],"mappings":"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","file":"chunk-AJWM5MDZ.js","sourcesContent":["/**\n * Custom error classes for the MPF SDK\n */\n\n/**\n * Base error class for all MPF SDK errors\n */\nexport class MPFError extends Error {\n /** Machine-readable error code */\n readonly code: string;\n /** HTTP status code if applicable */\n readonly status?: number;\n /** Additional error details from the API */\n readonly details?: unknown;\n\n constructor(message: string, code: string, status?: number, details?: unknown) {\n super(message);\n this.name = 'MPFError';\n this.code = code;\n this.status = status;\n this.details = details;\n\n // Maintains proper stack trace for where error was thrown (V8 engines)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n\n // Ensure prototype chain is properly set for instanceof checks\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /**\n * Convert error to a plain object for logging/serialization\n */\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n status: this.status,\n details: this.details,\n };\n }\n}\n\n/**\n * Error thrown when a network request fails (e.g., no connection, DNS failure)\n */\nexport class MPFNetworkError extends MPFError {\n /** The original error that caused the network failure */\n readonly cause?: Error;\n\n constructor(message: string, cause?: Error) {\n super(message, 'NETWORK_ERROR');\n this.name = 'MPFNetworkError';\n this.cause = cause;\n\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n override toJSON(): Record<string, unknown> {\n return {\n ...super.toJSON(),\n cause: this.cause?.message,\n };\n }\n}\n\n/**\n * Error thrown when the API returns an error response\n */\nexport class MPFAPIError extends MPFError {\n /** The raw response body if available */\n readonly body?: unknown;\n\n constructor(message: string, code: string, status: number, details?: unknown, body?: unknown) {\n super(message, code, status, details);\n this.name = 'MPFAPIError';\n this.body = body;\n\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n override toJSON(): Record<string, unknown> {\n return {\n ...super.toJSON(),\n body: this.body,\n };\n }\n}\n\n/**\n * Error thrown when request validation fails (e.g., invalid parameters)\n */\nexport class MPFValidationError extends MPFError {\n /** Field-specific validation errors */\n readonly fieldErrors?: Record<string, string[]>;\n\n constructor(message: string, details?: unknown, fieldErrors?: Record<string, string[]>) {\n super(message, 'VALIDATION_ERROR', 400, details);\n this.name = 'MPFValidationError';\n this.fieldErrors = fieldErrors;\n\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n override toJSON(): Record<string, unknown> {\n return {\n ...super.toJSON(),\n fieldErrors: this.fieldErrors,\n };\n }\n}\n\n/**\n * Error thrown when authentication or authorization fails (401/403)\n */\nexport class MPFAuthError extends MPFError {\n /** The type of auth error (e.g., 'unauthorized', 'forbidden', 'token_expired') */\n readonly authErrorType: 'unauthorized' | 'forbidden' | 'token_expired' | 'invalid_token';\n\n /**\n * The machine-readable code the server actually sent, when it sent one.\n *\n * `code` is derived from {@link authErrorType} and therefore collapses every\n * 401 to `UNAUTHORIZED`, which loses information the server deliberately\n * provides — `REFRESH_TOKEN_SUPERSEDED` being the case that matters: it means\n * \"another tab renewed first\", which is recoverable by adopting that tab's\n * token, and must not be treated as a dead session.\n *\n * Kept as a separate field rather than overwriting `code` so that existing\n * consumers switching on `code` are unaffected.\n */\n readonly serverCode?: string;\n\n constructor(\n message: string,\n authErrorType: 'unauthorized' | 'forbidden' | 'token_expired' | 'invalid_token',\n status: 401 | 403 = 401,\n details?: unknown,\n serverCode?: string\n ) {\n const code = authErrorType.toUpperCase();\n super(message, code, status, details);\n this.name = 'MPFAuthError';\n this.authErrorType = authErrorType;\n this.serverCode = serverCode;\n\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /**\n * Check if the error is due to an expired token\n */\n isTokenExpired(): boolean {\n return this.authErrorType === 'token_expired';\n }\n\n /**\n * Check if the error is a 403 Forbidden\n */\n isForbidden(): boolean {\n return this.authErrorType === 'forbidden';\n }\n\n override toJSON(): Record<string, unknown> {\n return {\n ...super.toJSON(),\n authErrorType: this.authErrorType,\n serverCode: this.serverCode,\n };\n }\n}\n\n/**\n * Error thrown when a request times out\n */\nexport class MPFTimeoutError extends MPFError {\n /** The timeout duration in milliseconds */\n readonly timeoutMs: number;\n\n constructor(message: string, timeoutMs: number) {\n super(message, 'TIMEOUT_ERROR');\n this.name = 'MPFTimeoutError';\n this.timeoutMs = timeoutMs;\n\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n override toJSON(): Record<string, unknown> {\n return {\n ...super.toJSON(),\n timeoutMs: this.timeoutMs,\n };\n }\n}\n\n/**\n * Type guard to check if an error is an MPFError\n */\nexport function isMPFError(error: unknown): error is MPFError {\n return error instanceof MPFError;\n}\n\n/**\n * Type guard to check if an error is an MPFAuthError\n */\nexport function isMPFAuthError(error: unknown): error is MPFAuthError {\n return error instanceof MPFAuthError;\n}\n\n/**\n * Type guard to check if an error is an MPFAPIError\n */\nexport function isMPFAPIError(error: unknown): error is MPFAPIError {\n return error instanceof MPFAPIError;\n}\n\n/**\n * Type guard to check if an error is an MPFNetworkError\n */\nexport function isMPFNetworkError(error: unknown): error is MPFNetworkError {\n return error instanceof MPFNetworkError;\n}\n\n/**\n * Type guard to check if an error is an MPFValidationError\n */\nexport function isMPFValidationError(error: unknown): error is MPFValidationError {\n return error instanceof MPFValidationError;\n}\n","/**\n * Errors for the auth layer.\n */\n\nimport { MPFError } from '../client/core/errors.js';\n\n/**\n * Base error for access-token claim decoding failures.\n */\nexport class MPFClaimsError extends MPFError {\n constructor(message: string, code = 'CLAIMS_INVALID', details?: unknown) {\n super(message, code, undefined, details);\n this.name = 'MPFClaimsError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Thrown when a token's claim-set version has no registered parser.\n *\n * Carries the offending version so a client can distinguish \"this app is older\n * than the server\" (a deploy-ordering problem, usually fixed by reloading) from\n * \"this token is garbage\" (a real auth failure).\n */\nexport class MPFUnsupportedClaimsVersionError extends MPFClaimsError {\n /** The `v` value found in the token, or null when it was absent/non-numeric. */\n readonly version: number | null;\n\n constructor(version: number | null) {\n super(\n version === null\n ? 'Access token has no claims version (`v`)'\n : `Unsupported access token claims version: ${version}`,\n 'CLAIMS_UNSUPPORTED_VERSION',\n { version },\n );\n this.name = 'MPFUnsupportedClaimsVersionError';\n this.version = version;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n override toJSON(): Record<string, unknown> {\n return { ...super.toJSON(), version: this.version };\n }\n}\n\n/**\n * Thrown when the session can no longer be renewed and the user must\n * re-authenticate.\n *\n * This is a terminal state: the refresh token was rejected, absent, or the\n * renew endpoint failed in a non-recoverable way.\n */\nexport class DeadSessionError extends MPFError {\n /** The underlying error that killed the session, when there was one. */\n readonly cause?: Error;\n\n constructor(message = 'Session is no longer valid', cause?: Error) {\n super(message, 'DEAD_SESSION', 401);\n this.name = 'DeadSessionError';\n this.cause = cause;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n override toJSON(): Record<string, unknown> {\n return { ...super.toJSON(), cause: this.cause?.message };\n }\n}\n\n/**\n * Type guard for {@link DeadSessionError}.\n */\nexport function isDeadSession(error: unknown): error is DeadSessionError {\n return error instanceof DeadSessionError;\n}\n","/**\n * ============================================================================\n * SECURITY BOUNDARY — READ BEFORE USING ANYTHING IN THIS FILE\n * ============================================================================\n *\n * This module DECODES access tokens. It does NOT VERIFY them.\n *\n * There is no signature check here. There is no key material here. We call\n * `jose.decodeJwt`, which base64-decodes the payload and nothing else — an\n * unsigned token, a token signed with the wrong key, and a token whose payload\n * was hand-edited in devtools all decode identically and successfully.\n *\n * A hostile client can therefore forge ANY value this module returns: any user\n * id, any session id, any tenant, any role, any expiry.\n *\n * Every consumer of this module must be UX-only:\n * - rendering a tenant switcher,\n * - failing an obviously-invalid selection locally instead of round-tripping\n * for a 403,\n * - deciding when to proactively renew.\n *\n * NEVER gate a security-relevant branch on these values. The server is the only\n * authority: `hasura-authd` and `internal/go-chi-middleware-auth` verify the\n * signature and re-derive tenant and role from the token on every single\n * request, and a forged claim buys the attacker exactly nothing there.\n *\n * `jwtVerify` must never appear in this package. Verification needs the signing\n * secret, and the signing secret must never reach a browser.\n * ============================================================================\n */\n\nimport { decodeJwt } from 'jose';\n\nimport type {\n ClaimsParser,\n MPFClaims,\n MPFClaimsV1,\n MPFRole,\n MPFTenantClaim,\n} from './claims-types.js';\nimport { MPFClaimsError, MPFUnsupportedClaimsVersionError } from './errors.js';\n\n/** Maximum number of decoded token payloads kept in memory. */\nconst CLAIMS_CACHE_MAX = 5;\n\n/**\n * LRU cache of successful decodes, keyed on the raw token string.\n *\n * Keyed on the token itself, never on `uid`/`sid` — keying on an identity claim\n * would let a tampered token collide with a legitimate entry.\n *\n * Insertion-ordered `Map`: a hit is re-inserted to move it to the back, so\n * eviction takes the least-recently-*used* entry rather than the oldest.\n * Failures are never cached.\n */\nconst claimsCache = new Map<string, MPFClaims>();\n\nfunction cacheGet(token: string): MPFClaims | undefined {\n const hit = claimsCache.get(token);\n if (hit === undefined) {\n return undefined;\n }\n // Bump to most-recently-used.\n claimsCache.delete(token);\n claimsCache.set(token, hit);\n return hit;\n}\n\nfunction cachePut(token: string, claims: MPFClaims): void {\n if (claimsCache.has(token)) {\n claimsCache.delete(token);\n }\n claimsCache.set(token, claims);\n\n while (claimsCache.size > CLAIMS_CACHE_MAX) {\n const oldest = claimsCache.keys().next();\n if (oldest.done) {\n break;\n }\n claimsCache.delete(oldest.value);\n }\n}\n\n/**\n * Drop every cached decode.\n *\n * Call on logout so a decoded token cannot outlive the session in memory.\n */\nexport function clearClaimsCache(): void {\n claimsCache.clear();\n}\n\n// ---------------------------------------------------------------------------\n// Field helpers\n// ---------------------------------------------------------------------------\n\nfunction requireString(payload: Record<string, unknown>, key: string): string {\n const value = payload[key];\n if (typeof value !== 'string' || value.length === 0) {\n throw new MPFClaimsError(`Claim \"${key}\" must be a non-empty string`);\n }\n return value;\n}\n\nfunction requireNumber(payload: Record<string, unknown>, key: string): number {\n const value = payload[key];\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new MPFClaimsError(`Claim \"${key}\" must be a finite number`);\n }\n return value;\n}\n\nfunction optionalNumber(payload: Record<string, unknown>, key: string): number | undefined {\n const value = payload[key];\n if (value === undefined) {\n return undefined;\n }\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new MPFClaimsError(`Claim \"${key}\" must be a finite number when present`);\n }\n return value;\n}\n\n/**\n * A claim whose EMPTY state is meaningful, so absence degrades instead of\n * throwing.\n *\n * `drl` is emitted without `omitempty` on the Go side, so it arrives as `\"\"`\n * rather than vanishing when a deployment has no tenantless role configured.\n * Nothing the client does with it is load-bearing — the server decides the\n * effective role regardless — so an empty value becomes `undefined` rather than\n * locking the user out of decoding their own token. A wrong *type* still throws:\n * that is corruption, not absence.\n */\nfunction optionalString(payload: Record<string, unknown>, key: string): string | undefined {\n const value = payload[key];\n if (value === undefined || value === null || value === '') {\n return undefined;\n }\n if (typeof value !== 'string') {\n throw new MPFClaimsError(`Claim \"${key}\" must be a string when present`);\n }\n return value;\n}\n\n/**\n * Read the tenant array, treating absence as \"no tenants\".\n *\n * `tnts` is a mandatory claim carrying no `omitempty`, and `Mint` normalises a\n * nil slice to `[]`, so the server always emits an array. Tolerating absence\n * here anyway is defence in depth on a cross-repo contract: the SDK is the half\n * that ships to consumers who cannot patch the server, and \"no tenants\" is a\n * legitimate state rather than a malformed token — an earlier `omitempty` on the\n * Go field made every fresh signup undecodable.\n *\n * A `tnts` of the wrong TYPE still throws — coercing an object or a string to an\n * empty array would hide a real server bug behind an empty switcher.\n */\nfunction requireTenantList(payload: Record<string, unknown>): MPFTenantClaim[] {\n const value = payload['tnts'];\n\n // Absent or explicitly null: a user with no tenants.\n if (value === undefined || value === null) {\n return [];\n }\n\n if (!Array.isArray(value)) {\n throw new MPFClaimsError('Claim \"tnts\" must be an array when present');\n }\n\n return value.map((entry, i) => {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n throw new MPFClaimsError(`Claim \"tnts[${i}]\" must be an object`);\n }\n const record = entry as Record<string, unknown>;\n\n const id = record['id'];\n if (typeof id !== 'string' || id.length === 0) {\n throw new MPFClaimsError(`Claim \"tnts[${i}].id\" must be a non-empty string`);\n }\n\n const rls = record['rls'];\n if (!Array.isArray(rls) || rls.some((role) => typeof role !== 'string')) {\n throw new MPFClaimsError(`Claim \"tnts[${i}].rls\" must be an array of strings`);\n }\n\n const tenant: MPFTenantClaim = { id, rls: [...(rls as MPFRole[])] };\n\n // dfr is OPTIONAL. Absent means \"no explicit default\" — see\n // defaultRoleForTenant, which falls back to rls[0]. A present-but-wrong\n // TYPE still throws: that is corruption, not absence.\n const dfr = record['dfr'];\n if (dfr !== undefined && dfr !== null && dfr !== '') {\n if (typeof dfr !== 'string') {\n throw new MPFClaimsError(`Claim \"tnts[${i}].dfr\" must be a string when present`);\n }\n tenant.dfr = dfr;\n }\n\n return tenant;\n });\n}\n\n/** The `tnts` entry for a tenant id, or `undefined`. */\nfunction findTenant(claims: MPFClaims, tenantId: string): MPFTenantClaim | undefined {\n return claims.tnts.find((t) => t.id === tenantId);\n}\n\n// ---------------------------------------------------------------------------\n// Parser registry\n// ---------------------------------------------------------------------------\n\n/**\n * v1 parser.\n *\n * Reads only the keys it knows about, so a field added on the Go side cannot\n * brick an older client. Unknown *versions* are rejected; unknown *keys* are\n * ignored.\n */\nconst parseV1: ClaimsParser = (payload) => {\n // Hard-required vs degrading is a deliberate line:\n //\n // REQUIRED — absence means the token cannot be interpreted at all.\n // v : without a version we do not know how to read the rest.\n // exp : a token with no expiry must not be trusted.\n // uid : a session attributed to nobody would render a UI for nobody.\n // sid : likewise; Go emits both without `omitempty` and neither is ever\n // legitimately empty.\n //\n // DEGRADING — the empty state is a real, expected state.\n // tnts : a brand-new user belongs to no tenants (an empty array).\n // drl : a deployment may have no tenantless role configured.\n // iat : only needed for the renewal-buffer clamp, which falls back.\n //\n // The through-line: a server-side regression in a non-essential claim must\n // not lock a user out of decoding their own token, but a claim we cannot\n // proceed without should fail loudly rather than silently default.\n const claims: MPFClaimsV1 = {\n v: 1,\n uid: requireString(payload, 'uid'),\n sid: requireString(payload, 'sid'),\n exp: requireNumber(payload, 'exp'),\n tnts: requireTenantList(payload),\n };\n\n const drl = optionalString(payload, 'drl');\n if (drl !== undefined) {\n claims.drl = drl;\n }\n\n const iat = optionalNumber(payload, 'iat');\n if (iat !== undefined) {\n claims.iat = iat;\n }\n\n return claims;\n};\n\n/**\n * Version → parser. Explicit registry rather than an if-chain, so a future v2\n * is one added entry and the two versions coexist during a rollout.\n */\nconst PARSERS = new Map<number, ClaimsParser>([[1, parseV1]]);\n\n/**\n * Register a parser for a claim-set version.\n *\n * Exported so a consumer pinned to an older SDK can teach it a newer shape\n * without waiting for a release.\n *\n * @throws {MPFClaimsError} If the version already has a parser — a silent\n * overwrite would make decoding depend on module import order.\n */\nexport function registerClaimsParser(version: number, parser: ClaimsParser): void {\n if (PARSERS.has(version)) {\n throw new MPFClaimsError(`A parser is already registered for claims version ${version}`);\n }\n PARSERS.set(version, parser);\n}\n\n/**\n * The claim-set versions this build understands.\n */\nexport function supportedClaimsVersions(): number[] {\n return [...PARSERS.keys()].sort((a, b) => a - b);\n}\n\n// ---------------------------------------------------------------------------\n// Decoding\n// ---------------------------------------------------------------------------\n\n/**\n * Decode an access token's claims.\n *\n * **Decodes, does not verify** — see the security block at the top of this file.\n *\n * Succeeds on an EXPIRED token by design: the session manager needs `exp` in\n * order to decide that a renewal is due, so expiry is the caller's question,\n * not this function's. Use {@link isExpired} to ask it.\n *\n * @param token - Raw JWT string.\n * @returns The typed claims.\n * @throws {MPFUnsupportedClaimsVersionError} If `v` is absent or unknown.\n * @throws {MPFClaimsError} If the token is malformed or a claim has the wrong type.\n */\nexport function decodeAccessToken(token: string): MPFClaims {\n if (typeof token !== 'string' || token.length === 0) {\n throw new MPFClaimsError('Access token must be a non-empty string');\n }\n\n const cached = cacheGet(token);\n if (cached !== undefined) {\n return cached;\n }\n\n let payload: Record<string, unknown>;\n try {\n payload = decodeJwt(token) as Record<string, unknown>;\n } catch (error) {\n throw new MPFClaimsError(\n `Access token is not a decodable JWT: ${error instanceof Error ? error.message : 'unknown error'}`,\n 'CLAIMS_MALFORMED',\n );\n }\n\n const rawVersion = payload['v'];\n if (typeof rawVersion !== 'number' || !Number.isFinite(rawVersion)) {\n throw new MPFUnsupportedClaimsVersionError(null);\n }\n\n const parser = PARSERS.get(rawVersion);\n if (!parser) {\n throw new MPFUnsupportedClaimsVersionError(rawVersion);\n }\n\n const claims = parser(payload);\n cachePut(token, claims);\n return claims;\n}\n\n/**\n * Decode an access token, returning `null` instead of throwing.\n *\n * For UI paths that must render something regardless of token health.\n *\n * **Decodes, does not verify** — see the security block at the top of this file.\n */\nexport function tryDecodeAccessToken(token: string | null | undefined): MPFClaims | null {\n if (token === null || token === undefined || token === '') {\n return null;\n }\n try {\n return decodeAccessToken(token);\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Accessors\n// ---------------------------------------------------------------------------\n\n/**\n * Tenant ids present in the token, in `tnts` order.\n */\nexport function listTenants(claims: MPFClaims): string[] {\n return claims.tnts.map((tenant) => tenant.id);\n}\n\n/**\n * Roles held in a tenant.\n *\n * Returns a fresh array; mutating it cannot corrupt the cached claims.\n */\nexport function rolesForTenant(claims: MPFClaims, tenantId: string): MPFRole[] {\n const tenant = findTenant(claims, tenantId);\n return tenant ? [...tenant.rls] : [];\n}\n\n/**\n * The default role for a tenant: its explicit `dfr` when the entry carries one,\n * otherwise `rls[0]`.\n *\n * `undefined` when the tenant is not in the token, or holds no roles.\n */\nexport function defaultRoleForTenant(claims: MPFClaims, tenantId: string): MPFRole | undefined {\n const tenant = findTenant(claims, tenantId);\n if (tenant === undefined) {\n return undefined;\n }\n return tenant.dfr ?? tenant.rls[0];\n}\n\n/**\n * Whether a tenant (and optionally a role) *appears* selectable in this token.\n *\n * ⚠️ **NOT A PERMISSION CHECK.** This reads unverified, client-forgeable claims.\n * It exists so a switcher can grey out an entry and so an obviously-invalid\n * selection fails locally instead of costing a round trip for a 403. A `true`\n * here grants nothing: the server re-derives tenant and role from the verified\n * token on every request and will reject a selection this function approved.\n * Never branch on it for anything that must hold.\n *\n * @param claims - Decoded (unverified) claims.\n * @param tenantId - Tenant to select, or `null` for the no-tenant path.\n * @param role - Optional specific role; omitted means \"the tenant's default\".\n */\nexport function canSelect(\n claims: MPFClaims,\n tenantId: string | null,\n role?: MPFRole,\n): boolean {\n // The no-tenant path resolves to `drl` server-side.\n if (tenantId === null) {\n return role === undefined || role === claims.drl;\n }\n\n const tenant = findTenant(claims, tenantId);\n if (!tenant || tenant.rls.length === 0) {\n return false;\n }\n\n // `dfr` narrows which role is DEFAULTED to, never which roles may be named,\n // so the membership check stays against `rls`.\n return role === undefined || tenant.rls.includes(role);\n}\n\n/**\n * The token's expiry as a `Date`.\n */\nexport function expiresAt(claims: MPFClaims): Date {\n return new Date(claims.exp * 1000);\n}\n\n/**\n * Whether the token is expired, optionally treating it as expired\n * `skewSeconds` early.\n *\n * @param claims - Decoded claims.\n * @param skewSeconds - Seconds of headroom. `120` answers \"will this token die\n * within the next two minutes?\", which is the question a renewal buffer asks.\n * @param nowMs - Injectable clock for tests. Defaults to `Date.now()`.\n */\nexport function isExpired(claims: MPFClaims, skewSeconds = 0, nowMs?: number): boolean {\n const now = (nowMs ?? Date.now()) / 1000;\n return claims.exp - skewSeconds <= now;\n}\n\n/**\n * Token lifetime in seconds (`exp - iat`), or `undefined` when the token\n * carries no `iat`.\n */\nexport function tokenLifetimeSeconds(claims: MPFClaims): number | undefined {\n if (claims.iat === undefined) {\n return undefined;\n }\n const lifetime = claims.exp - claims.iat;\n return lifetime > 0 ? lifetime : undefined;\n}\n"]}
|