@pontive/pontkit-server 1.2.0 → 1.3.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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/cookies.ts","../src/oidc.ts","../src/browser-mode.ts","../src/config.ts","../src/refresh.ts","../src/seal.ts","../src/server-auth.ts"],"sourcesContent":["/**\n * Cookie reading and writing, on plain header strings so it runs anywhere a\n * Web `Request`/`Response` does.\n *\n * A sealed session outgrows one cookie (browsers cap each at about 4 KB, and an\n * access token, a refresh token and a user fit in about 3–5), so it is split\n * across numbered chunks: `name`, `name.1`, `name.2`, … and joined on read.\n */\n\n/** Characters per chunk, leaving room under the ~4096-byte cap for the name and attributes. */\nexport const CHUNK_SIZE = 3500;\n/** Upper bound on chunks probed when reading or clearing. */\nexport const MAX_CHUNKS = 16;\n\nexport interface CookieAttributes {\n /** Seconds; 0 deletes the cookie. */\n maxAge: number;\n secure: boolean;\n}\n\n/** Parses a `Cookie` request header into name → value. */\nexport function parseCookies(header: string | null | undefined): Map<string, string> {\n const out = new Map<string, string>();\n if (!header) return out;\n\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq < 0) continue;\n\n const name = part.slice(0, eq).trim();\n if (!name || out.has(name)) continue;\n\n out.set(name, part.slice(eq + 1).trim());\n }\n\n return out;\n}\n\n/** Serialises a `Cookie` request header from name → value. */\nexport function formatCookieHeader(cookies: Map<string, string>): string {\n return [...cookies].map(([name, value]) => `${name}=${value}`).join(\"; \");\n}\n\n/**\n * A `Set-Cookie` value. Always HttpOnly, host-only, `Path=/` and\n * `SameSite=Lax`: Lax is what lets the cookie ride the top-level redirect back\n * from the sign-in page, and nothing here is ever for script.\n */\nexport function serializeCookie(name: string, value: string, attrs: CookieAttributes): string {\n const parts = [`${name}=${value}`, \"Path=/\", \"HttpOnly\", \"SameSite=Lax\", `Max-Age=${attrs.maxAge}`];\n if (attrs.secure) parts.push(\"Secure\");\n\n return parts.join(\"; \");\n}\n\nexport function chunkName(name: string, index: number): string {\n return index === 0 ? name : `${name}.${index}`;\n}\n\n/** Joins a chunked value, or null when its first chunk is absent. */\nexport function readChunked(cookies: Map<string, string>, name: string): string | null {\n const first = cookies.get(name);\n if (first === undefined) return null;\n\n let value = first;\n for (let i = 1; i < MAX_CHUNKS; i++) {\n const part = cookies.get(chunkName(name, i));\n if (part === undefined) break;\n value += part;\n }\n\n return value;\n}\n\n/**\n * The `Set-Cookie` values that store `value` in chunks, and delete any chunk\n * the request carried beyond what it now needs. Also updates `cookies` in\n * place, so a caller can forward the new state on the same request.\n */\nexport function writeChunked(\n cookies: Map<string, string>,\n name: string,\n value: string,\n attrs: CookieAttributes,\n): string[] {\n const out: string[] = [];\n const count = Math.max(1, Math.ceil(value.length / CHUNK_SIZE));\n\n for (let i = 0; i < count; i++) {\n const chunk = value.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);\n cookies.set(chunkName(name, i), chunk);\n out.push(serializeCookie(chunkName(name, i), chunk, attrs));\n }\n\n for (let i = count; i < MAX_CHUNKS; i++) {\n const stale = chunkName(name, i);\n if (!cookies.has(stale)) break;\n cookies.delete(stale);\n out.push(serializeCookie(stale, \"\", { ...attrs, maxAge: 0 }));\n }\n\n return out;\n}\n\n/** The `Set-Cookie` values that delete every chunk the request carried. */\nexport function clearChunked(cookies: Map<string, string>, name: string, secure: boolean): string[] {\n const out: string[] = [];\n\n for (let i = 0; i < MAX_CHUNKS; i++) {\n const chunk = chunkName(name, i);\n if (!cookies.has(chunk)) {\n if (i > 0) break;\n continue;\n }\n cookies.delete(chunk);\n out.push(serializeCookie(chunk, \"\", { maxAge: 0, secure }));\n }\n\n return out;\n}\n","import { createLocalJWKSet, jwtVerify, type JSONWebKeySet, type JWTPayload } from \"jose\";\n\n/**\n * The OIDC relying-party calls: discovery, PKCE, the two token grants and\n * id_token verification. Standard OIDC only, so any compliant issuer works.\n */\n\nexport interface Discovery {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n jwks_uri: string;\n end_session_endpoint?: string;\n}\n\nexport interface TokenResponse {\n access_token: string;\n token_type: string;\n expires_in?: number;\n refresh_token?: string;\n id_token?: string;\n scope?: string;\n}\n\nexport interface ClientCredentials {\n issuer: string;\n clientId: string;\n clientSecret?: string;\n}\n\n/** An error from the token endpoint, carrying its OAuth code. */\nexport class TokenEndpointError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n description: string,\n ) {\n super(`token endpoint ${status} ${code}: ${description}`);\n }\n}\n\ntype Fetch = typeof fetch;\n\nconst JWKS_TTL_MS = 10 * 60 * 1000;\n\nconst discoveries = new Map<string, Promise<Discovery>>();\nconst jwksCache = new Map<string, { at: number; set: ReturnType<typeof createLocalJWKSet> }>();\n\nfunction trimSlash(value: string): string {\n return value.replace(/\\/+$/, \"\");\n}\n\nexport function discover(issuer: string, fetchImpl: Fetch): Promise<Discovery> {\n const key = trimSlash(issuer);\n let pending = discoveries.get(key);\n\n if (!pending) {\n pending = fetchImpl(`${key}/.well-known/openid-configuration`)\n .then(async (res) => {\n if (!res.ok) throw new Error(`OIDC discovery failed for ${key}: ${res.status}`);\n return (await res.json()) as Discovery;\n })\n .catch((err: unknown) => {\n // A failure is not cached: the next request retries.\n discoveries.delete(key);\n throw err;\n });\n discoveries.set(key, pending);\n }\n\n return pending;\n}\n\nasync function jwks(uri: string, fetchImpl: Fetch, force: boolean): Promise<ReturnType<typeof createLocalJWKSet>> {\n const cached = jwksCache.get(uri);\n if (cached && !force && Date.now() - cached.at < JWKS_TTL_MS) return cached.set;\n\n const res = await fetchImpl(uri);\n if (!res.ok) throw new Error(`JWKS fetch failed for ${uri}: ${res.status}`);\n\n const set = createLocalJWKSet((await res.json()) as JSONWebKeySet);\n jwksCache.set(uri, { at: Date.now(), set });\n\n return set;\n}\n\nfunction base64url(bytes: Uint8Array): string {\n let binary = \"\";\n for (const b of bytes) binary += String.fromCharCode(b);\n\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** A random, URL-safe value for state, nonce and the PKCE verifier. */\nexport function randomValue(): string {\n return base64url(crypto.getRandomValues(new Uint8Array(32)));\n}\n\nexport async function pkceChallenge(verifier: string): Promise<string> {\n const digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(verifier));\n\n return base64url(new Uint8Array(digest));\n}\n\n/** Hex SHA-256, for keying state by a secret without holding the secret. */\nexport async function sha256Hex(value: string): Promise<string> {\n const digest = new Uint8Array(await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(value)));\n\n return [...digest].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport async function authorizationUrl(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { redirectUri: string; scopes: string; state: string; nonce: string; codeChallenge: string; prompt?: string },\n): Promise<string> {\n const disc = await discover(client.issuer, fetchImpl);\n const url = new URL(disc.authorization_endpoint);\n\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", client.clientId);\n url.searchParams.set(\"redirect_uri\", input.redirectUri);\n url.searchParams.set(\"scope\", input.scopes);\n url.searchParams.set(\"state\", input.state);\n url.searchParams.set(\"nonce\", input.nonce);\n url.searchParams.set(\"code_challenge\", input.codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n if (input.prompt) url.searchParams.set(\"prompt\", input.prompt);\n\n return url.toString();\n}\n\n/**\n * A token-endpoint call. A confidential client authenticates with HTTP Basic\n * (RFC 6749 §2.3.1); `client_id` is always in the form too, which a public\n * client needs and a confidential one is allowed.\n */\nasync function tokenRequest(\n client: ClientCredentials,\n fetchImpl: Fetch,\n params: Record<string, string>,\n): Promise<TokenResponse> {\n const disc = await discover(client.issuer, fetchImpl);\n const body = new URLSearchParams(params);\n body.set(\"client_id\", client.clientId);\n\n const headers: Record<string, string> = { \"content-type\": \"application/x-www-form-urlencoded\" };\n if (client.clientSecret) {\n const credentials = `${encodeURIComponent(client.clientId)}:${encodeURIComponent(client.clientSecret)}`;\n headers.authorization = `Basic ${btoa(credentials)}`;\n }\n\n const res = await fetchImpl(disc.token_endpoint, { method: \"POST\", headers, body });\n if (!res.ok) {\n let code = \"unknown_error\";\n let description = \"\";\n\n try {\n const err = (await res.json()) as { error?: string; error_description?: string };\n code = err.error ?? code;\n description = err.error_description ?? \"\";\n } catch {\n // Not JSON; the status says enough.\n }\n\n throw new TokenEndpointError(res.status, code, description);\n }\n\n return (await res.json()) as TokenResponse;\n}\n\nexport function exchangeCode(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { code: string; codeVerifier: string; redirectUri: string },\n): Promise<TokenResponse> {\n return tokenRequest(client, fetchImpl, {\n grant_type: \"authorization_code\",\n code: input.code,\n code_verifier: input.codeVerifier,\n redirect_uri: input.redirectUri,\n });\n}\n\nexport function refreshTokens(client: ClientCredentials, fetchImpl: Fetch, refreshToken: string): Promise<TokenResponse> {\n return tokenRequest(client, fetchImpl, { grant_type: \"refresh_token\", refresh_token: refreshToken });\n}\n\n/**\n * Verifies a JWT the issuer signed for this app: signature, issuer, audience\n * and expiry. A key the cached set does not know refetches the set once, which\n * is how a key rotation at the issuer is picked up without waiting out the\n * cache.\n */\nexport async function verifyJwt(client: ClientCredentials, fetchImpl: Fetch, token: string): Promise<JWTPayload> {\n const disc = await discover(client.issuer, fetchImpl);\n const options = { issuer: disc.issuer, audience: client.clientId };\n\n try {\n return (await jwtVerify(token, await jwks(disc.jwks_uri, fetchImpl, false), options)).payload;\n } catch (err) {\n if ((err as { code?: string }).code !== \"ERR_JWKS_NO_MATCHING_KEY\") throw err;\n return (await jwtVerify(token, await jwks(disc.jwks_uri, fetchImpl, true), options)).payload;\n }\n}\n\n/** Verifies an id_token as verifyJwt does, plus the nonce of the sign-in it answers. */\nexport async function verifyIdToken(\n client: ClientCredentials,\n fetchImpl: Fetch,\n idToken: string,\n nonce: string,\n): Promise<JWTPayload> {\n const payload = await verifyJwt(client, fetchImpl, idToken);\n if (payload.nonce !== nonce) throw new Error(\"id_token nonce does not match the sign-in request\");\n\n return payload;\n}\n\n/** RP-initiated logout (OIDC RP-Initiated Logout 1.0), or null when the issuer offers none. */\nexport async function endSessionUrl(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { postLogoutRedirectUri: string; sessionId?: string },\n): Promise<string | null> {\n const disc = await discover(client.issuer, fetchImpl);\n if (!disc.end_session_endpoint) return null;\n\n const url = new URL(disc.end_session_endpoint);\n url.searchParams.set(\"client_id\", client.clientId);\n url.searchParams.set(\"post_logout_redirect_uri\", input.postLogoutRedirectUri);\n // logout_hint is what lets the issuer end its session without asking \"Sign\n // out?\": only an app holding tokens from that session knows the sid.\n if (input.sessionId) url.searchParams.set(\"logout_hint\", input.sessionId);\n\n return url.toString();\n}\n\n/** For tests: forget cached discovery documents and key sets. */\nexport function resetOidcCaches(): void {\n discoveries.clear();\n jwksCache.clear();\n}\n","import type { JWTPayload } from \"jose\";\n\nimport type { ResolvedConfig } from \"./config\";\nimport { parseCookies } from \"./cookies\";\nimport { verifyJwt, type ClientCredentials } from \"./oidc\";\nimport type { RefreshOutcome, ServerAuth, Session, SessionUser } from \"./server-auth\";\n\n/**\n * Server-side sessions for an app whose browser holds the session: a regular\n * app, or a public oauth app, on the browser SDK.\n *\n * The browser SDK signs the user in and is the only thing that ever spends the\n * refresh token. The auth server, reached through the auth proxy on this app's\n * origin, keeps an HttpOnly cookie holding the current access token. The server\n * reads and verifies that cookie, and nothing more — so there is exactly one\n * refresher per session, and rotation never sees a token spent twice.\n *\n * When a page navigation arrives with no live access token but with the\n * session marker, the access token has lapsed while the user is still signed\n * in (the tab was closed, or idle past its refresh). The browser is sent\n * through the auth server's session handshake, which renews the cookies and\n * brings it straight back.\n */\n\n/** The access token, written by the auth server wherever it writes the refresh cookie. */\nexport const ACCESS_COOKIE = \"__Host-pontive_access\";\n/** The readable marker: the browser holds a refresh token. */\nexport const SESSION_HINT_COOKIE = \"__Secure-pontive_session_state\";\n\nfunction userFrom(claims: JWTPayload): SessionUser {\n const str = (key: string) => (typeof claims[key] === \"string\" ? (claims[key] as string) : undefined);\n\n return {\n id: claims.sub ?? \"\",\n email: str(\"email\"),\n name: str(\"name\") ?? str(\"email\"),\n username: str(\"preferred_username\"),\n picture: str(\"picture\"),\n emailVerified: claims.email_verified === true,\n };\n}\n\nexport function createBrowserAuth(cfg: ResolvedConfig): ServerAuth {\n const client: ClientCredentials = { issuer: cfg.issuer, clientId: cfg.clientId };\n\n async function read(cookieHeader: string | null | undefined): Promise<Session | null> {\n const token = parseCookies(cookieHeader).get(ACCESS_COOKIE);\n if (!token) return null;\n\n let claims: JWTPayload;\n try {\n claims = await verifyJwt(client, cfg.fetch, token);\n } catch (err) {\n // An expired token is the ordinary case — the handshake renews it — and\n // not worth a warning. Anything else is.\n if ((err as { code?: string }).code !== \"ERR_JWT_EXPIRED\") {\n cfg.logger.warn(\"[pontive] access token cookie refused:\", err instanceof Error ? err.message : String(err));\n }\n\n return null;\n }\n\n return {\n user: userFrom(claims),\n accessToken: token,\n expiresAt: (claims.exp ?? 0) * 1000,\n sessionId: typeof claims.sid === \"string\" ? claims.sid : undefined,\n };\n }\n\n return {\n // Sign-in and sign-out belong to the browser SDK's widgets in this mode.\n async handle() {\n return null;\n },\n\n getSession: read,\n\n async refresh(request) {\n const cookieHeader = request.headers.get(\"cookie\");\n const session = await read(cookieHeader);\n const outcome: RefreshOutcome = { session, setCookies: [], cookieHeader: cookieHeader ?? \"\" };\n\n if (session || !parseCookies(cookieHeader).has(SESSION_HINT_COOKIE)) return outcome;\n\n // Signed in, but the access token has lapsed. Never refreshed here: the\n // browser goes through the handshake, so the browser stays the only\n // thing that spends the refresh token.\n const url = new URL(request.url);\n const handshake = new URL(`${cfg.authPath}/oauth2/session`, url.origin);\n handshake.searchParams.set(\"client_id\", cfg.clientId);\n handshake.searchParams.set(\"return_to\", url.pathname + url.search);\n\n return { ...outcome, handshake: handshake.pathname + handshake.search };\n },\n };\n}\n","/**\n * What an app tells the SDK, and the defaults for everything it doesn't.\n *\n * Whether `clientSecret` is set picks the mode, and nothing else does:\n *\n * - **With a secret** (a confidential oauth app): the server holds the whole\n * session in a sealed cookie, signs users in through its own routes, and\n * refreshes in middleware. Needs `sessionSecret`.\n * - **Without one** (a regular app, or a public oauth app): the browser SDK\n * signs users in and is the only thing that refreshes. The server reads the\n * access token cookie the auth server keeps current, through the auth proxy,\n * and sends a lapsed navigation through the auth server's session handshake.\n *\n * Everything beyond the first few fields exists for the rare app that must\n * differ, and its default is the decision the SDK makes for everyone else.\n */\nexport interface ServerAuthConfig {\n /** The Pontive auth domain, e.g. `https://acme.auth.us.pontive.com`. */\n issuer: string;\n /** The app's id. */\n clientId: string;\n /**\n * Set for a confidential app. It is sent on every token call, and is what\n * lets the app hold its refresh token on its server at all.\n */\n clientSecret?: string;\n /**\n * Encrypts the session cookie. At least 32 characters; rotating it signs\n * everyone out. Required with a `clientSecret`; unused without one, where the\n * server stores nothing.\n */\n sessionSecret?: string;\n /**\n * The app's public origin, e.g. `https://app.acme.com`. Set it for any\n * deployment behind a proxy or in a container, where the request's own URL\n * names the address the server binds to rather than the one a browser uses.\n * Without it the origin comes from `x-forwarded-proto`/`x-forwarded-host`,\n * then the request URL.\n */\n baseUrl?: string;\n\n /**\n * Without a `clientSecret`: where the auth proxy serves the auth server on\n * this app's origin. Default `/__auth`, the auth proxy's own default.\n */\n authPath?: string;\n\n /** Default `openid email profile offline_access`. */\n scopes?: string;\n /** Where the SDK's sign-in routes live. Default `/api/auth`. */\n basePath?: string;\n /** Default `${origin}${basePath}/callback`; must be registered on the app verbatim. */\n redirectUri?: string;\n /** The app's own sign-in page: failed sign-ins and signed-out users land here. Default `/login`. */\n signInPath?: string;\n /** Where a sign-in lands when it did not start from a page. Default `/`. */\n afterSignInPath?: string;\n /** An OIDC `prompt` to send, e.g. `login`. */\n prompt?: string;\n /** Default `pontive_session`. */\n cookieName?: string;\n /** Default: whether the public origin is https. */\n secureCookies?: boolean;\n /** The session cookie's lifetime. Default 7 days. */\n sessionMaxAgeSeconds?: number;\n /** Refresh when fewer than this many seconds remain. Default 60. */\n refreshSkewSeconds?: number;\n /** Defaults to the global `fetch`. */\n fetch?: typeof fetch;\n /** Where the SDK reports a failed refresh or sign-in. Defaults to `console`. */\n logger?: Pick<Console, \"warn\">;\n}\n\nexport interface ResolvedConfig {\n /** `server`: a confidential app holding its own session. `browser`: the browser SDK's session, read. */\n mode: \"server\" | \"browser\";\n authPath: string;\n issuer: string;\n clientId: string;\n clientSecret?: string;\n sessionSecret: string;\n baseUrl?: string;\n scopes: string;\n basePath: string;\n redirectUri?: string;\n signInPath: string;\n afterSignInPath: string;\n prompt?: string;\n cookieName: string;\n secureCookies?: boolean;\n sessionMaxAgeSeconds: number;\n refreshSkewSeconds: number;\n fetch: typeof fetch;\n logger: Pick<Console, \"warn\">;\n}\n\nconst SEVEN_DAYS = 7 * 24 * 60 * 60;\n\nexport function resolveConfig(config: ServerAuthConfig): ResolvedConfig {\n const mode = config.clientSecret ? \"server\" : \"browser\";\n\n const required: (keyof ServerAuthConfig)[] = [\"issuer\", \"clientId\"];\n if (mode === \"server\") required.push(\"sessionSecret\");\n for (const key of required) {\n if (!config[key]) throw new Error(`@pontive/pontkit-server: ${key} is required`);\n }\n\n if (mode === \"server\" && config.sessionSecret!.length < 32) {\n throw new Error(\"@pontive/pontkit-server: sessionSecret must be at least 32 characters\");\n }\n\n if (config.baseUrl) {\n try {\n new URL(config.baseUrl);\n } catch {\n throw new Error(`@pontive/pontkit-server: baseUrl \"${config.baseUrl}\" is not an absolute URL`);\n }\n }\n\n return {\n mode,\n authPath: (config.authPath ?? \"/__auth\").replace(/\\/+$/, \"\"),\n issuer: config.issuer.replace(/\\/+$/, \"\"),\n clientId: config.clientId,\n clientSecret: config.clientSecret || undefined,\n sessionSecret: config.sessionSecret ?? \"\",\n baseUrl: config.baseUrl ? new URL(config.baseUrl).origin : undefined,\n scopes: config.scopes ?? \"openid email profile offline_access\",\n basePath: (config.basePath ?? \"/api/auth\").replace(/\\/+$/, \"\"),\n redirectUri: config.redirectUri,\n signInPath: config.signInPath ?? \"/login\",\n afterSignInPath: config.afterSignInPath ?? \"/\",\n prompt: config.prompt,\n cookieName: config.cookieName ?? \"pontive_session\",\n secureCookies: config.secureCookies,\n sessionMaxAgeSeconds: config.sessionMaxAgeSeconds ?? SEVEN_DAYS,\n refreshSkewSeconds: config.refreshSkewSeconds ?? 60,\n fetch: config.fetch ?? ((...args) => fetch(...args)),\n logger: config.logger ?? console,\n };\n}\n","import { sha256Hex } from \"./oidc\";\n\n/**\n * One refresh per session, however many requests ask for it.\n *\n * With rotation on, every refresh spends the token it presents. A page that\n * fires several requests the moment its access token expires would otherwise\n * refresh once per request, and every request after the first would present a\n * token the first had already spent — which the issuer reads as theft.\n *\n * Two layers, in this process:\n *\n * - In flight: requests for the same refresh token share one call.\n * - Recently done: a request that arrives a moment later still holding the old\n * cookie (its browser had not yet stored the new one) is handed the same\n * result rather than spending the old token again.\n *\n * Across processes — several replicas of the app — the issuer's reuse grace\n * window covers the same race, so no shared lock is needed.\n */\n\n/** How long a result is handed to late arrivals. Matches the issuer's grace window. */\nexport const RECENT_RESULT_MS = 30_000;\n\nexport class RefreshCoordinator<T> {\n private readonly inFlight = new Map<string, Promise<T>>();\n private readonly recent = new Map<string, { at: number; result: T }>();\n\n constructor(private readonly recentMs: number = RECENT_RESULT_MS) {}\n\n async run(refreshToken: string, refresh: () => Promise<T>): Promise<T> {\n const key = await sha256Hex(refreshToken);\n this.sweep();\n\n const done = this.recent.get(key);\n if (done) return done.result;\n\n let pending = this.inFlight.get(key);\n if (!pending) {\n pending = refresh()\n .then((result) => {\n this.recent.set(key, { at: Date.now(), result });\n return result;\n })\n .finally(() => this.inFlight.delete(key));\n this.inFlight.set(key, pending);\n }\n\n return pending;\n }\n\n private sweep(): void {\n const cutoff = Date.now() - this.recentMs;\n for (const [key, entry] of this.recent) {\n if (entry.at < cutoff) this.recent.delete(key);\n }\n }\n}\n","import { EncryptJWT, jwtDecrypt } from \"jose\";\n\n/**\n * Sealing: authenticated encryption (JWE, `dir` + A256GCM) of whatever the\n * server stores in a cookie. The browser holds ciphertext it can neither read\n * nor alter, which is what lets a refresh token live in a cookie at all.\n */\n\nconst keys = new Map<string, Promise<Uint8Array>>();\n\n/** A 256-bit key derived from the configured secret, whatever its length. */\nfunction keyFor(secret: string): Promise<Uint8Array> {\n let key = keys.get(secret);\n if (!key) {\n key = crypto.subtle\n .digest(\"SHA-256\", new TextEncoder().encode(secret))\n .then((digest) => new Uint8Array(digest));\n keys.set(secret, key);\n }\n\n return key;\n}\n\nexport async function seal(payload: Record<string, unknown>, secret: string, maxAgeSeconds: number): Promise<string> {\n return new EncryptJWT(payload)\n .setProtectedHeader({ alg: \"dir\", enc: \"A256GCM\" })\n .setIssuedAt()\n .setExpirationTime(`${maxAgeSeconds}s`)\n .encrypt(await keyFor(secret));\n}\n\n/** The payload, or null when the value is not one this secret sealed, or has expired. */\nexport async function unseal<T>(value: string, secret: string): Promise<T | null> {\n try {\n const { payload } = await jwtDecrypt(value, await keyFor(secret));\n return payload as T;\n } catch {\n return null;\n }\n}\n","import type { JWTPayload } from \"jose\";\n\nimport { createBrowserAuth } from \"./browser-mode\";\nimport { resolveConfig, type ResolvedConfig, type ServerAuthConfig } from \"./config\";\nimport {\n clearChunked,\n formatCookieHeader,\n parseCookies,\n readChunked,\n serializeCookie,\n writeChunked,\n} from \"./cookies\";\nimport {\n authorizationUrl,\n endSessionUrl,\n exchangeCode,\n pkceChallenge,\n randomValue,\n refreshTokens,\n TokenEndpointError,\n verifyIdToken,\n type ClientCredentials,\n type TokenResponse,\n} from \"./oidc\";\nimport { RefreshCoordinator } from \"./refresh\";\nimport { seal, unseal } from \"./seal\";\n\nexport interface SessionUser {\n id: string;\n email?: string;\n name?: string;\n username?: string;\n picture?: string;\n emailVerified: boolean;\n}\n\n/** What `getSession` hands the app. */\nexport interface Session {\n user: SessionUser;\n /** A valid access token, for calling APIs as this user. */\n accessToken: string;\n /** When the access token expires, epoch milliseconds. */\n expiresAt: number;\n /** The issuer's session id (`sid`), when it sent one. */\n sessionId?: string;\n}\n\n/** What the cookie holds: the session plus the refresh token, which the app never sees. */\ninterface StoredSession extends Session {\n refreshToken?: string;\n}\n\n/** A sign-in in progress, sealed into its own short-lived cookie. */\ninterface SignInTransaction {\n state: string;\n verifier: string;\n nonce: string;\n returnTo: string;\n}\n\n/** What a refresh produced, for the middleware to apply. */\nexport interface RefreshOutcome {\n /** The session after the refresh, or null when there is none (or it could not be refreshed). */\n session: Session | null;\n /** `Set-Cookie` values for the response. Empty when nothing changed. */\n setCookies: string[];\n /**\n * The request's `Cookie` header as it should be seen by whatever handles\n * this request next, carrying the refreshed session. Forward it, so a page\n * rendered on this very request reads the new token rather than the expired\n * one the browser sent.\n */\n cookieHeader: string;\n /**\n * Set when a signed-in browser's access token has lapsed and it must go\n * through the auth server's session handshake to renew it — browser mode\n * only. A path on this app's origin. Send a page navigation there; any other\n * request just proceeds without a session.\n */\n handshake?: string;\n}\n\nexport interface ServerAuth {\n /**\n * Handles the SDK's routes under `basePath`: `/login`, `/callback` and\n * `/logout`. Null for any other path.\n */\n handle(request: Request): Promise<Response | null>;\n /**\n * The session in a `Cookie` header, or null. Read-only and never refreshes:\n * any server code can call it, including code that cannot set cookies.\n * Returns null for a session whose access token has expired — `refresh`,\n * run in middleware ahead of the request, is what keeps it current.\n */\n getSession(cookieHeader: string | null | undefined): Promise<Session | null>;\n /**\n * Refreshes the session when its access token is about to expire. For\n * middleware: it runs before anything renders, and its response can always\n * carry the new cookie. Concurrent calls for one session share one refresh.\n */\n refresh(request: Request): Promise<RefreshOutcome>;\n}\n\nconst DEFAULT_ACCESS_TTL_SECONDS = 900;\nconst TRANSACTION_MAX_AGE = 600;\n\n/** Only a same-origin path may be returned to after sign-in: no open redirect. */\nfunction safePath(value: string | null | undefined, fallback: string): string {\n if (value && value.startsWith(\"/\") && !value.startsWith(\"//\") && !value.startsWith(\"/\\\\\")) return value;\n return fallback;\n}\n\nfunction redirect(location: string, setCookies: string[] = []): Response {\n const headers = new Headers({ location, \"cache-control\": \"no-store\" });\n for (const cookie of setCookies) headers.append(\"set-cookie\", cookie);\n\n return new Response(null, { status: 302, headers });\n}\n\nfunction publicOrigin(cfg: ResolvedConfig, request: Request): string {\n if (cfg.baseUrl) return cfg.baseUrl;\n\n const host = request.headers.get(\"x-forwarded-host\")?.split(\",\")[0]?.trim() || request.headers.get(\"host\")?.trim();\n if (host) {\n const proto =\n request.headers.get(\"x-forwarded-proto\")?.split(\",\")[0]?.trim() || new URL(request.url).protocol.replace(\":\", \"\");\n try {\n return new URL(`${proto}://${host}`).origin;\n } catch {\n // Fall through to the request URL.\n }\n }\n\n return new URL(request.url).origin;\n}\n\nfunction userFrom(claims: JWTPayload): SessionUser {\n const str = (key: string) => (typeof claims[key] === \"string\" ? (claims[key] as string) : undefined);\n const name =\n str(\"name\") ?? ([str(\"given_name\"), str(\"family_name\")].filter(Boolean).join(\" \") || undefined) ?? str(\"email\");\n\n return {\n id: claims.sub ?? \"\",\n email: str(\"email\"),\n name,\n username: str(\"preferred_username\"),\n picture: str(\"picture\"),\n emailVerified: claims.email_verified === true,\n };\n}\n\nfunction expiresAtFrom(token: TokenResponse): number {\n return Date.now() + (token.expires_in ?? DEFAULT_ACCESS_TTL_SECONDS) * 1000;\n}\n\nexport function createServerAuth(config: ServerAuthConfig): ServerAuth {\n const cfg = resolveConfig(config);\n if (cfg.mode === \"browser\") return createBrowserAuth(cfg);\n\n const client: ClientCredentials = { issuer: cfg.issuer, clientId: cfg.clientId, clientSecret: cfg.clientSecret };\n const txCookie = `${cfg.cookieName}_tx`;\n const coordinator = new RefreshCoordinator<StoredSession | null>();\n\n const secure = (request: Request) => cfg.secureCookies ?? publicOrigin(cfg, request).startsWith(\"https:\");\n const redirectUri = (request: Request) => cfg.redirectUri ?? `${publicOrigin(cfg, request)}${cfg.basePath}/callback`;\n const toSignIn = (request: Request, error?: string) => {\n const url = new URL(cfg.signInPath, publicOrigin(cfg, request));\n if (error) url.searchParams.set(\"error\", error);\n return url.toString();\n };\n\n async function readStored(cookies: Map<string, string>): Promise<StoredSession | null> {\n const raw = readChunked(cookies, cfg.cookieName);\n if (raw === null) return null;\n\n const stored = await unseal<StoredSession>(raw, cfg.sessionSecret);\n if (!stored) cfg.logger.warn(\"[pontive] session cookie could not be read (sealed with another secret, or truncated)\");\n\n return stored;\n }\n\n async function writeStored(cookies: Map<string, string>, stored: StoredSession, request: Request): Promise<string[]> {\n const sealed = await seal({ ...stored }, cfg.sessionSecret, cfg.sessionMaxAgeSeconds);\n\n return writeChunked(cookies, cfg.cookieName, sealed, { maxAge: cfg.sessionMaxAgeSeconds, secure: secure(request) });\n }\n\n function publicSession(stored: StoredSession): Session {\n const { refreshToken: _refreshToken, ...session } = stored;\n return session;\n }\n\n async function signIn(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const tx: SignInTransaction = {\n state: randomValue(),\n verifier: randomValue(),\n nonce: randomValue(),\n returnTo: safePath(url.searchParams.get(\"returnTo\") ?? url.searchParams.get(\"next\"), cfg.afterSignInPath),\n };\n\n const location = await authorizationUrl(client, cfg.fetch, {\n redirectUri: redirectUri(request),\n scopes: cfg.scopes,\n state: tx.state,\n nonce: tx.nonce,\n codeChallenge: await pkceChallenge(tx.verifier),\n prompt: cfg.prompt,\n });\n\n const sealed = await seal({ ...tx }, cfg.sessionSecret, TRANSACTION_MAX_AGE);\n\n return redirect(location, [serializeCookie(txCookie, sealed, { maxAge: TRANSACTION_MAX_AGE, secure: secure(request) })]);\n }\n\n async function callback(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n // Single use, whatever happens next.\n const setCookies = [serializeCookie(txCookie, \"\", { maxAge: 0, secure: secure(request) })];\n\n const issuerError = url.searchParams.get(\"error\");\n if (issuerError) return redirect(toSignIn(request, issuerError), setCookies);\n\n const raw = cookies.get(txCookie);\n const tx = raw ? await unseal<SignInTransaction>(raw, cfg.sessionSecret) : null;\n const code = url.searchParams.get(\"code\");\n\n if (!tx || !code || url.searchParams.get(\"state\") !== tx.state) {\n return redirect(toSignIn(request, \"invalid_state\"), setCookies);\n }\n\n try {\n const tokens = await exchangeCode(client, cfg.fetch, {\n code,\n codeVerifier: tx.verifier,\n redirectUri: redirectUri(request),\n });\n if (!tokens.id_token) throw new Error(\"the token response carried no id_token; is the openid scope requested?\");\n\n const claims = await verifyIdToken(client, cfg.fetch, tokens.id_token, tx.nonce);\n const stored: StoredSession = {\n user: userFrom(claims),\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresAt: expiresAtFrom(tokens),\n sessionId: typeof claims.sid === \"string\" ? claims.sid : undefined,\n };\n\n setCookies.push(...(await writeStored(cookies, stored, request)));\n } catch (err) {\n cfg.logger.warn(\"[pontive] sign-in failed:\", err instanceof Error ? err.message : String(err));\n return redirect(toSignIn(request, \"exchange_failed\"), setCookies);\n }\n\n return redirect(new URL(tx.returnTo, publicOrigin(cfg, request)).toString(), setCookies);\n }\n\n async function signOut(request: Request): Promise<Response> {\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n // Read before clearing: the session holds the sid the issuer's logout needs.\n const stored = await readStored(cookies);\n const setCookies = clearChunked(cookies, cfg.cookieName, secure(request));\n\n let location = toSignIn(request);\n try {\n location =\n (await endSessionUrl(client, cfg.fetch, {\n postLogoutRedirectUri: toSignIn(request),\n sessionId: stored?.sessionId,\n })) ?? location;\n } catch (err) {\n // An unreachable issuer must not stop the local sign-out.\n cfg.logger.warn(\"[pontive] could not reach the issuer to end its session:\", err instanceof Error ? err.message : String(err));\n }\n\n return redirect(location, setCookies);\n }\n\n /**\n * Spends the refresh token once, returning the refreshed session, or null\n * when the issuer refused it for good. Anything transient throws instead, so\n * the coordinator does not hand a passing failure to other requests.\n */\n async function refreshStored(stored: StoredSession): Promise<StoredSession | null> {\n try {\n const tokens = await refreshTokens(client, cfg.fetch, stored.refreshToken!);\n\n return {\n ...stored,\n accessToken: tokens.access_token,\n // A response without one means rotation is off: the token in hand is still current.\n refreshToken: tokens.refresh_token ?? stored.refreshToken,\n expiresAt: expiresAtFrom(tokens),\n };\n } catch (err) {\n if (err instanceof TokenEndpointError && err.status >= 400 && err.status < 500) {\n cfg.logger.warn(\"[pontive] refresh refused; signing out:\", err.message);\n return null;\n }\n\n throw err;\n }\n }\n\n return {\n async handle(request) {\n const { pathname } = new URL(request.url);\n if (!pathname.startsWith(`${cfg.basePath}/`)) return null;\n\n const route = pathname.slice(cfg.basePath.length + 1);\n if (route === \"login\" && request.method === \"GET\") return signIn(request);\n if (route === \"callback\" && request.method === \"GET\") return callback(request);\n if (route === \"logout\" && (request.method === \"GET\" || request.method === \"POST\")) return signOut(request);\n\n return null;\n },\n\n async getSession(cookieHeader) {\n const stored = await readStored(parseCookies(cookieHeader));\n if (!stored) return null;\n\n if (Date.now() >= stored.expiresAt) {\n cfg.logger.warn(\n \"[pontive] the session's access token has expired and getSession cannot refresh it. \" +\n \"Run the SDK's middleware on this path so it refreshes before the request is handled.\",\n );\n return null;\n }\n\n return publicSession(stored);\n },\n\n async refresh(request) {\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n const unchanged = (session: Session | null): RefreshOutcome => ({\n session,\n setCookies: [],\n cookieHeader: request.headers.get(\"cookie\") ?? \"\",\n });\n\n const stored = await readStored(cookies);\n if (!stored) return unchanged(null);\n\n // The SDK's own routes manage the session themselves. Refreshing ahead of\n // them only spends a refresh token that sign-out is about to discard, and\n // puts a second session cookie on the response it clears.\n if (new URL(request.url).pathname.startsWith(`${cfg.basePath}/`)) {\n return unchanged(Date.now() < stored.expiresAt ? publicSession(stored) : null);\n }\n\n if (Date.now() < stored.expiresAt - cfg.refreshSkewSeconds * 1000) return unchanged(publicSession(stored));\n\n const signOutNow = (): RefreshOutcome => {\n const setCookies = clearChunked(cookies, cfg.cookieName, secure(request));\n return { session: null, setCookies, cookieHeader: formatCookieHeader(cookies) };\n };\n\n if (!stored.refreshToken) return signOutNow();\n\n let refreshed: StoredSession | null;\n try {\n refreshed = await coordinator.run(stored.refreshToken, () => refreshStored(stored));\n } catch (err) {\n // Transient: keep the session and let the next request try again. The\n // token may still have a few seconds of the skew left.\n cfg.logger.warn(\"[pontive] refresh failed, will retry:\", err instanceof Error ? err.message : String(err));\n return unchanged(Date.now() < stored.expiresAt ? publicSession(stored) : null);\n }\n\n if (!refreshed) return signOutNow();\n\n const setCookies = await writeStored(cookies, refreshed, request);\n\n return { session: publicSession(refreshed), setCookies, cookieHeader: formatCookieHeader(cookies) };\n },\n };\n}\n"],"names":["CHUNK_SIZE","MAX_CHUNKS","parseCookies","header","out","part","eq","name","formatCookieHeader","cookies","value","serializeCookie","attrs","parts","chunkName","index","readChunked","first","i","writeChunked","count","chunk","stale","clearChunked","secure","TokenEndpointError","status","code","description","JWKS_TTL_MS","discoveries","jwksCache","trimSlash","discover","issuer","fetchImpl","key","pending","res","err","jwks","uri","force","cached","set","createLocalJWKSet","base64url","bytes","binary","b","randomValue","pkceChallenge","verifier","digest","sha256Hex","authorizationUrl","client","input","disc","url","tokenRequest","params","body","headers","credentials","exchangeCode","refreshTokens","refreshToken","verifyJwt","token","options","jwtVerify","verifyIdToken","idToken","nonce","payload","endSessionUrl","ACCESS_COOKIE","SESSION_HINT_COOKIE","userFrom","claims","str","createBrowserAuth","cfg","read","cookieHeader","request","session","outcome","handshake","SEVEN_DAYS","resolveConfig","config","mode","required","args","RECENT_RESULT_MS","RefreshCoordinator","recentMs","refresh","done","result","cutoff","entry","keys","keyFor","secret","seal","maxAgeSeconds","EncryptJWT","unseal","jwtDecrypt","DEFAULT_ACCESS_TTL_SECONDS","TRANSACTION_MAX_AGE","safePath","fallback","redirect","location","setCookies","cookie","publicOrigin","host","proto","expiresAtFrom","createServerAuth","txCookie","coordinator","redirectUri","toSignIn","error","readStored","raw","stored","writeStored","sealed","publicSession","_refreshToken","signIn","tx","callback","issuerError","tokens","signOut","refreshStored","pathname","route","unchanged","signOutNow","refreshed"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/cookies.ts","../src/oidc.ts","../src/browser-mode.ts","../src/config.ts","../src/refresh.ts","../src/seal.ts","../src/server-auth.ts"],"sourcesContent":["/**\n * Cookie reading and writing, on plain header strings so it runs anywhere a\n * Web `Request`/`Response` does.\n *\n * A sealed session outgrows one cookie (browsers cap each at about 4 KB, and an\n * access token, a refresh token and a user fit in about 3–5), so it is split\n * across numbered chunks: `name`, `name.1`, `name.2`, … and joined on read.\n */\n\n/** Characters per chunk, leaving room under the ~4096-byte cap for the name and attributes. */\nexport const CHUNK_SIZE = 3500;\n/** Upper bound on chunks probed when reading or clearing. */\nexport const MAX_CHUNKS = 16;\n\nexport interface CookieAttributes {\n /** Seconds; 0 deletes the cookie. */\n maxAge: number;\n secure: boolean;\n}\n\n/** Parses a `Cookie` request header into name → value. */\nexport function parseCookies(header: string | null | undefined): Map<string, string> {\n const out = new Map<string, string>();\n if (!header) return out;\n\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq < 0) continue;\n\n const name = part.slice(0, eq).trim();\n if (!name || out.has(name)) continue;\n\n out.set(name, part.slice(eq + 1).trim());\n }\n\n return out;\n}\n\n/** Serialises a `Cookie` request header from name → value. */\nexport function formatCookieHeader(cookies: Map<string, string>): string {\n return [...cookies].map(([name, value]) => `${name}=${value}`).join(\"; \");\n}\n\n/**\n * A `Set-Cookie` value. Always HttpOnly, host-only, `Path=/` and\n * `SameSite=Lax`: Lax is what lets the cookie ride the top-level redirect back\n * from the sign-in page, and nothing here is ever for script.\n */\nexport function serializeCookie(name: string, value: string, attrs: CookieAttributes): string {\n const parts = [`${name}=${value}`, \"Path=/\", \"HttpOnly\", \"SameSite=Lax\", `Max-Age=${attrs.maxAge}`];\n if (attrs.secure) parts.push(\"Secure\");\n\n return parts.join(\"; \");\n}\n\nexport function chunkName(name: string, index: number): string {\n return index === 0 ? name : `${name}.${index}`;\n}\n\n/** Joins a chunked value, or null when its first chunk is absent. */\nexport function readChunked(cookies: Map<string, string>, name: string): string | null {\n const first = cookies.get(name);\n if (first === undefined) return null;\n\n let value = first;\n for (let i = 1; i < MAX_CHUNKS; i++) {\n const part = cookies.get(chunkName(name, i));\n if (part === undefined) break;\n value += part;\n }\n\n return value;\n}\n\n/**\n * The `Set-Cookie` values that store `value` in chunks, and delete any chunk\n * the request carried beyond what it now needs. Also updates `cookies` in\n * place, so a caller can forward the new state on the same request.\n */\nexport function writeChunked(\n cookies: Map<string, string>,\n name: string,\n value: string,\n attrs: CookieAttributes,\n): string[] {\n const out: string[] = [];\n const count = Math.max(1, Math.ceil(value.length / CHUNK_SIZE));\n\n for (let i = 0; i < count; i++) {\n const chunk = value.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);\n cookies.set(chunkName(name, i), chunk);\n out.push(serializeCookie(chunkName(name, i), chunk, attrs));\n }\n\n for (let i = count; i < MAX_CHUNKS; i++) {\n const stale = chunkName(name, i);\n if (!cookies.has(stale)) break;\n cookies.delete(stale);\n out.push(serializeCookie(stale, \"\", { ...attrs, maxAge: 0 }));\n }\n\n return out;\n}\n\n/** The `Set-Cookie` values that delete every chunk the request carried. */\nexport function clearChunked(cookies: Map<string, string>, name: string, secure: boolean): string[] {\n const out: string[] = [];\n\n for (let i = 0; i < MAX_CHUNKS; i++) {\n const chunk = chunkName(name, i);\n if (!cookies.has(chunk)) {\n if (i > 0) break;\n continue;\n }\n cookies.delete(chunk);\n out.push(serializeCookie(chunk, \"\", { maxAge: 0, secure }));\n }\n\n return out;\n}\n","import { createLocalJWKSet, jwtVerify, type JSONWebKeySet, type JWTPayload } from \"jose\";\n\n/**\n * The OIDC relying-party calls: discovery, PKCE, the two token grants and\n * id_token verification. Standard OIDC only, so any compliant issuer works.\n */\n\nexport interface Discovery {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n jwks_uri: string;\n userinfo_endpoint?: string;\n end_session_endpoint?: string;\n}\n\nexport interface TokenResponse {\n access_token: string;\n token_type: string;\n expires_in?: number;\n refresh_token?: string;\n id_token?: string;\n scope?: string;\n}\n\nexport interface ClientCredentials {\n issuer: string;\n clientId: string;\n clientSecret?: string;\n}\n\n/** An error from the token endpoint, carrying its OAuth code. */\nexport class TokenEndpointError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n description: string,\n ) {\n super(`token endpoint ${status} ${code}: ${description}`);\n }\n}\n\ntype Fetch = typeof fetch;\n\nconst JWKS_TTL_MS = 10 * 60 * 1000;\n\nconst discoveries = new Map<string, Promise<Discovery>>();\nconst jwksCache = new Map<string, { at: number; set: ReturnType<typeof createLocalJWKSet> }>();\n\nfunction trimSlash(value: string): string {\n return value.replace(/\\/+$/, \"\");\n}\n\nexport function discover(issuer: string, fetchImpl: Fetch): Promise<Discovery> {\n const key = trimSlash(issuer);\n let pending = discoveries.get(key);\n\n if (!pending) {\n pending = fetchImpl(`${key}/.well-known/openid-configuration`)\n .then(async (res) => {\n if (!res.ok) throw new Error(`OIDC discovery failed for ${key}: ${res.status}`);\n return (await res.json()) as Discovery;\n })\n .catch((err: unknown) => {\n // A failure is not cached: the next request retries.\n discoveries.delete(key);\n throw err;\n });\n discoveries.set(key, pending);\n }\n\n return pending;\n}\n\nasync function jwks(uri: string, fetchImpl: Fetch, force: boolean): Promise<ReturnType<typeof createLocalJWKSet>> {\n const cached = jwksCache.get(uri);\n if (cached && !force && Date.now() - cached.at < JWKS_TTL_MS) return cached.set;\n\n const res = await fetchImpl(uri);\n if (!res.ok) throw new Error(`JWKS fetch failed for ${uri}: ${res.status}`);\n\n const set = createLocalJWKSet((await res.json()) as JSONWebKeySet);\n jwksCache.set(uri, { at: Date.now(), set });\n\n return set;\n}\n\nfunction base64url(bytes: Uint8Array): string {\n let binary = \"\";\n for (const b of bytes) binary += String.fromCharCode(b);\n\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** A random, URL-safe value for state, nonce and the PKCE verifier. */\nexport function randomValue(): string {\n return base64url(crypto.getRandomValues(new Uint8Array(32)));\n}\n\nexport async function pkceChallenge(verifier: string): Promise<string> {\n const digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(verifier));\n\n return base64url(new Uint8Array(digest));\n}\n\n/** Hex SHA-256, for keying state by a secret without holding the secret. */\nexport async function sha256Hex(value: string): Promise<string> {\n const digest = new Uint8Array(await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(value)));\n\n return [...digest].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport async function authorizationUrl(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { redirectUri: string; scopes: string; state: string; nonce: string; codeChallenge: string; prompt?: string },\n): Promise<string> {\n const disc = await discover(client.issuer, fetchImpl);\n const url = new URL(disc.authorization_endpoint);\n\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", client.clientId);\n url.searchParams.set(\"redirect_uri\", input.redirectUri);\n url.searchParams.set(\"scope\", input.scopes);\n url.searchParams.set(\"state\", input.state);\n url.searchParams.set(\"nonce\", input.nonce);\n url.searchParams.set(\"code_challenge\", input.codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n if (input.prompt) url.searchParams.set(\"prompt\", input.prompt);\n\n return url.toString();\n}\n\n/**\n * A token-endpoint call. A confidential client authenticates with HTTP Basic\n * (RFC 6749 §2.3.1); `client_id` is always in the form too, which a public\n * client needs and a confidential one is allowed.\n */\nasync function tokenRequest(\n client: ClientCredentials,\n fetchImpl: Fetch,\n params: Record<string, string>,\n): Promise<TokenResponse> {\n const disc = await discover(client.issuer, fetchImpl);\n const body = new URLSearchParams(params);\n body.set(\"client_id\", client.clientId);\n\n const headers: Record<string, string> = { \"content-type\": \"application/x-www-form-urlencoded\" };\n if (client.clientSecret) {\n const credentials = `${encodeURIComponent(client.clientId)}:${encodeURIComponent(client.clientSecret)}`;\n headers.authorization = `Basic ${btoa(credentials)}`;\n }\n\n const res = await fetchImpl(disc.token_endpoint, { method: \"POST\", headers, body });\n if (!res.ok) {\n let code = \"unknown_error\";\n let description = \"\";\n\n try {\n const err = (await res.json()) as { error?: string; error_description?: string };\n code = err.error ?? code;\n description = err.error_description ?? \"\";\n } catch {\n // Not JSON; the status says enough.\n }\n\n throw new TokenEndpointError(res.status, code, description);\n }\n\n return (await res.json()) as TokenResponse;\n}\n\nexport function exchangeCode(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { code: string; codeVerifier: string; redirectUri: string },\n): Promise<TokenResponse> {\n return tokenRequest(client, fetchImpl, {\n grant_type: \"authorization_code\",\n code: input.code,\n code_verifier: input.codeVerifier,\n redirect_uri: input.redirectUri,\n });\n}\n\nexport function refreshTokens(client: ClientCredentials, fetchImpl: Fetch, refreshToken: string): Promise<TokenResponse> {\n return tokenRequest(client, fetchImpl, { grant_type: \"refresh_token\", refresh_token: refreshToken });\n}\n\n/**\n * Verifies a JWT the issuer signed for this app: signature, issuer, audience\n * and expiry. A key the cached set does not know refetches the set once, which\n * is how a key rotation at the issuer is picked up without waiting out the\n * cache.\n */\nexport async function verifyJwt(client: ClientCredentials, fetchImpl: Fetch, token: string): Promise<JWTPayload> {\n const disc = await discover(client.issuer, fetchImpl);\n const options = { issuer: disc.issuer, audience: client.clientId };\n\n try {\n return (await jwtVerify(token, await jwks(disc.jwks_uri, fetchImpl, false), options)).payload;\n } catch (err) {\n if ((err as { code?: string }).code !== \"ERR_JWKS_NO_MATCHING_KEY\") throw err;\n return (await jwtVerify(token, await jwks(disc.jwks_uri, fetchImpl, true), options)).payload;\n }\n}\n\n/** Verifies an id_token as verifyJwt does, plus the nonce of the sign-in it answers. */\nexport async function verifyIdToken(\n client: ClientCredentials,\n fetchImpl: Fetch,\n idToken: string,\n nonce: string,\n): Promise<JWTPayload> {\n const payload = await verifyJwt(client, fetchImpl, idToken);\n if (payload.nonce !== nonce) throw new Error(\"id_token nonce does not match the sign-in request\");\n\n return payload;\n}\n\n/**\n * The user's profile claims (OIDC Core §5.3), for an access token that carries\n * none of its own. Null when the issuer offers no userinfo endpoint; throws\n * when it refuses.\n */\nexport async function userInfo(\n client: ClientCredentials,\n fetchImpl: Fetch,\n accessToken: string,\n): Promise<Record<string, unknown> | null> {\n const disc = await discover(client.issuer, fetchImpl);\n if (!disc.userinfo_endpoint) return null;\n\n const res = await fetchImpl(disc.userinfo_endpoint, { headers: { authorization: `Bearer ${accessToken}` } });\n if (!res.ok) throw new Error(`userinfo ${res.status}`);\n\n return (await res.json()) as Record<string, unknown>;\n}\n\n/** RP-initiated logout (OIDC RP-Initiated Logout 1.0), or null when the issuer offers none. */\nexport async function endSessionUrl(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { postLogoutRedirectUri: string; sessionId?: string },\n): Promise<string | null> {\n const disc = await discover(client.issuer, fetchImpl);\n if (!disc.end_session_endpoint) return null;\n\n const url = new URL(disc.end_session_endpoint);\n url.searchParams.set(\"client_id\", client.clientId);\n url.searchParams.set(\"post_logout_redirect_uri\", input.postLogoutRedirectUri);\n // logout_hint is what lets the issuer end its session without asking \"Sign\n // out?\": only an app holding tokens from that session knows the sid.\n if (input.sessionId) url.searchParams.set(\"logout_hint\", input.sessionId);\n\n return url.toString();\n}\n\n/** For tests: forget cached discovery documents and key sets. */\nexport function resetOidcCaches(): void {\n discoveries.clear();\n jwksCache.clear();\n}\n","import type { JWTPayload } from \"jose\";\n\nimport type { ResolvedConfig } from \"./config\";\nimport { parseCookies } from \"./cookies\";\nimport { sha256Hex, userInfo, verifyJwt, type ClientCredentials } from \"./oidc\";\nimport type { RefreshOutcome, ServerAuth, Session, SessionUser } from \"./server-auth\";\n\n/**\n * Server-side sessions for an app whose browser holds the session: a regular\n * app, or a public oauth app, on the browser SDK.\n *\n * The browser SDK signs the user in and is the only thing that ever spends the\n * refresh token. The auth server, reached through the auth proxy on this app's\n * origin, keeps an HttpOnly cookie holding the current access token. The server\n * reads and verifies that cookie, and nothing more — so there is exactly one\n * refresher per session, and rotation never sees a token spent twice.\n *\n * When a page navigation arrives with no live access token but with the\n * session marker, the access token has lapsed while the user is still signed\n * in (the tab was closed, or idle past its refresh). The browser is sent\n * through the auth server's session handshake, which renews the cookies and\n * brings it straight back.\n */\n\n/** The access token, written by the auth server wherever it writes the refresh cookie. */\nexport const ACCESS_COOKIE = \"__Host-pontive_access\";\n/** The readable marker: the browser holds a refresh token. */\nexport const SESSION_HINT_COOKIE = \"__Secure-pontive_session_state\";\n\nfunction userFrom(claims: Record<string, unknown>): SessionUser {\n const str = (key: string) => (typeof claims[key] === \"string\" ? (claims[key] as string) : undefined);\n\n return {\n id: str(\"sub\") ?? \"\",\n email: str(\"email\"),\n name: str(\"name\") ?? str(\"email\"),\n username: str(\"preferred_username\"),\n picture: str(\"picture\"),\n emailVerified: claims.email_verified === true,\n };\n}\n\nexport function createBrowserAuth(cfg: ResolvedConfig): ServerAuth {\n const client: ClientCredentials = { issuer: cfg.issuer, clientId: cfg.clientId };\n\n // Profiles by access token, until the token expires. An access token carries\n // who the user is (sub) but not their email or name, so the profile comes\n // from userinfo — once per token per process, not once per request. Keyed by\n // a hash so the map never holds a live credential.\n const profiles = new Map<string, { expiresAt: number; profile: Promise<Record<string, unknown> | null> }>();\n\n function sweep(now: number) {\n for (const [key, entry] of profiles) {\n if (entry.expiresAt <= now) profiles.delete(key);\n }\n }\n\n async function profileFor(token: string, expiresAt: number): Promise<Record<string, unknown> | null> {\n const now = Date.now();\n sweep(now);\n\n const key = await sha256Hex(token);\n const cached = profiles.get(key);\n if (cached) return cached.profile;\n\n const profile = userInfo(client, cfg.fetch, token).catch((err: unknown) => {\n // The session is valid without a profile: fall back to what the token\n // says, and let the next request try again.\n profiles.delete(key);\n cfg.logger.warn(\"[pontive] could not read the user's profile:\", err instanceof Error ? err.message : String(err));\n return null;\n });\n profiles.set(key, { expiresAt, profile });\n\n return profile;\n }\n\n async function read(cookieHeader: string | null | undefined): Promise<Session | null> {\n const token = parseCookies(cookieHeader).get(ACCESS_COOKIE);\n if (!token) return null;\n\n let claims: JWTPayload;\n try {\n claims = await verifyJwt(client, cfg.fetch, token);\n } catch (err) {\n // An expired token is the ordinary case — the handshake renews it — and\n // not worth a warning. Anything else is.\n if ((err as { code?: string }).code !== \"ERR_JWT_EXPIRED\") {\n cfg.logger.warn(\"[pontive] access token cookie refused:\", err instanceof Error ? err.message : String(err));\n }\n\n return null;\n }\n\n const expiresAt = (claims.exp ?? 0) * 1000;\n const profile = await profileFor(token, expiresAt);\n\n return {\n // The token's own claims win for sub: the profile describes the user the\n // token names, never another one.\n user: userFrom({ ...profile, ...claims, sub: claims.sub }),\n accessToken: token,\n expiresAt,\n sessionId: typeof claims.sid === \"string\" ? claims.sid : undefined,\n };\n }\n\n return {\n // Sign-in and sign-out belong to the browser SDK's widgets in this mode.\n async handle() {\n return null;\n },\n\n getSession: read,\n\n async refresh(request) {\n const cookieHeader = request.headers.get(\"cookie\");\n const session = await read(cookieHeader);\n const outcome: RefreshOutcome = { session, setCookies: [], cookieHeader: cookieHeader ?? \"\" };\n\n if (session || !parseCookies(cookieHeader).has(SESSION_HINT_COOKIE)) return outcome;\n\n // Signed in, but the access token has lapsed. Never refreshed here: the\n // browser goes through the handshake, so the browser stays the only\n // thing that spends the refresh token.\n const url = new URL(request.url);\n const handshake = new URL(`${cfg.authPath}/oauth2/session`, url.origin);\n handshake.searchParams.set(\"client_id\", cfg.clientId);\n handshake.searchParams.set(\"return_to\", url.pathname + url.search);\n\n return { ...outcome, handshake: handshake.pathname + handshake.search };\n },\n };\n}\n","/**\n * What an app tells the SDK, and the defaults for everything it doesn't.\n *\n * Whether `clientSecret` is set picks the mode, and nothing else does:\n *\n * - **With a secret** (a confidential oauth app): the server holds the whole\n * session in a sealed cookie, signs users in through its own routes, and\n * refreshes in middleware. Needs `sessionSecret`.\n * - **Without one** (a regular app, or a public oauth app): the browser SDK\n * signs users in and is the only thing that refreshes. The server reads the\n * access token cookie the auth server keeps current, through the auth proxy,\n * and sends a lapsed navigation through the auth server's session handshake.\n *\n * Everything beyond the first few fields exists for the rare app that must\n * differ, and its default is the decision the SDK makes for everyone else.\n */\nexport interface ServerAuthConfig {\n /** The Pontive auth domain, e.g. `https://acme.auth.us.pontive.com`. */\n issuer: string;\n /** The app's id. */\n clientId: string;\n /**\n * Set for a confidential app. It is sent on every token call, and is what\n * lets the app hold its refresh token on its server at all.\n */\n clientSecret?: string;\n /**\n * Encrypts the session cookie. At least 32 characters; rotating it signs\n * everyone out. Required with a `clientSecret`; unused without one, where the\n * server stores nothing.\n */\n sessionSecret?: string;\n /**\n * The app's public origin, e.g. `https://app.acme.com`. Set it for any\n * deployment behind a proxy or in a container, where the request's own URL\n * names the address the server binds to rather than the one a browser uses.\n * Without it the origin comes from `x-forwarded-proto`/`x-forwarded-host`,\n * then the request URL.\n */\n baseUrl?: string;\n\n /**\n * Without a `clientSecret`: where the auth proxy serves the auth server on\n * this app's origin. Default `/__auth`, the auth proxy's own default.\n */\n authPath?: string;\n\n /** Default `openid email profile offline_access`. */\n scopes?: string;\n /** Where the SDK's sign-in routes live. Default `/api/auth`. */\n basePath?: string;\n /** Default `${origin}${basePath}/callback`; must be registered on the app verbatim. */\n redirectUri?: string;\n /** The app's own sign-in page: failed sign-ins and signed-out users land here. Default `/login`. */\n signInPath?: string;\n /** Where a sign-in lands when it did not start from a page. Default `/`. */\n afterSignInPath?: string;\n /** An OIDC `prompt` to send, e.g. `login`. */\n prompt?: string;\n /** Default `pontive_session`. */\n cookieName?: string;\n /** Default: whether the public origin is https. */\n secureCookies?: boolean;\n /** The session cookie's lifetime. Default 7 days. */\n sessionMaxAgeSeconds?: number;\n /** Refresh when fewer than this many seconds remain. Default 60. */\n refreshSkewSeconds?: number;\n /** Defaults to the global `fetch`. */\n fetch?: typeof fetch;\n /** Where the SDK reports a failed refresh or sign-in. Defaults to `console`. */\n logger?: Pick<Console, \"warn\">;\n}\n\nexport interface ResolvedConfig {\n /** `server`: a confidential app holding its own session. `browser`: the browser SDK's session, read. */\n mode: \"server\" | \"browser\";\n authPath: string;\n issuer: string;\n clientId: string;\n clientSecret?: string;\n sessionSecret: string;\n baseUrl?: string;\n scopes: string;\n basePath: string;\n redirectUri?: string;\n signInPath: string;\n afterSignInPath: string;\n prompt?: string;\n cookieName: string;\n secureCookies?: boolean;\n sessionMaxAgeSeconds: number;\n refreshSkewSeconds: number;\n fetch: typeof fetch;\n logger: Pick<Console, \"warn\">;\n}\n\nconst SEVEN_DAYS = 7 * 24 * 60 * 60;\n\nexport function resolveConfig(config: ServerAuthConfig): ResolvedConfig {\n const mode = config.clientSecret ? \"server\" : \"browser\";\n\n const required: (keyof ServerAuthConfig)[] = [\"issuer\", \"clientId\"];\n if (mode === \"server\") required.push(\"sessionSecret\");\n for (const key of required) {\n if (!config[key]) throw new Error(`@pontive/pontkit-server: ${key} is required`);\n }\n\n if (mode === \"server\" && config.sessionSecret!.length < 32) {\n throw new Error(\"@pontive/pontkit-server: sessionSecret must be at least 32 characters\");\n }\n\n if (config.baseUrl) {\n try {\n new URL(config.baseUrl);\n } catch {\n throw new Error(`@pontive/pontkit-server: baseUrl \"${config.baseUrl}\" is not an absolute URL`);\n }\n }\n\n return {\n mode,\n authPath: (config.authPath ?? \"/__auth\").replace(/\\/+$/, \"\"),\n issuer: config.issuer.replace(/\\/+$/, \"\"),\n clientId: config.clientId,\n clientSecret: config.clientSecret || undefined,\n sessionSecret: config.sessionSecret ?? \"\",\n baseUrl: config.baseUrl ? new URL(config.baseUrl).origin : undefined,\n scopes: config.scopes ?? \"openid email profile offline_access\",\n basePath: (config.basePath ?? \"/api/auth\").replace(/\\/+$/, \"\"),\n redirectUri: config.redirectUri,\n signInPath: config.signInPath ?? \"/login\",\n afterSignInPath: config.afterSignInPath ?? \"/\",\n prompt: config.prompt,\n cookieName: config.cookieName ?? \"pontive_session\",\n secureCookies: config.secureCookies,\n sessionMaxAgeSeconds: config.sessionMaxAgeSeconds ?? SEVEN_DAYS,\n refreshSkewSeconds: config.refreshSkewSeconds ?? 60,\n fetch: config.fetch ?? ((...args) => fetch(...args)),\n logger: config.logger ?? console,\n };\n}\n","import { sha256Hex } from \"./oidc\";\n\n/**\n * One refresh per session, however many requests ask for it.\n *\n * With rotation on, every refresh spends the token it presents. A page that\n * fires several requests the moment its access token expires would otherwise\n * refresh once per request, and every request after the first would present a\n * token the first had already spent — which the issuer reads as theft.\n *\n * Two layers, in this process:\n *\n * - In flight: requests for the same refresh token share one call.\n * - Recently done: a request that arrives a moment later still holding the old\n * cookie (its browser had not yet stored the new one) is handed the same\n * result rather than spending the old token again.\n *\n * Across processes — several replicas of the app — the issuer's reuse grace\n * window covers the same race, so no shared lock is needed.\n */\n\n/** How long a result is handed to late arrivals. Matches the issuer's grace window. */\nexport const RECENT_RESULT_MS = 30_000;\n\nexport class RefreshCoordinator<T> {\n private readonly inFlight = new Map<string, Promise<T>>();\n private readonly recent = new Map<string, { at: number; result: T }>();\n\n constructor(private readonly recentMs: number = RECENT_RESULT_MS) {}\n\n async run(refreshToken: string, refresh: () => Promise<T>): Promise<T> {\n const key = await sha256Hex(refreshToken);\n this.sweep();\n\n const done = this.recent.get(key);\n if (done) return done.result;\n\n let pending = this.inFlight.get(key);\n if (!pending) {\n pending = refresh()\n .then((result) => {\n this.recent.set(key, { at: Date.now(), result });\n return result;\n })\n .finally(() => this.inFlight.delete(key));\n this.inFlight.set(key, pending);\n }\n\n return pending;\n }\n\n private sweep(): void {\n const cutoff = Date.now() - this.recentMs;\n for (const [key, entry] of this.recent) {\n if (entry.at < cutoff) this.recent.delete(key);\n }\n }\n}\n","import { EncryptJWT, jwtDecrypt } from \"jose\";\n\n/**\n * Sealing: authenticated encryption (JWE, `dir` + A256GCM) of whatever the\n * server stores in a cookie. The browser holds ciphertext it can neither read\n * nor alter, which is what lets a refresh token live in a cookie at all.\n */\n\nconst keys = new Map<string, Promise<Uint8Array>>();\n\n/** A 256-bit key derived from the configured secret, whatever its length. */\nfunction keyFor(secret: string): Promise<Uint8Array> {\n let key = keys.get(secret);\n if (!key) {\n key = crypto.subtle\n .digest(\"SHA-256\", new TextEncoder().encode(secret))\n .then((digest) => new Uint8Array(digest));\n keys.set(secret, key);\n }\n\n return key;\n}\n\nexport async function seal(payload: Record<string, unknown>, secret: string, maxAgeSeconds: number): Promise<string> {\n return new EncryptJWT(payload)\n .setProtectedHeader({ alg: \"dir\", enc: \"A256GCM\" })\n .setIssuedAt()\n .setExpirationTime(`${maxAgeSeconds}s`)\n .encrypt(await keyFor(secret));\n}\n\n/** The payload, or null when the value is not one this secret sealed, or has expired. */\nexport async function unseal<T>(value: string, secret: string): Promise<T | null> {\n try {\n const { payload } = await jwtDecrypt(value, await keyFor(secret));\n return payload as T;\n } catch {\n return null;\n }\n}\n","import type { JWTPayload } from \"jose\";\n\nimport { createBrowserAuth } from \"./browser-mode\";\nimport { resolveConfig, type ResolvedConfig, type ServerAuthConfig } from \"./config\";\nimport {\n clearChunked,\n formatCookieHeader,\n parseCookies,\n readChunked,\n serializeCookie,\n writeChunked,\n} from \"./cookies\";\nimport {\n authorizationUrl,\n endSessionUrl,\n exchangeCode,\n pkceChallenge,\n randomValue,\n refreshTokens,\n TokenEndpointError,\n verifyIdToken,\n type ClientCredentials,\n type TokenResponse,\n} from \"./oidc\";\nimport { RefreshCoordinator } from \"./refresh\";\nimport { seal, unseal } from \"./seal\";\n\nexport interface SessionUser {\n id: string;\n email?: string;\n name?: string;\n username?: string;\n picture?: string;\n emailVerified: boolean;\n}\n\n/** What `getSession` hands the app. */\nexport interface Session {\n user: SessionUser;\n /** A valid access token, for calling APIs as this user. */\n accessToken: string;\n /** When the access token expires, epoch milliseconds. */\n expiresAt: number;\n /** The issuer's session id (`sid`), when it sent one. */\n sessionId?: string;\n}\n\n/** What the cookie holds: the session plus the refresh token, which the app never sees. */\ninterface StoredSession extends Session {\n refreshToken?: string;\n}\n\n/** A sign-in in progress, sealed into its own short-lived cookie. */\ninterface SignInTransaction {\n state: string;\n verifier: string;\n nonce: string;\n returnTo: string;\n}\n\n/** What a refresh produced, for the middleware to apply. */\nexport interface RefreshOutcome {\n /** The session after the refresh, or null when there is none (or it could not be refreshed). */\n session: Session | null;\n /** `Set-Cookie` values for the response. Empty when nothing changed. */\n setCookies: string[];\n /**\n * The request's `Cookie` header as it should be seen by whatever handles\n * this request next, carrying the refreshed session. Forward it, so a page\n * rendered on this very request reads the new token rather than the expired\n * one the browser sent.\n */\n cookieHeader: string;\n /**\n * Set when a signed-in browser's access token has lapsed and it must go\n * through the auth server's session handshake to renew it — browser mode\n * only. A path on this app's origin. Send a page navigation there; any other\n * request just proceeds without a session.\n */\n handshake?: string;\n}\n\nexport interface ServerAuth {\n /**\n * Handles the SDK's routes under `basePath`: `/login`, `/callback` and\n * `/logout`. Null for any other path.\n */\n handle(request: Request): Promise<Response | null>;\n /**\n * The session in a `Cookie` header, or null. Read-only and never refreshes:\n * any server code can call it, including code that cannot set cookies.\n * Returns null for a session whose access token has expired — `refresh`,\n * run in middleware ahead of the request, is what keeps it current.\n */\n getSession(cookieHeader: string | null | undefined): Promise<Session | null>;\n /**\n * Refreshes the session when its access token is about to expire. For\n * middleware: it runs before anything renders, and its response can always\n * carry the new cookie. Concurrent calls for one session share one refresh.\n */\n refresh(request: Request): Promise<RefreshOutcome>;\n}\n\nconst DEFAULT_ACCESS_TTL_SECONDS = 900;\nconst TRANSACTION_MAX_AGE = 600;\n\n/** Only a same-origin path may be returned to after sign-in: no open redirect. */\nfunction safePath(value: string | null | undefined, fallback: string): string {\n if (value && value.startsWith(\"/\") && !value.startsWith(\"//\") && !value.startsWith(\"/\\\\\")) return value;\n return fallback;\n}\n\nfunction redirect(location: string, setCookies: string[] = []): Response {\n const headers = new Headers({ location, \"cache-control\": \"no-store\" });\n for (const cookie of setCookies) headers.append(\"set-cookie\", cookie);\n\n return new Response(null, { status: 302, headers });\n}\n\nfunction publicOrigin(cfg: ResolvedConfig, request: Request): string {\n if (cfg.baseUrl) return cfg.baseUrl;\n\n const host = request.headers.get(\"x-forwarded-host\")?.split(\",\")[0]?.trim() || request.headers.get(\"host\")?.trim();\n if (host) {\n const proto =\n request.headers.get(\"x-forwarded-proto\")?.split(\",\")[0]?.trim() || new URL(request.url).protocol.replace(\":\", \"\");\n try {\n return new URL(`${proto}://${host}`).origin;\n } catch {\n // Fall through to the request URL.\n }\n }\n\n return new URL(request.url).origin;\n}\n\nfunction userFrom(claims: JWTPayload): SessionUser {\n const str = (key: string) => (typeof claims[key] === \"string\" ? (claims[key] as string) : undefined);\n const name =\n str(\"name\") ?? ([str(\"given_name\"), str(\"family_name\")].filter(Boolean).join(\" \") || undefined) ?? str(\"email\");\n\n return {\n id: claims.sub ?? \"\",\n email: str(\"email\"),\n name,\n username: str(\"preferred_username\"),\n picture: str(\"picture\"),\n emailVerified: claims.email_verified === true,\n };\n}\n\nfunction expiresAtFrom(token: TokenResponse): number {\n return Date.now() + (token.expires_in ?? DEFAULT_ACCESS_TTL_SECONDS) * 1000;\n}\n\nexport function createServerAuth(config: ServerAuthConfig): ServerAuth {\n const cfg = resolveConfig(config);\n if (cfg.mode === \"browser\") return createBrowserAuth(cfg);\n\n const client: ClientCredentials = { issuer: cfg.issuer, clientId: cfg.clientId, clientSecret: cfg.clientSecret };\n const txCookie = `${cfg.cookieName}_tx`;\n const coordinator = new RefreshCoordinator<StoredSession | null>();\n\n const secure = (request: Request) => cfg.secureCookies ?? publicOrigin(cfg, request).startsWith(\"https:\");\n const redirectUri = (request: Request) => cfg.redirectUri ?? `${publicOrigin(cfg, request)}${cfg.basePath}/callback`;\n const toSignIn = (request: Request, error?: string) => {\n const url = new URL(cfg.signInPath, publicOrigin(cfg, request));\n if (error) url.searchParams.set(\"error\", error);\n return url.toString();\n };\n\n async function readStored(cookies: Map<string, string>): Promise<StoredSession | null> {\n const raw = readChunked(cookies, cfg.cookieName);\n if (raw === null) return null;\n\n const stored = await unseal<StoredSession>(raw, cfg.sessionSecret);\n if (!stored) cfg.logger.warn(\"[pontive] session cookie could not be read (sealed with another secret, or truncated)\");\n\n return stored;\n }\n\n async function writeStored(cookies: Map<string, string>, stored: StoredSession, request: Request): Promise<string[]> {\n const sealed = await seal({ ...stored }, cfg.sessionSecret, cfg.sessionMaxAgeSeconds);\n\n return writeChunked(cookies, cfg.cookieName, sealed, { maxAge: cfg.sessionMaxAgeSeconds, secure: secure(request) });\n }\n\n function publicSession(stored: StoredSession): Session {\n const { refreshToken: _refreshToken, ...session } = stored;\n return session;\n }\n\n async function signIn(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const tx: SignInTransaction = {\n state: randomValue(),\n verifier: randomValue(),\n nonce: randomValue(),\n returnTo: safePath(url.searchParams.get(\"returnTo\") ?? url.searchParams.get(\"next\"), cfg.afterSignInPath),\n };\n\n const location = await authorizationUrl(client, cfg.fetch, {\n redirectUri: redirectUri(request),\n scopes: cfg.scopes,\n state: tx.state,\n nonce: tx.nonce,\n codeChallenge: await pkceChallenge(tx.verifier),\n prompt: cfg.prompt,\n });\n\n const sealed = await seal({ ...tx }, cfg.sessionSecret, TRANSACTION_MAX_AGE);\n\n return redirect(location, [serializeCookie(txCookie, sealed, { maxAge: TRANSACTION_MAX_AGE, secure: secure(request) })]);\n }\n\n async function callback(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n // Single use, whatever happens next.\n const setCookies = [serializeCookie(txCookie, \"\", { maxAge: 0, secure: secure(request) })];\n\n const issuerError = url.searchParams.get(\"error\");\n if (issuerError) return redirect(toSignIn(request, issuerError), setCookies);\n\n const raw = cookies.get(txCookie);\n const tx = raw ? await unseal<SignInTransaction>(raw, cfg.sessionSecret) : null;\n const code = url.searchParams.get(\"code\");\n\n if (!tx || !code || url.searchParams.get(\"state\") !== tx.state) {\n return redirect(toSignIn(request, \"invalid_state\"), setCookies);\n }\n\n try {\n const tokens = await exchangeCode(client, cfg.fetch, {\n code,\n codeVerifier: tx.verifier,\n redirectUri: redirectUri(request),\n });\n if (!tokens.id_token) throw new Error(\"the token response carried no id_token; is the openid scope requested?\");\n\n const claims = await verifyIdToken(client, cfg.fetch, tokens.id_token, tx.nonce);\n const stored: StoredSession = {\n user: userFrom(claims),\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresAt: expiresAtFrom(tokens),\n sessionId: typeof claims.sid === \"string\" ? claims.sid : undefined,\n };\n\n setCookies.push(...(await writeStored(cookies, stored, request)));\n } catch (err) {\n cfg.logger.warn(\"[pontive] sign-in failed:\", err instanceof Error ? err.message : String(err));\n return redirect(toSignIn(request, \"exchange_failed\"), setCookies);\n }\n\n return redirect(new URL(tx.returnTo, publicOrigin(cfg, request)).toString(), setCookies);\n }\n\n async function signOut(request: Request): Promise<Response> {\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n // Read before clearing: the session holds the sid the issuer's logout needs.\n const stored = await readStored(cookies);\n const setCookies = clearChunked(cookies, cfg.cookieName, secure(request));\n\n let location = toSignIn(request);\n try {\n location =\n (await endSessionUrl(client, cfg.fetch, {\n postLogoutRedirectUri: toSignIn(request),\n sessionId: stored?.sessionId,\n })) ?? location;\n } catch (err) {\n // An unreachable issuer must not stop the local sign-out.\n cfg.logger.warn(\"[pontive] could not reach the issuer to end its session:\", err instanceof Error ? err.message : String(err));\n }\n\n return redirect(location, setCookies);\n }\n\n /**\n * Spends the refresh token once, returning the refreshed session, or null\n * when the issuer refused it for good. Anything transient throws instead, so\n * the coordinator does not hand a passing failure to other requests.\n */\n async function refreshStored(stored: StoredSession): Promise<StoredSession | null> {\n try {\n const tokens = await refreshTokens(client, cfg.fetch, stored.refreshToken!);\n\n return {\n ...stored,\n accessToken: tokens.access_token,\n // A response without one means rotation is off: the token in hand is still current.\n refreshToken: tokens.refresh_token ?? stored.refreshToken,\n expiresAt: expiresAtFrom(tokens),\n };\n } catch (err) {\n if (err instanceof TokenEndpointError && err.status >= 400 && err.status < 500) {\n cfg.logger.warn(\"[pontive] refresh refused; signing out:\", err.message);\n return null;\n }\n\n throw err;\n }\n }\n\n return {\n async handle(request) {\n const { pathname } = new URL(request.url);\n if (!pathname.startsWith(`${cfg.basePath}/`)) return null;\n\n const route = pathname.slice(cfg.basePath.length + 1);\n if (route === \"login\" && request.method === \"GET\") return signIn(request);\n if (route === \"callback\" && request.method === \"GET\") return callback(request);\n if (route === \"logout\" && (request.method === \"GET\" || request.method === \"POST\")) return signOut(request);\n\n return null;\n },\n\n async getSession(cookieHeader) {\n const stored = await readStored(parseCookies(cookieHeader));\n if (!stored) return null;\n\n if (Date.now() >= stored.expiresAt) {\n cfg.logger.warn(\n \"[pontive] the session's access token has expired and getSession cannot refresh it. \" +\n \"Run the SDK's middleware on this path so it refreshes before the request is handled.\",\n );\n return null;\n }\n\n return publicSession(stored);\n },\n\n async refresh(request) {\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n const unchanged = (session: Session | null): RefreshOutcome => ({\n session,\n setCookies: [],\n cookieHeader: request.headers.get(\"cookie\") ?? \"\",\n });\n\n const stored = await readStored(cookies);\n if (!stored) return unchanged(null);\n\n // The SDK's own routes manage the session themselves. Refreshing ahead of\n // them only spends a refresh token that sign-out is about to discard, and\n // puts a second session cookie on the response it clears.\n if (new URL(request.url).pathname.startsWith(`${cfg.basePath}/`)) {\n return unchanged(Date.now() < stored.expiresAt ? publicSession(stored) : null);\n }\n\n if (Date.now() < stored.expiresAt - cfg.refreshSkewSeconds * 1000) return unchanged(publicSession(stored));\n\n const signOutNow = (): RefreshOutcome => {\n const setCookies = clearChunked(cookies, cfg.cookieName, secure(request));\n return { session: null, setCookies, cookieHeader: formatCookieHeader(cookies) };\n };\n\n if (!stored.refreshToken) return signOutNow();\n\n let refreshed: StoredSession | null;\n try {\n refreshed = await coordinator.run(stored.refreshToken, () => refreshStored(stored));\n } catch (err) {\n // Transient: keep the session and let the next request try again. The\n // token may still have a few seconds of the skew left.\n cfg.logger.warn(\"[pontive] refresh failed, will retry:\", err instanceof Error ? err.message : String(err));\n return unchanged(Date.now() < stored.expiresAt ? publicSession(stored) : null);\n }\n\n if (!refreshed) return signOutNow();\n\n const setCookies = await writeStored(cookies, refreshed, request);\n\n return { session: publicSession(refreshed), setCookies, cookieHeader: formatCookieHeader(cookies) };\n },\n };\n}\n"],"names":["CHUNK_SIZE","MAX_CHUNKS","parseCookies","header","out","part","eq","name","formatCookieHeader","cookies","value","serializeCookie","attrs","parts","chunkName","index","readChunked","first","i","writeChunked","count","chunk","stale","clearChunked","secure","TokenEndpointError","status","code","description","JWKS_TTL_MS","discoveries","jwksCache","trimSlash","discover","issuer","fetchImpl","key","pending","res","err","jwks","uri","force","cached","set","createLocalJWKSet","base64url","bytes","binary","b","randomValue","pkceChallenge","verifier","digest","sha256Hex","authorizationUrl","client","input","disc","url","tokenRequest","params","body","headers","credentials","exchangeCode","refreshTokens","refreshToken","verifyJwt","token","options","jwtVerify","verifyIdToken","idToken","nonce","payload","userInfo","accessToken","endSessionUrl","ACCESS_COOKIE","SESSION_HINT_COOKIE","userFrom","claims","str","createBrowserAuth","cfg","profiles","sweep","now","entry","profileFor","expiresAt","profile","read","cookieHeader","request","session","outcome","handshake","SEVEN_DAYS","resolveConfig","config","mode","required","args","RECENT_RESULT_MS","RefreshCoordinator","recentMs","refresh","done","result","cutoff","keys","keyFor","secret","seal","maxAgeSeconds","EncryptJWT","unseal","jwtDecrypt","DEFAULT_ACCESS_TTL_SECONDS","TRANSACTION_MAX_AGE","safePath","fallback","redirect","location","setCookies","cookie","publicOrigin","host","proto","expiresAtFrom","createServerAuth","txCookie","coordinator","redirectUri","toSignIn","error","readStored","raw","stored","writeStored","sealed","publicSession","_refreshToken","signIn","tx","callback","issuerError","tokens","signOut","refreshStored","pathname","route","unchanged","signOutNow","refreshed"],"mappings":"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"}
package/dist/oidc.d.ts CHANGED
@@ -8,6 +8,7 @@ export interface Discovery {
8
8
  authorization_endpoint: string;
9
9
  token_endpoint: string;
10
10
  jwks_uri: string;
11
+ userinfo_endpoint?: string;
11
12
  end_session_endpoint?: string;
12
13
  }
13
14
  export interface TokenResponse {
@@ -59,6 +60,12 @@ export declare function refreshTokens(client: ClientCredentials, fetchImpl: Fetc
59
60
  export declare function verifyJwt(client: ClientCredentials, fetchImpl: Fetch, token: string): Promise<JWTPayload>;
60
61
  /** Verifies an id_token as verifyJwt does, plus the nonce of the sign-in it answers. */
61
62
  export declare function verifyIdToken(client: ClientCredentials, fetchImpl: Fetch, idToken: string, nonce: string): Promise<JWTPayload>;
63
+ /**
64
+ * The user's profile claims (OIDC Core §5.3), for an access token that carries
65
+ * none of its own. Null when the issuer offers no userinfo endpoint; throws
66
+ * when it refuses.
67
+ */
68
+ export declare function userInfo(client: ClientCredentials, fetchImpl: Fetch, accessToken: string): Promise<Record<string, unknown> | null>;
62
69
  /** RP-initiated logout (OIDC RP-Initiated Logout 1.0), or null when the issuer offers none. */
63
70
  export declare function endSessionUrl(client: ClientCredentials, fetchImpl: Fetch, input: {
64
71
  postLogoutRedirectUri: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pontive/pontkit-server",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "license": "MIT",
5
5
  "description": "Server-side sessions for Pontive apps: sign-in, a sealed session cookie and safe token refresh, on Web Request/Response.",
6
6
  "type": "module",