@gydr-ai/mcp-oauth 0.1.2 → 0.1.4

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,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/errors/mcp-auth-config-error.ts", "../src/helpers/json.ts", "../src/constants/defaults.ts", "../src/constants/protocol.ts", "../src/helpers/responses.ts", "../src/helpers/tokens.ts", "../src/helpers/freeze.ts", "../src/core/grant-context.ts", "../src/helpers/guards.ts", "../src/core/grant-summary.ts", "../src/helpers/time.ts", "../src/core/grants.ts", "../src/core/authenticate.ts", "../src/constants/error-pages.ts", "../src/helpers/client-ip.ts", "../src/helpers/locales.ts", "../src/core/policy.ts", "../src/core/context.ts", "../src/helpers/text.ts", "../src/helpers/html-document.ts", "../src/core/pages.ts", "../src/helpers/client-fields.ts", "../src/helpers/redirect-uris.ts", "../src/core/resolve-clients.ts", "../src/core/resolve-parts.ts", "../src/core/resolve-options.ts", "../src/core/client-metadata.ts", "../src/errors/client-metadata-error.ts", "../src/core/clients.ts", "../src/helpers/cookies.ts", "../src/helpers/params.ts", "../src/helpers/resource.ts", "../src/helpers/scopes.ts", "../src/handlers/authorize.ts", "../src/handlers/callback.ts", "../src/core/consent-page.ts", "../src/core/subject.ts", "../src/errors/body-too-large-error.ts", "../src/helpers/body.ts", "../src/handlers/consent.ts", "../src/handlers/register.ts", "../src/errors/endpoint-refusal.ts", "../src/core/client-form.ts", "../src/handlers/revoke.ts", "../src/core/token-policy.ts", "../src/core/code-exchange.ts", "../src/core/refresh-exchange.ts", "../src/handlers/token.ts", "../src/core/metadata.ts", "../src/core/router.ts", "../src/errors/grant-inactive-error.ts", "../src/errors/upstream-token-error.ts", "../src/core/upstream-tokens.ts", "../src/core/server.ts", "../src/crypto/aes-gcm-sealer.ts", "../src/errors/sealer-error.ts", "../src/errors/identity-provider-error.ts", "../src/providers/oidc-provider.ts", "../src/constants/oidc.ts", "../src/providers/oidc-documents.ts", "../src/stores/client-collection.ts", "../src/stores/collection.ts", "../src/stores/memory-store.ts"],
4
- "sourcesContent": ["/**\n * Thrown by `createMcpAuth` when its options are invalid.\n *\n * @example\n * ```ts\n * import { createMcpAuth, McpAuthConfigError, type McpAuthOptions } from \"@gydr-ai/mcp-oauth\"\n *\n * export function tryCreate(options: McpAuthOptions) {\n * try {\n * return createMcpAuth(options)\n * } catch (error) {\n * if (error instanceof McpAuthConfigError) console.error(error.message)\n * throw error\n * }\n * }\n * ```\n */\nexport class McpAuthConfigError extends Error {\n /**\n * @param message - What is wrong with the configuration.\n */\n constructor(message: string) {\n super(message)\n this.name = \"McpAuthConfigError\"\n }\n}\n", "/**\n * Serialise a hook's result to JSON, check its size, and return a fresh\n * deep copy, so the server never keeps a live object the hook still holds.\n *\n * @param value - Whatever the hook returned.\n * @param limit - Largest serialised size in UTF-8 bytes.\n * @param label - Name of the hook, for the error message.\n * @returns A JSON-safe copy, or `undefined` when `value` is `undefined`.\n * @throws {TypeError} When the value is not JSON-serializable or is larger\n * than `limit` bytes.\n * @internal\n */\nexport function jsonCopy(\n value: unknown,\n limit: number,\n label: string,\n): unknown {\n if (value === undefined) return undefined\n\n // `unknown`: JSON.stringify returns undefined for a function or symbol.\n let text: unknown\n\n try {\n text = JSON.stringify(value)\n } catch {\n throw new TypeError(`${label} returned data that is not JSON-serializable`)\n }\n\n if (typeof text !== \"string\") {\n throw new TypeError(`${label} returned data that is not JSON-serializable`)\n }\n\n if (Buffer.byteLength(text, \"utf8\") > limit) {\n throw new TypeError(`${label} returned more than ${limit} bytes`)\n }\n\n const copy: unknown = JSON.parse(text)\n\n return copy\n}\n", "/**\n * Default lifetimes in seconds, applied when `lifetimes` omits a value.\n *\n * @internal\n */\nexport const DEFAULT_LIFETIMES = {\n accessToken: 1800,\n refreshToken: 1_209_600,\n grant: 7_776_000,\n authorizationCode: 60,\n pendingAuthorization: 900,\n clientMetadataCache: 1800,\n clientMetadataFailure: 60,\n} as const\n\n/**\n * Smallest and largest configurable lifetimes, in seconds. The caps keep a\n * typo from creating codes or sign-in windows that live for days, or tokens\n * that never expire in practice.\n *\n * @internal\n */\nexport const LIFETIME_BOUNDS = {\n accessToken: { min: 1, max: 86_400 },\n refreshToken: { min: 1, max: 34_560_000 },\n grant: { min: 1, max: 34_560_000 },\n authorizationCode: { min: 1, max: 600 },\n pendingAuthorization: { min: 1, max: 3600 },\n clientMetadataCache: { min: 0, max: 86_400 },\n clientMetadataFailure: { min: 0, max: 3600 },\n} as const\n\n/**\n * Default size and count limits, applied when `limits` omits a value.\n *\n * @internal\n */\nexport const DEFAULT_LIMITS = {\n clientNameLength: 128,\n redirectUris: 8,\n redirectUriLength: 2048,\n stateLength: 2048,\n scopeLength: 1024,\n registrationBytes: 16 * 1024,\n formBytes: 32 * 1024,\n clientMetadataBytes: 5 * 1024,\n clientMetadataTimeoutMs: 4000,\n clientMetadataFailures: 10_000,\n grantContextBytes: 4096,\n consentDetailsBytes: 8192,\n grantDataBytes: 4096,\n} as const\n\n/**\n * Scopes offered when `scopes` is not set.\n *\n * @internal\n */\nexport const DEFAULT_SCOPES: readonly string[] = [\"mcp\"]\n\n/**\n * Retry window for a just-used refresh token, in seconds.\n *\n * @internal\n */\nexport const DEFAULT_REFRESH_REUSE_GRACE_SECONDS = 10\n\n/**\n * Longest refresh retry window that may be configured, in seconds.\n *\n * @internal\n */\nexport const MAX_REFRESH_REUSE_GRACE_SECONDS = 300\n\n/**\n * Default name prefix of the browser-binding cookie.\n *\n * @internal\n */\nexport const DEFAULT_COOKIE_PREFIX = \"__Host-mcp-auth-\"\n\n/**\n * Default capacity of each memory-store collection.\n *\n * @internal\n */\nexport const DEFAULT_MEMORY_STORE_ENTRIES = 100_000\n\n/**\n * Default idle lifetime of a grant-less dynamic client in the memory store, in seconds (28 days).\n *\n * @internal\n */\nexport const DEFAULT_DYNAMIC_CLIENT_IDLE_SECONDS = 2_419_200\n\n/**\n * Upstream access tokens are renewed this long before they expire.\n *\n * @internal\n */\nexport const UPSTREAM_EXPIRY_SKEW_MS = 60_000\n\n/**\n * Most grants whose upstream access token is cached in memory.\n *\n * @internal\n */\nexport const UPSTREAM_CACHE_GRANTS = 10_000\n\n/**\n * Upstream access tokens are treated as expiring within a day at most.\n *\n * @internal\n */\nexport const UPSTREAM_LIFETIME_CEILING_SECONDS = 86_400\n\n/**\n * `client_id` values longer than this are never looked up.\n *\n * @internal\n */\nexport const CLIENT_ID_LENGTH_LIMIT = 2048\n\n/**\n * Cache lifetime of metadata responses, in seconds.\n *\n * @internal\n */\nexport const METADATA_MAX_AGE_SECONDS = 600\n\n/**\n * Default seconds during which a repeated consent submission gets the same\n * answer.\n *\n * @internal\n */\nexport const DEFAULT_CONSENT_REPLAY_SECONDS = 10\n\n/**\n * Longest configurable consent replay window, in seconds.\n *\n * @internal\n */\nexport const MAX_CONSENT_REPLAY_SECONDS = 60\n", "/**\n * Well-known path of RFC 9728 protected resource metadata.\n *\n * @internal\n */\nexport const PROTECTED_RESOURCE_METADATA_PATH =\n \"/.well-known/oauth-protected-resource\"\n\n/**\n * Well-known path of RFC 8414 authorization server metadata.\n *\n * @internal\n */\nexport const AUTHORIZATION_SERVER_METADATA_PATH =\n \"/.well-known/oauth-authorization-server\"\n\n/**\n * Hostnames treated as the local machine (RFC 8252 \u00A7 7.3).\n *\n * @internal\n */\nexport const LOOPBACK_HOSTS: ReadonlySet<string> = new Set([\n \"127.0.0.1\",\n \"[::1]\",\n \"localhost\",\n])\n\n/**\n * Names of the fields the consent form posts back.\n *\n * @internal\n */\nexport const CONSENT_FIELDS = {\n id: \"interaction\",\n csrf: \"form_token\",\n decision: \"choice\",\n} as const\n\n/**\n * The value of the decision field that grants access.\n *\n * @internal\n */\nexport const CONSENT_APPROVE = \"approve\"\n\n/**\n * Media type of form bodies.\n *\n * @internal\n */\nexport const FORM_MEDIA_TYPE = \"application/x-www-form-urlencoded\"\n\n/**\n * Media type of JSON bodies.\n *\n * @internal\n */\nexport const JSON_MEDIA_TYPE = \"application/json\"\n", "import { METADATA_MAX_AGE_SECONDS } from \"../constants/defaults.js\"\nimport { JSON_MEDIA_TYPE } from \"../constants/protocol.js\"\n\n/** Caching headers every OAuth response carries (RFC 6749 \u00A7 5.1). */\nconst NO_STORE: Readonly<Record<string, string>> = {\n \"Cache-Control\": \"no-store\",\n Pragma: \"no-cache\",\n}\n\n/** Open CORS for the cookie-free endpoints that browser-based clients call. */\nconst CORS: Readonly<Record<string, string>> = {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Expose-Headers\": \"WWW-Authenticate\",\n}\n\n/** Options for {@link json}. */\ninterface JsonOptions {\n /** Add open CORS headers. Default: `false`. */\n readonly cors?: boolean\n /** Allow shared caches to keep the response briefly. Default: `false`. */\n readonly cacheable?: boolean\n /** Extra headers. */\n readonly headers?: Readonly<Record<string, string>>\n}\n\n/**\n * Build a JSON response, not cacheable unless asked.\n *\n * @param status - HTTP status.\n * @param body - Value to serialize.\n * @param options - CORS, caching and extra headers.\n * @returns The response with `Content-Type: application/json`.\n * @internal\n */\nexport function json(\n status: number,\n body: unknown,\n options: JsonOptions = {},\n): Response {\n const headers = new Headers({\n \"Content-Type\": JSON_MEDIA_TYPE,\n ...(options.cacheable\n ? { \"Cache-Control\": `public, max-age=${METADATA_MAX_AGE_SECONDS}` }\n : NO_STORE),\n ...(options.cors ? CORS : {}),\n ...options.headers,\n })\n\n return new Response(JSON.stringify(body), { status, headers })\n}\n\n/**\n * Build an OAuth error response (RFC 6749 \u00A7 5.2).\n *\n * @param status - HTTP status.\n * @param error - OAuth error code.\n * @param description - Optional `error_description`.\n * @param challenge - Optional `WWW-Authenticate` value.\n * @returns A JSON response with open CORS and `Cache-Control: no-store`.\n * @internal\n */\nexport function oauthError(\n status: number,\n error: string,\n description?: string,\n challenge?: string,\n): Response {\n return json(\n status,\n description === undefined\n ? { error }\n : { error, error_description: description },\n {\n cors: true,\n ...(challenge !== undefined\n ? { headers: { \"WWW-Authenticate\": challenge } }\n : {}),\n },\n )\n}\n\n/**\n * Answer a CORS preflight for a browser-callable endpoint.\n *\n * @param methods - Comma-separated allowed methods.\n * @returns A 204 response.\n * @internal\n */\nexport function preflight(methods: string): Response {\n return new Response(null, {\n status: 204,\n headers: {\n ...CORS,\n \"Access-Control-Allow-Methods\": methods,\n \"Access-Control-Allow-Headers\":\n \"Authorization, Content-Type, MCP-Protocol-Version\",\n \"Access-Control-Max-Age\": \"600\",\n },\n })\n}\n\n/**\n * Answer 405 with the methods the route does accept.\n *\n * @param allow - Comma-separated allowed methods.\n * @returns A 405 response.\n * @internal\n */\nexport function methodNotAllowed(allow: string): Response {\n return new Response(null, {\n status: 405,\n headers: { Allow: allow, ...NO_STORE },\n })\n}\n\n/**\n * Security headers for every HTML page: a strict Content-Security-Policy\n * (inline styles only, no scripts, forms only to this server and the listed\n * origins, no framing), plus no caching and no referrer.\n *\n * @param formActionOrigins - Extra origins a form may submit or redirect to.\n * @param scriptHashes - Inline script hashes (`sha256-\u2026`) the page may run.\n * @returns Header names and values for the page response.\n * @internal\n */\nexport function pageHeaders(\n formActionOrigins: readonly string[] = [],\n scriptHashes: readonly string[] = [],\n): Record<string, string> {\n const formAction = [\"'self'\", ...formActionOrigins].join(\" \")\n const scripts =\n scriptHashes.length === 0\n ? \"\"\n : ` script-src ${scriptHashes.map((h) => `'${h}'`).join(\" \")};`\n\n return {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"Content-Security-Policy\": `default-src 'none';${scripts} style-src 'unsafe-inline'; img-src data:; form-action ${formAction}; frame-ancestors 'none'; base-uri 'none'`,\n \"X-Frame-Options\": \"DENY\",\n \"X-Content-Type-Options\": \"nosniff\",\n \"Referrer-Policy\": \"no-referrer\",\n ...NO_STORE,\n }\n}\n\n/** Options for {@link html}. */\ninterface HtmlOptions {\n /** Extra origins the page's form may submit or redirect to. */\n readonly formActionOrigins?: readonly string[]\n /** `Set-Cookie` header values to add. */\n readonly setCookies?: readonly string[]\n /** Inline script hashes the page may run. */\n readonly scriptHashes?: readonly string[]\n}\n\n/**\n * Build an HTML page response with the page security headers.\n *\n * @param status - HTTP status.\n * @param body - The complete HTML document.\n * @param options - Form origins and cookies.\n * @returns The HTML response.\n * @internal\n */\nexport function html(\n status: number,\n body: string,\n options: HtmlOptions = {},\n): Response {\n const headers = new Headers(\n pageHeaders(options.formActionOrigins, options.scriptHashes),\n )\n\n for (const cookie of options.setCookies ?? []) {\n headers.append(\"Set-Cookie\", cookie)\n }\n\n return new Response(body, { status, headers })\n}\n\n/**\n * Build a redirect that is never cached and sends no referrer.\n *\n * @param location - Target URL.\n * @param setCookies - `Set-Cookie` header values to add.\n * @param status - 302, or 303 after a form POST so the browser follows with\n * GET (OAuth 2.1 \u00A7 7.5.4).\n * @returns A redirect response.\n * @internal\n */\nexport function redirect(\n location: string,\n setCookies: readonly string[] = [],\n status: 302 | 303 = 302,\n): Response {\n const headers = new Headers({\n Location: location,\n \"Referrer-Policy\": \"no-referrer\",\n ...NO_STORE,\n })\n\n for (const cookie of setCookies) headers.append(\"Set-Cookie\", cookie)\n\n return new Response(null, { status, headers })\n}\n", "import { createHash, randomBytes, timingSafeEqual } from \"node:crypto\"\n\n/** Entropy of every token, code, identifier and cookie value we mint. */\nconst SECRET_BYTES = 32\n\n/** Length of a base64url-encoded SHA-256 digest or of 32 random bytes. */\nconst ENCODED_32_BYTES = 43\n\n/**\n * A new unguessable value: 256 random bits, base64url without padding.\n *\n * @returns 43 base64url characters.\n * @internal\n */\nexport function randomToken(): string {\n return randomBytes(SECRET_BYTES).toString(\"base64url\")\n}\n\n/**\n * Hex SHA-256 of a UTF-8 string. Tokens and codes are stored only like this.\n *\n * @param value - Any UTF-8 string, typically an opaque token, code or cookie value.\n * @returns 64 lower-case hexadecimal characters.\n * @internal\n */\nexport function sha256Hex(value: string): string {\n return createHash(\"sha256\").update(value, \"utf8\").digest(\"hex\")\n}\n\n/**\n * The S256 code challenge for a verifier (RFC 7636 \u00A7 4.2).\n *\n * @param verifier - An RFC 7636 code verifier (ASCII).\n * @returns The base64url challenge.\n * @internal\n */\nexport function pkceChallenge(verifier: string): string {\n return createHash(\"sha256\").update(verifier, \"ascii\").digest(\"base64url\")\n}\n\n/**\n * Compare two strings without leaking where they differ.\n *\n * @param a - First value, typically a freshly computed hash.\n * @param b - Second value, typically the stored hash.\n * @returns `true` when equal.\n * @internal\n */\nexport function constantTimeEqual(a: string, b: string): boolean {\n const left = Buffer.from(a, \"utf8\")\n const right = Buffer.from(b, \"utf8\")\n\n return left.length === right.length && timingSafeEqual(left, right)\n}\n\nconst BASE64URL_EXTRA = \"-_\"\nconst VERIFIER_EXTRA = \"-._~\"\n\nfunction charsetCheck(extra: string) {\n return (text: string): boolean => {\n for (let i = 0; i < text.length; i += 1) {\n const c = text.charCodeAt(i)\n const alphanumeric =\n (c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122)\n\n if (!alphanumeric && !extra.includes(text.charAt(i))) return false\n }\n\n return true\n }\n}\n\nconst onlyBase64Url = charsetCheck(BASE64URL_EXTRA)\nconst onlyVerifierChars = charsetCheck(VERIFIER_EXTRA)\n\n/**\n * A code verifier as RFC 7636 \u00A7 4.1 defines it: 43 to 128 unreserved characters.\n *\n * @param verifier - The raw `code_verifier` form value.\n * @returns `true` when well formed.\n * @internal\n */\nexport function isWellFormedVerifier(verifier: string): boolean {\n return (\n verifier.length >= 43 &&\n verifier.length <= 128 &&\n onlyVerifierChars(verifier)\n )\n}\n\n/**\n * An S256 challenge: a SHA-256 digest in base64url, 43 characters.\n *\n * @param challenge - The raw `code_challenge` query value.\n * @returns `true` when well formed.\n * @internal\n */\nexport function isWellFormedChallenge(challenge: string): boolean {\n return challenge.length === ENCODED_32_BYTES && onlyBase64Url(challenge)\n}\n\n/**\n * Shaped like a token minted by {@link randomToken}.\n *\n * @param value - A bearer value, code or refresh token as sent by a client, unvalidated.\n * @returns `true` when well formed.\n * @internal\n */\nexport function isOpaqueToken(value: string): boolean {\n return value.length === ENCODED_32_BYTES && onlyBase64Url(value)\n}\n\n/**\n * Does `verifier` hash to `challenge`? Malformed verifiers never do.\n *\n * @param verifier - The raw `code_verifier` form value; malformed values never match.\n * @param challenge - The S256 challenge stored with the authorization code.\n * @returns `true` on a match.\n * @internal\n */\nexport function pkceMatches(verifier: string, challenge: string): boolean {\n return (\n isWellFormedVerifier(verifier) &&\n constantTimeEqual(pkceChallenge(verifier), challenge)\n )\n}\n", "/**\n * Freeze an object graph in place, recursively.\n *\n * @param value - A value produced by `structuredClone` (plain data only).\n * @returns The same value, now deeply frozen.\n */\nfunction deepFreeze<T>(value: T): T {\n if (typeof value === \"object\" && value !== null && !Object.isFrozen(value)) {\n Object.freeze(value)\n\n for (const key of Reflect.ownKeys(value)) {\n deepFreeze(Reflect.get(value, key))\n }\n }\n\n return value\n}\n\n/**\n * A deeply frozen copy of plain data, handed to integrator hooks so that a\n * hook can read validated state but can never change what the server goes\n * on to use.\n *\n * @param value - Plain, structured-cloneable data (no functions, no `Request`).\n * @returns An independent, deeply frozen copy.\n * @internal\n */\nexport function frozenCopy<T>(value: T): T {\n return deepFreeze(structuredClone(value))\n}\n\n/**\n * Deeply freeze data that the server owns and shares with hooks or callers,\n * such as pre-registered client records.\n *\n * @param value - Plain data to freeze in place.\n * @returns The same value, deeply frozen.\n * @internal\n */\nexport function freezeDeep<T>(value: T): T {\n return deepFreeze(value)\n}\n", "import { jsonCopy } from \"../helpers/json.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { GrantRecord } from \"../interfaces/records.js\"\n\n/**\n * Ask `getGrantContext` for the data to expose on `AuthResult.context`.\n *\n * The hook sees a copy of the grant's public fields and its result is\n * returned as a fresh JSON copy, so it can neither change the grant nor leak\n * live objects between requests. A result that cannot be serialized, or is\n * larger than `limits.grantContextBytes`, is an integration error.\n *\n * @param context - Context supplying the `getGrantContext` hook and `limits.grantContextBytes`.\n * @param grant - The grant behind the token.\n * @returns A JSON-safe copy of the hook's result, or `undefined` without a hook.\n * @throws {TypeError} When the result is not JSON-serializable or too large.\n * @internal\n */\nexport async function grantContextFor(\n context: HandlerContext,\n grant: GrantRecord,\n): Promise<unknown> {\n const hook = context.options.getGrantContext\n\n if (hook === undefined) return undefined\n\n const result = await hook(\n frozenCopy({\n grantId: grant.grantId,\n clientId: grant.clientId,\n subject: grant.subject,\n scopes: [...grant.scopes],\n resource: grant.resource,\n createdAt: grant.createdAt,\n ...(grant.data !== undefined ? { data: grant.data } : {}),\n }),\n )\n\n return jsonCopy(\n result,\n context.options.limits.grantContextBytes,\n \"getGrantContext\",\n )\n}\n", "/**\n * Whether a value is a plain object (not `null`, not an array).\n *\n * @param value - Any value.\n * @returns `true` for an object record.\n * @internal\n */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\n/**\n * Whether a value is an array of strings.\n *\n * @param value - Any value.\n * @returns `true` for a string array.\n * @internal\n */\nexport function isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === \"string\")\n}\n", "import type { GrantSummary } from \"../interfaces/consent.js\"\nimport type { GrantRecord } from \"../interfaces/records.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\n\n/**\n * A deeply frozen copy of a grant without its encrypted upstream tokens,\n * for hooks and events.\n *\n * @param grant - The stored grant.\n * @returns The summary.\n * @internal\n */\nexport function grantSummary(grant: GrantRecord): GrantSummary {\n // An allow-list, so a field added to GrantRecord later is never exposed\n // by accident.\n return frozenCopy({\n grantId: grant.grantId,\n clientId: grant.clientId,\n subject: grant.subject,\n scopes: grant.scopes,\n resource: grant.resource,\n createdAt: grant.createdAt,\n expiresAt: grant.expiresAt,\n ...(grant.clientName !== undefined ? { clientName: grant.clientName } : {}),\n ...(grant.displayName !== undefined\n ? { displayName: grant.displayName }\n : {}),\n ...(grant.allowRefresh !== undefined\n ? { allowRefresh: grant.allowRefresh }\n : {}),\n ...(grant.data !== undefined ? { data: grant.data } : {}),\n })\n}\n", "/**\n * Whether something with this expiry has expired at `now`. Written so that a\n * non-finite value on either side (`NaN`, `undefined` coerced to a number)\n * counts as expired: a broken clock or record must never keep a token alive.\n *\n * @param expiresAt - Expiry in milliseconds since the epoch, as stored.\n * @param now - Current time in milliseconds since the epoch.\n * @returns `true` unless `expiresAt` is a number strictly greater than `now`.\n * @internal\n */\nexport function hasExpired(expiresAt: number, now: number): boolean {\n return !(\n Number.isFinite(expiresAt) &&\n Number.isFinite(now) &&\n expiresAt > now\n )\n}\n\n/**\n * Wrap a clock so that it can only return finite numbers. Anything else\n * throws, which fails the current request closed instead of silently\n * disabling every expiry check.\n *\n * @param clock - Returns milliseconds since the epoch, such as `Date.now`.\n * @returns A clock with the same values, or throwing for a non-finite one.\n * @internal\n */\nexport function checkedClock(clock: () => number): () => number {\n return () => {\n const value: unknown = clock()\n\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n throw new TypeError(\"The configured clock returned a non-finite time\")\n }\n\n return value\n }\n}\n", "import { randomToken, sha256Hex } from \"../helpers/tokens.js\"\nimport { isRecord } from \"../helpers/guards.js\"\nimport { grantSummary } from \"./grant-summary.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { GrantRevokeReason } from \"../interfaces/events.js\"\nimport type { GrantRecord } from \"../interfaces/records.js\"\nimport type { TokenResponse } from \"../interfaces/token-response.js\"\nimport { hasExpired } from \"../helpers/time.js\"\n\n/**\n * Mint and store an access token (and, unless the client did not register\n * the refresh_token grant, a refresh token) for a grant. The refresh token's\n * window slides but never passes the grant's absolute expiry.\n *\n * @param context - Context supplying the store, clock and token lifetimes.\n * @param grant - The live grant the tokens are issued under; its `expiresAt` caps both token lifetimes and `allowRefresh` decides whether a refresh token is issued.\n * @param scopes - Scopes the access token carries; already checked to be within the grant's scopes.\n * @param refreshScopes - Scopes the refresh token carries. Default: `scopes`.\n * @returns The RFC 6749 \u00A7 5.1 response body; `refresh_token` is absent when the grant does not allow refresh.\n * @internal\n */\nexport async function issueTokenPair(\n context: HandlerContext,\n grant: GrantRecord,\n scopes: readonly string[],\n refreshScopes: readonly string[] = scopes,\n): Promise<TokenResponse> {\n const { options } = context\n const now = options.now()\n const accessToken = randomToken()\n const accessExpiresAt = Math.min(\n now + options.accessTokenTtlMs,\n grant.expiresAt,\n )\n\n await options.store.putAccessToken({\n tokenHash: sha256Hex(accessToken),\n grantId: grant.grantId,\n clientId: grant.clientId,\n scopes,\n resource: grant.resource,\n expiresAt: accessExpiresAt,\n })\n\n const response = {\n access_token: accessToken,\n token_type: \"Bearer\" as const,\n expires_in: Math.max(0, Math.floor((accessExpiresAt - now) / 1000)),\n scope: scopes.join(\" \"),\n }\n\n if (grant.allowRefresh === false) return response\n\n const refreshToken = randomToken()\n\n await options.store.putRefreshToken({\n tokenHash: sha256Hex(refreshToken),\n grantId: grant.grantId,\n clientId: grant.clientId,\n scopes: refreshScopes,\n resource: grant.resource,\n expiresAt: Math.min(now + options.refreshTokenTtlMs, grant.expiresAt),\n })\n\n return { ...response, refresh_token: refreshToken }\n}\n\n/**\n * The grant when it exists, is unexpired and belongs to `clientId`.\n *\n * @param context - Context supplying the store and clock.\n * @param grantId - Grant id taken from a stored token or code record.\n * @param clientId - Client the caller claims to be; the grant must belong to it.\n * @returns The grant when it exists, has not reached its absolute expiry and belongs to `clientId`; otherwise `undefined`.\n * @internal\n */\nexport async function liveGrant(\n context: HandlerContext,\n grantId: string,\n clientId: string,\n): Promise<GrantRecord | undefined> {\n const grant = await context.options.store.getGrant(grantId)\n\n if (\n grant === undefined ||\n grant.clientId !== clientId ||\n hasExpired(grant.expiresAt, context.options.now())\n ) {\n return undefined\n }\n\n return grant\n}\n\n/**\n * Revoke an upstream refresh token at the IdP. Best effort, never throws.\n *\n * @param context - Context supplying the identity provider whose optional `revoke` is called.\n * @param refreshToken - Plaintext upstream refresh token, as returned by the identity provider.\n * @internal\n */\nexport async function revokeUpstreamToken(\n context: HandlerContext,\n refreshToken: string,\n): Promise<void> {\n const provider = context.options.identityProvider\n\n if (provider.revoke === undefined) return\n\n try {\n await provider.revoke(refreshToken)\n } catch {\n // Best effort: nothing on our side holds the token any more.\n }\n}\n\n/**\n * Open a sealed upstream refresh token and revoke it. Best effort.\n *\n * @param context - Context supplying the sealer and identity provider.\n * @param sealed - Upstream refresh token encrypted with additional data `grantId`; nothing happens when `undefined`.\n * @param grantId - Grant id the value was encrypted for; used as the additional authenticated data.\n * @internal\n */\nexport async function revokeSealedUpstreamToken(\n context: HandlerContext,\n sealed: string | undefined,\n grantId: string,\n): Promise<void> {\n if (\n sealed === undefined ||\n context.options.identityProvider.revoke === undefined\n ) {\n return\n }\n\n let plaintext: string\n\n try {\n plaintext = await context.options.sealer.open(sealed, grantId)\n } catch {\n return\n }\n\n await revokeUpstreamToken(context, plaintext)\n}\n\n/**\n * End a grant. The `grant_revoked` event (with a copy of the grant, minus\n * its encrypted upstream tokens) is emitted first. Removing the record\n * invalidates all tokens tied to it.\n *\n * The upstream refresh token revoked at the identity provider is the last\n * one stored: the record the store returns from `deleteGrant`, or else the\n * one read just before deleting. `onRevoking` runs before both, so an\n * upstream renewal under way in this process sees the grant ending and\n * revokes the token it was about to store itself.\n *\n * @param context - Context supplying the store, sealer, identity provider and event hook.\n * @param grantId - Id of the grant to end; unknown ids are a silent no-op.\n * @param reason - Why the grant ends; reported in the `grant_revoked` event.\n * @param onRevoking - Called synchronously before the delete, so in-memory state for the grant can be dropped.\n * @internal\n */\nexport async function revokeGrantById(\n context: HandlerContext,\n grantId: string,\n reason: GrantRevokeReason,\n onRevoking: (grantId: string) => void,\n): Promise<void> {\n const { options } = context\n const grant = await options.store.getGrant(grantId)\n\n // Reported before the delete, so a listener still finds the grant (and\n // whatever it keeps about it) in the store.\n if (grant !== undefined) {\n await context.emit({\n type: \"grant_revoked\",\n grantId,\n reason,\n grant: grantSummary(grant),\n })\n }\n\n onRevoking(grantId)\n\n // Read again: a renewal may have stored a new upstream token meanwhile.\n const latest = await options.store.getGrant(grantId)\n const deleted: unknown = await options.store.deleteGrant(grantId)\n const sealed =\n isRecord(deleted) && deleted.grantId === grantId\n ? deleted.sealedUpstreamRefreshToken\n : (latest ?? grant)?.sealedUpstreamRefreshToken\n\n if (sealed !== undefined && typeof sealed !== \"string\") return\n\n await revokeSealedUpstreamToken(context, sealed, grantId)\n}\n", "import { isOpaqueToken, sha256Hex } from \"../helpers/tokens.js\"\nimport type { AuthResult, BearerError } from \"../interfaces/auth.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport { grantContextFor } from \"./grant-context.js\"\nimport { liveGrant } from \"./grants.js\"\nimport { hasExpired } from \"../helpers/time.js\"\n\n/**\n * Quote a value for an `auth-param` (RFC 9110 \u00A7 11.4), escaping quotes and\n * backslashes and folding line breaks.\n *\n * @param value - The raw value.\n * @returns The quoted string.\n */\nfunction quoted(value: string): string {\n return `\"${value.replace(/[\\\\\"]/g, \"\\\\$&\").replace(/[\\r\\n]/g, \" \")}\"`\n}\n\n/**\n * HTTP status for a bearer error (RFC 6750 \u00A7 3.1).\n *\n * @param error - The error code, if any.\n * @returns 400, 401 or 403.\n */\nfunction challengeStatus(error: BearerError[\"error\"]): number {\n switch (error) {\n case \"insufficient_scope\":\n return 403\n case \"invalid_request\":\n return 400\n case \"invalid_token\":\n case undefined:\n return 401\n }\n}\n\n/**\n * A 401 (or 403 for `insufficient_scope`) carrying\n * `WWW-Authenticate: Bearer resource_metadata=\"\u2026\"` (RFC 6750 and RFC 9728\n * \u00A7 5.1), so an MCP client can discover the authorization server.\n *\n * @param context - Context supplying the resource metadata URL and default scopes for the challenge.\n * @param error - What to report.\n * @returns A JSON response with status 400, 401 or 403 and the `WWW-Authenticate: Bearer` header.\n * @internal\n */\nexport function bearerChallenge(\n context: HandlerContext,\n error: BearerError = {},\n): Response {\n const { options } = context\n const params = [`resource_metadata=${quoted(options.resourceMetadataUrl)}`]\n const scope = error.scope ?? options.defaultScopes\n\n if (error.error !== undefined) params.push(`error=${quoted(error.error)}`)\n\n if (error.description !== undefined) {\n params.push(`error_description=${quoted(error.description)}`)\n }\n\n params.push(`scope=${quoted(scope.join(\" \"))}`)\n\n const status = challengeStatus(error.error)\n\n return new Response(\n JSON.stringify({\n error: error.error ?? \"unauthorized\",\n ...(error.description !== undefined\n ? { error_description: error.description }\n : {}),\n }),\n {\n status,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Cache-Control\": \"no-store\",\n \"WWW-Authenticate\": `Bearer ${params.join(\", \")}`,\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Expose-Headers\": \"WWW-Authenticate\",\n },\n },\n )\n}\n\n/**\n * A generic 500 for a request the server could not finish, with no detail.\n *\n * @returns A JSON `server_error` response that is never cached.\n */\nfunction serverFailure(): Response {\n return new Response(JSON.stringify({ error: \"server_error\" }), {\n status: 500,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Cache-Control\": \"no-store\",\n },\n })\n}\n\n/**\n * The bearer token of a request.\n *\n * @param request - The request.\n * @returns The token; `null` when no Bearer credentials were sent (no\n * `Authorization` header, or one with another scheme); `undefined` when a\n * Bearer credential is empty.\n */\nfunction bearerToken(request: Request): string | null | undefined {\n const header = request.headers.get(\"authorization\")\n\n if (header === null) return null\n\n const trimmed = header.trim()\n const space = trimmed.indexOf(\" \")\n const scheme = space === -1 ? trimmed : trimmed.slice(0, space)\n\n // RFC 6750 \u00A7 3.1: a request without Bearer credentials gets the bare\n // challenge, with no error code.\n if (scheme.toLowerCase() !== \"bearer\") return null\n\n const token = space === -1 ? \"\" : trimmed.slice(space + 1).trim()\n\n return token === \"\" ? undefined : token\n}\n\n/**\n * Check the bearer token on a request to the protected resource. It must be\n * a token this server issued, not expired, addressed to this resource\n * (RFC 8707 audience), and its grant must still exist for the same client.\n *\n * @param context - Context supplying the store, clock, configured resource and `getGrantContext` hook.\n * @param request - The request.\n * @param upstream - Produces an upstream access token for a grant id.\n * @returns The outcome. When `getGrantContext` throws, returns data that is\n * not JSON-serializable, or exceeds `limits.grantContextBytes`, the result\n * is a failure with a generic 500 response and a `grant_context_failed`\n * event; the hook's error never leaves this function.\n * @internal\n */\nexport async function authenticateRequest(\n context: HandlerContext,\n request: Request,\n upstream: (grantId: string, forceRefresh: boolean) => Promise<string>,\n): Promise<AuthResult> {\n const { options } = context\n const token = bearerToken(request)\n\n if (token === null) {\n return { ok: false, response: bearerChallenge(context) }\n }\n\n const invalid = (description: string): AuthResult => ({\n ok: false,\n response: bearerChallenge(context, { error: \"invalid_token\", description }),\n })\n\n if (token === undefined || !isOpaqueToken(token)) {\n return invalid(\"The bearer value is not in this server's token format\")\n }\n\n const row = await options.store.getAccessToken(sha256Hex(token))\n\n if (row === undefined || hasExpired(row.expiresAt, options.now())) {\n return invalid(\"The token is unknown here or past its expiry\")\n }\n\n if (row.resource !== options.resource) {\n return invalid(\"The token's audience is a different resource\")\n }\n\n const grant = await liveGrant(context, row.grantId, row.clientId)\n\n if (grant === undefined) {\n return invalid(\"Access behind this token has been withdrawn\")\n }\n\n let grantContext: unknown\n\n try {\n grantContext = await grantContextFor(context, grant)\n } catch {\n // The hook's message may contain anything; it is neither logged nor\n // returned, so a host error handler cannot leak it.\n await context.emit({ type: \"grant_context_failed\", grantId: grant.grantId })\n\n return { ok: false, response: serverFailure() }\n }\n\n return {\n ok: true,\n subject: grant.subject,\n grantId: grant.grantId,\n clientId: grant.clientId,\n scopes: row.scopes,\n context: grantContext,\n getUpstreamAccessToken: (options) =>\n upstream(grant.grantId, options?.forceRefresh === true),\n }\n}\n", "/**\n * Stable reason codes for error pages. A custom `renderErrorPage` can switch\n * on them to show its own wording or language.\n *\n * @example\n * ```ts\n * import type { ErrorPageReason } from \"@gydr-ai/mcp-oauth\"\n *\n * export function heading(reason: ErrorPageReason): string {\n * switch (reason) {\n * case \"access_denied\":\n * return \"Access not granted\"\n * case \"rate_limited\":\n * return \"Slow down\"\n * default:\n * return \"Something went wrong\"\n * }\n * }\n * ```\n */\nexport type ErrorPageReason =\n | \"rate_limited\"\n | \"repeated_parameter\"\n | \"client_unknown\"\n | \"redirect_uri_not_registered\"\n | \"invalid_request\"\n | \"unsupported_response_type\"\n | \"invalid_target\"\n | \"invalid_scope\"\n | \"consent_required\"\n | \"access_denied\"\n | \"flow_ended\"\n | \"malformed_response\"\n | \"browser_mismatch\"\n | \"sign_in_not_confirmed\"\n | \"subject_not_permitted\"\n | \"consent_unavailable\"\n | \"unsupported_submission\"\n | \"server_error\"\n\n/** Status, title and message of one built-in error page. */\ninterface ErrorPageText {\n /** HTTP status. */\n readonly status: number\n /** Heading. */\n readonly title: string\n /** Explanation for the person. */\n readonly message: string\n}\n\n/**\n * Built-in status, title and message for every error page reason.\n *\n * @internal\n */\nexport const ERROR_PAGES: Readonly<Record<ErrorPageReason, ErrorPageText>> = {\n rate_limited: {\n status: 429,\n title: \"Slow down\",\n message:\n \"An unusual number of requests came from your network just now. Please wait a little, then start again from your application.\",\n },\n repeated_parameter: {\n status: 400,\n title: \"Malformed link\",\n message:\n \"The link that opened this page names the same setting more than once, so it cannot be trusted.\",\n },\n client_unknown: {\n status: 400,\n title: \"Application not recognised\",\n message:\n \"The application that opened this page is not known to this server. Try connecting again from the application.\",\n },\n redirect_uri_not_registered: {\n status: 400,\n title: \"Unexpected return address\",\n message:\n \"The application asked for the result to go somewhere other than the return addresses it registered, so nothing was sent.\",\n },\n invalid_request: {\n status: 400,\n title: \"Incomplete request\",\n message:\n \"The application sent a request this server cannot accept. Please try connecting again from the application.\",\n },\n unsupported_response_type: {\n status: 400,\n title: \"Unsupported request\",\n message:\n \"The application asked for a kind of sign-in this server does not offer.\",\n },\n invalid_target: {\n status: 400,\n title: \"Wrong service\",\n message:\n \"The application asked for access to a service this server does not protect.\",\n },\n invalid_scope: {\n status: 400,\n title: \"Unavailable permissions\",\n message:\n \"The application asked for permissions that are not available to it.\",\n },\n consent_required: {\n status: 400,\n title: \"Approval needed\",\n message:\n \"This server always asks you before granting access, but the application asked to skip that step.\",\n },\n access_denied: {\n status: 403,\n title: \"Not permitted\",\n message: \"This application may not connect to this service.\",\n },\n flow_ended: {\n status: 400,\n title: \"This sign-in has ended\",\n message:\n \"The sign-in you returned from is no longer open, either because it took too long or because it was already completed. Please connect again from your application.\",\n },\n malformed_response: {\n status: 400,\n title: \"Malformed response\",\n message:\n \"The identity provider's response could not be used to finish signing in.\",\n },\n browser_mismatch: {\n status: 400,\n title: \"Different browser\",\n message:\n \"This sign-in has to finish in the browser where it began. Open your application on this device and connect again.\",\n },\n sign_in_not_confirmed: {\n status: 400,\n title: \"Sign-in not confirmed\",\n message:\n \"Your identity provider did not confirm who you are. Please connect again from your application.\",\n },\n subject_not_permitted: {\n status: 403,\n title: \"Access not permitted\",\n message: \"The account you signed in with may not connect this application.\",\n },\n consent_unavailable: {\n status: 400,\n title: \"Nothing to approve\",\n message:\n \"There is no open request to approve here. It may have been answered already, taken too long, or been opened in another browser. Please connect again from your application.\",\n },\n unsupported_submission: {\n status: 415,\n title: \"Unexpected submission\",\n message: \"Your answer must be sent with the form on the previous page.\",\n },\n server_error: {\n status: 500,\n title: \"Unexpected problem\",\n message:\n \"Something on this server stopped the request from finishing. Please start again from your application.\",\n },\n}\n", "import type { ResolvedOptions } from \"../interfaces/options.js\"\n\n/**\n * The client address used as the rate-limit key: `getClientIp` when given,\n * else the right-most `X-Forwarded-For` entry when `trustProxy` is set, else\n * `\"unknown\"`. The right-most entry is the one the nearest proxy added; the\n * others are supplied by the client and can be forged.\n *\n * @param options - Checked options.\n * @param request - The request.\n * @returns The key.\n * @internal\n */\nexport function clientIp(\n options: Pick<ResolvedOptions, \"getClientIp\" | \"trustProxy\">,\n request: Request,\n): string {\n const custom = options.getClientIp?.(request)\n\n if (custom !== undefined && custom !== \"\") return custom\n\n if (!options.trustProxy) return \"unknown\"\n\n const forwarded = request.headers.get(\"x-forwarded-for\")\n const hops = (forwarded ?? \"\")\n .split(\",\")\n .map((hop) => hop.trim())\n .filter((hop) => hop !== \"\")\n\n return hops[hops.length - 1] ?? \"unknown\"\n}\n", "/** Most language tags taken from one `Accept-Language` header. */\nconst LOCALE_LIMIT = 10\n\n/** A BCP 47-shaped language tag (letters, digits and hyphens). */\nconst TAG_PATTERN = /^[A-Za-z]{1,8}(?:-[A-Za-z0-9]{1,8})*$/\n\n/**\n * The languages a browser prefers, from its `Accept-Language` header, most\n * preferred first. Malformed entries, `*` and `q=0` entries are dropped.\n *\n * @param header - The raw `Accept-Language` header value, or `null`.\n * @returns Up to 10 language tags, such as `[\"de-CH\", \"de\", \"en\"]`.\n * @internal\n */\nexport function parseLocales(header: string | null): string[] {\n if (header === null) return []\n\n const ranked: { tag: string; q: number; order: number }[] = []\n\n for (const [order, part] of header.split(\",\").entries()) {\n const [rawTag = \"\", ...params] = part.trim().split(\";\")\n const tag = rawTag.trim()\n const qParam = params.map((p) => p.trim()).find((p) => p.startsWith(\"q=\"))\n const q = qParam === undefined ? 1 : Number(qParam.slice(2))\n\n if (TAG_PATTERN.test(tag) && Number.isFinite(q) && q > 0 && q <= 1) {\n ranked.push({ tag, q, order })\n }\n }\n\n ranked.sort((a, b) => b.q - a.q || a.order - b.order)\n\n return [...new Set(ranked.map((entry) => entry.tag))].slice(0, LOCALE_LIMIT)\n}\n", "import { isRecord } from \"../helpers/guards.js\"\nimport type { MaybePromise, PolicyDecision } from \"../interfaces/policy.js\"\n\n/** A refusal reason: short, machine-readable, safe to pass to a renderer. */\nconst REASON_PATTERN = /^[A-Za-z0-9_.:-]{1,64}$/\n\n/** The outcome of a policy hook, after validation. */\nexport interface PolicyOutcome {\n /** Whether the request may continue. */\n readonly allowed: boolean\n /** The hook's reason for a refusal, when it gave a valid one. */\n readonly reason?: string\n}\n\n/**\n * Run a policy hook that may only refuse.\n *\n * Only a literal `true`, or an object whose `allow` is the literal `true`,\n * lets the request continue. `false`, any other value, a rejection or a\n * throw refuses, so a broken hook can never open a door. A refusal may carry\n * a `reason` of 1\u201364 characters from `A-Z a-z 0-9 _ . : -`; any other reason\n * is dropped (the request is still refused).\n *\n * @param check - Calls the hook; may return a value or a promise.\n * @returns Whether the request may continue, and a valid refusal reason.\n * @internal\n */\nexport async function decide(\n check: () => MaybePromise<PolicyDecision>,\n): Promise<PolicyOutcome> {\n let verdict: unknown\n\n try {\n // Hooks are written in plain JavaScript too: trust no declared type.\n verdict = await check()\n } catch {\n return { allowed: false }\n }\n\n return judge(verdict)\n}\n\n/**\n * Read a policy verdict that has already been obtained (see {@link decide}).\n *\n * @param verdict - Whatever the hook returned.\n * @returns Whether the request may continue, and a valid refusal reason.\n * @internal\n */\nexport function judge(verdict: unknown): PolicyOutcome {\n if (verdict === true) return { allowed: true }\n\n if (!isRecord(verdict)) return { allowed: false }\n\n if (verdict.allow === true) return { allowed: true }\n\n const reason = verdict.reason\n\n return typeof reason === \"string\" && REASON_PATTERN.test(reason)\n ? { allowed: false, reason }\n : { allowed: false }\n}\n\n/**\n * Run a policy hook and report only whether the request may continue.\n *\n * @param check - Calls the hook; may return a value or a promise.\n * @returns `true` only for an allowing verdict (see {@link decide}).\n * @internal\n */\nexport async function permits(\n check: () => MaybePromise<PolicyDecision>,\n): Promise<boolean> {\n return (await decide(check)).allowed\n}\n", "import { ERROR_PAGES } from \"../constants/error-pages.js\"\nimport { clientIp } from \"../helpers/client-ip.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\nimport { parseLocales } from \"../helpers/locales.js\"\nimport { permits } from \"./policy.js\"\nimport { html } from \"../helpers/responses.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { ResolvedOptions } from \"../interfaces/options.js\"\n\n/**\n * Build the context shared by every handler of one `createMcpAuth` instance.\n *\n * @param options - Checked options.\n * @returns The handler context.\n * @internal\n */\nexport function createContext(options: ResolvedOptions): HandlerContext {\n const emit: HandlerContext[\"emit\"] = async (event) => {\n if (!options.onEvent) return\n\n try {\n await options.onEvent(event)\n } catch {\n // Logging trouble must not change an authorization outcome.\n }\n }\n\n return {\n options,\n cimd: { failures: new Map(), inflight: new Map() },\n emit,\n async allowed(request, scope) {\n if (!options.rateLimit) return true\n\n const limiter = options.rateLimit\n // Same rule as the policy hooks: only a literal `true` admits the\n // request; any other value, a rejection or a throw refuses it.\n const ok = await permits(() => limiter(clientIp(options, request), scope))\n\n if (!ok) await emit({ type: \"request_throttled\", scope })\n\n return ok\n },\n errorPage(request, reason, extra = {}) {\n const text = ERROR_PAGES[reason]\n const page = options.renderErrorPage(\n frozenCopy({\n status: text.status,\n reason,\n title: text.title,\n message: text.message,\n locales: parseLocales(request.headers.get(\"accept-language\")),\n ...(extra.detail !== undefined ? { detail: extra.detail } : {}),\n ...(extra.policyReason !== undefined\n ? { policyReason: extra.policyReason }\n : {}),\n }),\n )\n\n return html(text.status, page, { setCookies: extra.setCookies ?? [] })\n },\n }\n}\n", "/**\n * Characters a display name must never contain: controls (Cc, which covers\n * C0, DEL and C1), every format character (Cf: soft hyphen, zero-width\n * space/joiners, LRM/RLM, bidirectional embeddings, overrides and isolates,\n * word joiner and invisible operators, BOM, ...), line and paragraph\n * separators (Zl, Zp), private-use (Co), lone surrogates (Cs) and\n * unassigned code points (Cn).\n */\nconst DISALLOWED = /[\\p{Cc}\\p{Cf}\\p{Zl}\\p{Zp}\\p{Co}\\p{Cs}\\p{Cn}]/u\n\n/**\n * True when `value` has a character that is invisible or changes how the\n * surrounding text is displayed. Such characters let an application name\n * pass itself off as another one on the consent page.\n *\n * @param value - Self-declared text to show a person, such as a client name or scope description.\n * @returns `true` when such a character is present.\n * @internal\n */\nexport function hasHiddenCharacters(value: string): boolean {\n return DISALLOWED.test(value)\n}\n\nconst HTML_ESCAPES: Readonly<Record<string, string>> = {\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#39;\",\n}\n\n/**\n * Escape a value for HTML text AND for a double- or single-quoted attribute.\n * Every interpolated value on a page goes through this.\n *\n * @param value - Text to escape.\n * @returns The escaped text.\n * @example\n * ```ts\n * import { type ConsentPageContext, escapeHtml } from \"@gydr-ai/mcp-oauth\"\n *\n * export function renderConsentPage(context: ConsentPageContext): string {\n * const fields = Object.entries(context.hiddenFields)\n * .map(([name, value]) => `<input type=\"hidden\" name=\"${escapeHtml(name)}\" value=\"${escapeHtml(value)}\">`)\n * .join(\"\")\n *\n * return `<form method=\"post\" action=\"${escapeHtml(context.formAction)}\">${fields}\n * <button name=\"${escapeHtml(context.decisionField)}\" value=\"approve\">Allow</button>\n * <button name=\"${escapeHtml(context.decisionField)}\" value=\"deny\">Deny</button></form>`\n * }\n * ```\n */\nexport function escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (char) => HTML_ESCAPES[char] ?? char)\n}\n\n/**\n * True for an RFC 6749 \u00A7 3.3 scope-token: one or more of\n * %x21 / %x23-5B / %x5D-7E.\n *\n * @param value - One space-separated element of a `scope` value.\n * @returns `true` for a valid scope token.\n * @internal\n */\nexport function isScopeToken(value: string): boolean {\n if (value.length === 0) return false\n\n for (let i = 0; i < value.length; i += 1) {\n const code = value.charCodeAt(i)\n\n if (\n code !== 0x21 &&\n !(code >= 0x23 && code <= 0x5b) &&\n !(code >= 0x5d && code <= 0x7e)\n ) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Remove every trailing occurrence of one character, in linear time. Used in\n * place of an end-anchored repeat pattern such as `/\\/+$/`, whose regular\n * expression engine cost grows polynomially on long runs of the character.\n *\n * @param text - The string to trim, for example a URL path or host name.\n * @param char - The single character to strip from the end, such as `\"/\"`.\n * @returns `text` without any trailing `char`.\n */\nexport function trimTrailing(text: string, char: string): string {\n let end = text.length\n\n while (end > 0 && text[end - 1] === char) end -= 1\n\n return text.slice(0, end)\n}\n", "import { escapeHtml } from \"./text.js\"\n\n/**\n * Wrap page content in a complete, self-contained HTML document with inline\n * styles only, so it works under the pages' Content-Security-Policy.\n *\n * @param title - Page title; escaped here.\n * @param body - Body HTML; the caller escapes its values.\n * @returns The HTML document.\n * @internal\n */\nexport function document(title: string, body: string): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width\">\n<title>${escapeHtml(title)}</title>\n<style>\nhtml{font:16px/1.55 ui-sans-serif,system-ui,sans-serif;background:Canvas;color:CanvasText}\nbody{margin:0 auto;max-width:34rem;padding:2rem 1rem}\nh1{font-size:1.3rem;font-weight:600}\n.facts{padding:.75rem 1rem;border-left:3px solid GrayText;margin:1.25rem 0}\n.facts span{display:block;font-size:.8rem;text-transform:uppercase;letter-spacing:.04em;opacity:.7}\n.facts p{margin:.6rem 0;overflow-wrap:anywhere}\n.note{padding:.75rem 1rem;border:1px dashed #b45309}\n.choices{display:flex;flex-wrap:wrap;gap:.75rem;margin-top:1.5rem}\nbutton{font:inherit;padding:.55rem 1.4rem;border:1px solid GrayText;background:transparent;color:inherit;cursor:pointer}\nbutton.go{background:#0f766e;border-color:#0f766e;color:#fff}\n</style>\n</head>\n<body>\n${body}\n</body>\n</html>`\n}\n", "import { document } from \"../helpers/html-document.js\"\nimport { escapeHtml } from \"../helpers/text.js\"\nimport type {\n ConsentPageContext,\n ErrorPageContext,\n} from \"../interfaces/pages.js\"\n\n/**\n * The sentence telling the person who vouches for the application.\n *\n * @param context - Consent page data.\n * @returns HTML for the sentence.\n */\nfunction provenance(context: ConsentPageContext): string {\n const unchecked = `<p class=\"note\"><strong>Self-registered application.</strong> Nobody has checked the name above. Go on only if you just asked this application to connect.</p>`\n\n switch (context.clientKind) {\n case \"static\":\n return \"<p>The operator of this service added this application.</p>\"\n case \"metadata_document\":\n return context.clientHost === undefined || context.clientHost === \"\"\n ? unchecked\n : `<p>The description of this application is published at <strong>${escapeHtml(context.clientHost)}</strong>. The name above is the application's own claim.</p>`\n case \"dynamic\":\n return unchecked\n }\n}\n\n/**\n * The extra fields `readConsentFields` asked to correct, shown again with\n * their messages and the values the person sent, all escaped.\n *\n * @param context - Consent page data.\n * @returns HTML for the fields, or an empty string on a first showing.\n */\nfunction correctionFields(context: ConsentPageContext): string {\n const errors = context.fieldErrors ?? {}\n const submitted = context.submitted ?? {}\n const names = [\n ...new Set([...Object.keys(errors), ...Object.keys(submitted)]),\n ]\n\n if (names.length === 0) return \"\"\n\n const items = names.map((name, index) => {\n const id = `field-${index}`\n const message = Object.hasOwn(errors, name) ? errors[name] : undefined\n const value = Object.hasOwn(submitted, name) ? submitted[name] : undefined\n const note =\n message === undefined\n ? \"\"\n : `<span class=\"note\" id=\"${id}-error\">${escapeHtml(message)}</span>`\n const described =\n message === undefined\n ? \"\"\n : ` aria-invalid=\"true\" aria-describedby=\"${id}-error\"`\n\n return `<p><label for=\"${id}\">${escapeHtml(name)}</label>\n<input type=\"text\" id=\"${id}\" name=\"${escapeHtml(name)}\" value=\"${escapeHtml(value ?? \"\")}\"${described}>\n${note}</p>`\n })\n\n return `<section class=\"facts\" role=\"alert\">${items.join(\"\\n\")}</section>`\n}\n\n/**\n * The consent page used unless `renderConsentPage` is set.\n *\n * @param context - Consent page data.\n * @returns The HTML document.\n * @internal\n */\nexport function defaultConsentPage(context: ConsentPageContext): string {\n const title = context.clientName ?? \"An application without a name\"\n const delivery = context.redirectIsLoopback\n ? `a program running on this device (${escapeHtml(context.redirectHost)})`\n : escapeHtml(context.redirectHost)\n const account =\n context.subjectDisplayName === undefined\n ? \"\"\n : `<p>You are signed in as <strong>${escapeHtml(context.subjectDisplayName)}</strong>.</p>`\n const scopeItems = context.scopes\n .map((scope) => {\n // Own properties only: a scope named like a prototype member\n // (`constructor`, `toString`) has no description unless configured.\n const description = Object.hasOwn(context.scopeDescriptions, scope)\n ? context.scopeDescriptions[scope]\n : undefined\n const detail =\n description === undefined ? \"\" : ` \u2014 ${escapeHtml(description)}`\n\n return `<li><code>${escapeHtml(scope)}</code>${detail}</li>`\n })\n .join(\"\")\n const fields = Object.entries(context.hiddenFields)\n .map(\n ([name, value]) =>\n `<input type=\"hidden\" name=\"${escapeHtml(name)}\" value=\"${escapeHtml(value)}\">`,\n )\n .join(\"\\n\")\n const decision = escapeHtml(context.decisionField)\n const corrections = correctionFields(context)\n\n return document(\n \"Connect application\",\n `<h1>Connect <strong>${escapeHtml(title)}</strong>?</h1>\n${account}\n${provenance(context)}\n<section class=\"facts\">\n<p><span>Permissions</span></p><ul>${scopeItems}</ul>\n<p><span>Service</span> ${escapeHtml(context.resource)}</p>\n<p><span>Result goes to</span> ${delivery}</p>\n<p><span>Application id</span> <code>${escapeHtml(context.clientId)}</code></p>\n</section>\n<p>Connect only applications you recognise. The connection can be removed again at any time.</p>\n<form method=\"post\" action=\"${escapeHtml(context.formAction)}\">\n${fields}\n${corrections}\n<p class=\"choices\">\n<button type=\"submit\" name=\"${decision}\" value=\"approve\" class=\"go\">Connect</button>\n<button type=\"submit\" name=\"${decision}\" value=\"deny\">Cancel</button>\n</p>\n</form>`,\n )\n}\n\n/**\n * The error page used unless `renderErrorPage` is set.\n *\n * @param context - Error page data.\n * @returns The HTML document.\n * @internal\n */\nexport function defaultErrorPage(context: ErrorPageContext): string {\n return document(\n context.title,\n `<h1>${escapeHtml(context.title)}</h1>\n<p>${escapeHtml(context.message)}</p>${\n context.detail === undefined\n ? \"\"\n : `\\n<p class=\"note\">${escapeHtml(context.detail)}</p>`\n }`,\n )\n}\n", "import { CLIENT_ID_LENGTH_LIMIT } from \"../constants/defaults.js\"\nimport type { SupportedGrantType } from \"../interfaces/records.js\"\nimport { isStringArray } from \"./guards.js\"\nimport { hasHiddenCharacters } from \"./text.js\"\n\n/**\n * Does this `client_id` point at a Client ID Metadata Document? It must be an\n * `https` URL with a non-root path and no credentials or fragment, written in\n * exactly the form a URL parser would print it (so the identifier and the\n * address that is fetched can never differ: no dot segments, no case or\n * percent-encoding variants).\n *\n * @param clientId - The raw `client_id` parameter, unvalidated.\n * @returns `true` for a metadata-document client id.\n * @internal\n */\nexport function isMetadataDocumentClientId(clientId: string): boolean {\n if (\n clientId.length > CLIENT_ID_LENGTH_LIMIT ||\n !clientId.startsWith(\"https://\") ||\n clientId.includes(\"#\")\n ) {\n return false\n }\n\n let parsed: URL\n\n try {\n parsed = new URL(clientId)\n } catch {\n return false\n }\n\n return (\n parsed.hostname !== \"\" &&\n parsed.username === \"\" &&\n parsed.password === \"\" &&\n parsed.pathname.length > 1 &&\n parsed.href === clientId\n )\n}\n\n/**\n * A self-declared client name, trimmed, or `undefined` when it is empty,\n * longer than `maxLength`, or contains invisible or control characters.\n *\n * @param value - The raw `client_name` from registration metadata or a metadata document, of unknown type.\n * @param maxLength - Longest name accepted after trimming, in UTF-16 code units (`limits.clientNameLength`).\n * @returns The cleaned name, or `undefined`.\n * @internal\n */\nexport function cleanClientName(\n value: unknown,\n maxLength: number,\n): string | undefined {\n if (typeof value !== \"string\") return undefined\n\n const name = value.trim()\n\n if (name.length === 0 || name.length > maxLength) return undefined\n\n return hasHiddenCharacters(name) ? undefined : name\n}\n\n/**\n * Whether a grant type is one this server issues.\n *\n * @param value - A grant type name.\n * @returns `true` for `authorization_code` or `refresh_token`.\n */\nfunction isSupportedGrantType(value: string): value is SupportedGrantType {\n return value === \"authorization_code\" || value === \"refresh_token\"\n}\n\n/**\n * Interpret registered `grant_types`. Absent means `authorization_code` alone\n * (RFC 7591 \u00A7 2). The list must contain `authorization_code`; with `strict`,\n * any type this server cannot issue makes it invalid, otherwise such types\n * are dropped. Returns `undefined` when invalid.\n *\n * @param value - The raw `grant_types` metadata value, of unknown type; `undefined` means omitted.\n * @param strict - `true` for registration (any unknown grant type refuses), `false` for metadata documents (unknown types are dropped).\n * @returns The grant types, or `undefined` when invalid.\n * @internal\n */\nexport function parseGrantTypes(\n value: unknown,\n strict: boolean,\n): SupportedGrantType[] | undefined {\n if (value === undefined) return [\"authorization_code\"]\n\n if (!isStringArray(value) || !value.includes(\"authorization_code\")) {\n return undefined\n }\n\n const known = value.filter(isSupportedGrantType)\n\n if (strict && known.length !== value.length) return undefined\n\n return [...new Set(known)]\n}\n\n/**\n * Interpret a registered `scope` string against the supported scopes.\n * `null`: none registered. `undefined`: invalid (not a string, too long,\n * nothing supported, or \u2014 with `strict` \u2014 anything unsupported).\n *\n * @param value - The raw `scope` metadata value, of unknown type; `undefined` means omitted.\n * @param supported - The server's supported scopes.\n * @param strict - `true` for registration (an unsupported scope refuses), `false` for metadata documents (unsupported scopes are dropped).\n * @param maxLength - Longest `scope` string accepted (`limits.scopeLength`).\n * @returns The scopes, `null` when none were registered, or `undefined` when invalid.\n * @internal\n */\nexport function parseClientScopes(\n value: unknown,\n supported: readonly string[],\n strict: boolean,\n maxLength: number,\n): string[] | undefined | null {\n if (value === undefined) return null\n\n if (typeof value !== \"string\" || value.length > maxLength) return undefined\n\n const requested = [...new Set(value.split(\" \").filter((s) => s.length > 0))]\n const usable = requested.filter((s) => supported.includes(s))\n\n if (usable.length === 0) return undefined\n\n if (strict && usable.length < requested.length) return undefined\n\n return usable\n}\n", "/**\n * Which redirect URIs a client may use, and when a requested one counts as\n * registered. Authorization codes are delivered to these addresses, so the\n * rules are deliberately narrow:\n *\n * - allowed: `https` with a host, or plain `http` to a loopback host\n * (`127.0.0.1`, `[::1]`, `localhost`) for native apps (RFC 8252 \u00A7 7.3);\n * never a fragment (RFC 6749 \u00A7 3.1.2), never embedded credentials;\n * - comparison is character-for-character, except that a loopback URI may\n * name any port, because native apps bind an ephemeral one.\n */\n\nimport { LOOPBACK_HOSTS } from \"../constants/protocol.js\"\n\n/**\n * Default for {@link redirectUriProblem}'s length limit.\n *\n * @internal\n */\nexport const DEFAULT_REDIRECT_URI_LENGTH = 2048\n\n/**\n * Why a redirect URI is not allowed.\n *\n * @internal\n */\nexport type RedirectUriProblem =\n | \"unparseable\"\n | \"over_length\"\n | \"has_fragment\"\n | \"has_credentials\"\n | \"disallowed_scheme\"\n | \"reserved_query_parameter\"\n\n/**\n * Parameters the server adds to the redirect; a registered URI may not\n * carry them already, or the client could read a forged value (RFC 9700\n * \u00A7 4.1.1, OAuth 2.1 \u00A7 7.5.4.1, RFC 9207).\n */\nconst RESERVED_REDIRECT_PARAMETERS: readonly string[] = [\n \"code\",\n \"state\",\n \"iss\",\n \"error\",\n \"error_description\",\n \"error_uri\",\n]\n\nfunction tryParse(text: string): URL | undefined {\n try {\n return new URL(text)\n } catch {\n return undefined\n }\n}\n\nfunction isLoopbackHttp(url: URL): boolean {\n return url.protocol === \"http:\" && LOOPBACK_HOSTS.has(url.hostname)\n}\n\n/**\n * True for a plain-`http` loopback URI.\n *\n * @param uri - A redirect URI, possibly unparseable.\n * @returns `true` for a loopback URI.\n * @internal\n */\nexport function isLoopbackRedirectUri(uri: string): boolean {\n const url = tryParse(uri)\n\n return url !== undefined && isLoopbackHttp(url)\n}\n\n/**\n * The reason `uri` is unusable for redirection, or `undefined` when usable.\n *\n * @param uri - A redirect URI from registration, a metadata document or configuration, unvalidated.\n * @param limits - Length limit to apply. Default: 2048 characters.\n * @param limits.redirectUriLength - Longest URI accepted, in characters (`limits.redirectUriLength`).\n * @returns The problem, or `undefined` when the URI is usable.\n * @internal\n */\nexport function redirectUriProblem(\n uri: string,\n limits: { readonly redirectUriLength: number } = {\n redirectUriLength: DEFAULT_REDIRECT_URI_LENGTH,\n },\n): RedirectUriProblem | undefined {\n if (uri.length > limits.redirectUriLength) return \"over_length\"\n\n const url = tryParse(uri)\n\n if (url === undefined) return \"unparseable\"\n\n // Checked on the raw text: the parser forgets an empty fragment.\n if (uri.includes(\"#\")) return \"has_fragment\"\n\n if (url.username.length > 0 || url.password.length > 0) {\n return \"has_credentials\"\n }\n\n const secure = url.protocol === \"https:\" && url.hostname.length > 0\n\n if (!secure && !isLoopbackHttp(url)) return \"disallowed_scheme\"\n\n return RESERVED_REDIRECT_PARAMETERS.some((name) => url.searchParams.has(name))\n ? \"reserved_query_parameter\"\n : undefined\n}\n\n/**\n * True when `uri` is allowed as a redirect URI.\n *\n * @param uri - A redirect URI, unvalidated.\n * @param limits - Length limit to apply. Default: 2048 characters.\n * @param limits.redirectUriLength - Longest URI accepted, in characters.\n * @returns `true` when usable.\n * @internal\n */\nexport function isAcceptableRedirectUri(\n uri: string,\n limits?: { readonly redirectUriLength: number },\n): boolean {\n return redirectUriProblem(uri, limits) === undefined\n}\n\nfunction isDigit(code: number): boolean {\n return code >= 48 && code <= 57\n}\n\n/**\n * `uri` with the `:port` after a loopback host removed, host lower-cased.\n *\n * @param uri - The raw URI text, exactly as registered or requested.\n * @param url - The same URI parsed; only its scheme and hostname are used.\n * @returns The URI without its port.\n */\nfunction portless(uri: string, url: URL): string {\n const head = `${url.protocol}//${url.hostname}`\n\n if (uri.slice(0, head.length).toLowerCase() !== head.toLowerCase()) {\n return uri\n }\n\n let tail = uri.slice(head.length)\n\n if (tail.startsWith(\":\")) {\n let i = 1\n\n while (i < tail.length && isDigit(tail.charCodeAt(i))) i += 1\n tail = tail.slice(i)\n }\n\n return head.toLowerCase() + tail\n}\n\n/**\n * Is `requested` the same redirect URI as the registered one?\n *\n * @param registered - A redirect URI stored on the client, already validated.\n * @param requested - The raw `redirect_uri` query value, unvalidated.\n * @param limits - Length limit for the requested URI. Default: 2048 characters.\n * @param limits.redirectUriLength - Longest URI accepted, in characters.\n * @returns `true` on a match.\n * @internal\n */\nexport function redirectUriMatches(\n registered: string,\n requested: string,\n limits?: { readonly redirectUriLength: number },\n): boolean {\n if (!isAcceptableRedirectUri(requested, limits)) return false\n\n if (requested === registered) return true\n\n const a = tryParse(registered)\n const b = tryParse(requested)\n\n if (a === undefined || b === undefined) return false\n\n if (!isLoopbackHttp(a) || !isLoopbackHttp(b)) return false\n\n // Compare the text, not parsed fields: parsing normalizes paths, and a\n // normalized match is not an exact one.\n return portless(registered, a) === portless(requested, b)\n}\n\n/**\n * Does `requested` match one of the registered URIs?\n *\n * @param registered - Redirect URIs stored on the client.\n * @param requested - The raw `redirect_uri` query value, unvalidated.\n * @param limits - Length limit for the requested URI. Default: 2048 characters.\n * @param limits.redirectUriLength - Longest URI accepted, in characters.\n * @returns `true` on a match.\n * @internal\n */\nexport function matchesAnyRedirectUri(\n registered: readonly string[],\n requested: string,\n limits?: { readonly redirectUriLength: number },\n): boolean {\n return registered.some((uri) => redirectUriMatches(uri, requested, limits))\n}\n\n/**\n * Host to show a person: loopback without its port, otherwise `host`.\n *\n * @param uri - A redirect URI already matched against the client's registration.\n * @returns The host, or an empty string.\n * @internal\n */\nexport function redirectHostOf(uri: string): string {\n const url = tryParse(uri)\n\n if (url === undefined) return \"\"\n\n return isLoopbackHttp(url) ? url.hostname : url.host\n}\n\n/**\n * `scheme://host[:port]` of a URI, or `undefined`.\n *\n * @param uri - A redirect URI already matched against the client's registration.\n * @returns The origin, or `undefined`.\n * @internal\n */\nexport function originOf(uri: string): string | undefined {\n return tryParse(uri)?.origin\n}\n\n/**\n * Add response parameters to a redirect URI that has already been matched,\n * keeping any query it has. `undefined` values are left out.\n *\n * @param redirectUri - A redirect URI already matched against the client's registration; never pass an unchecked value.\n * @param params - Query parameters to append (`code`, `state`, `iss`, `error`\u2026); `undefined` values are skipped.\n * @returns The redirect URL.\n * @internal\n */\nexport function buildRedirect(\n redirectUri: string,\n params: Readonly<Record<string, string | undefined>>,\n): string {\n const target = new URL(redirectUri)\n\n for (const [name, value] of Object.entries(params)) {\n if (value !== undefined) target.searchParams.append(name, value)\n }\n\n return target.toString()\n}\n", "import { McpAuthConfigError } from \"../errors/mcp-auth-config-error.js\"\nimport { freezeDeep } from \"../helpers/freeze.js\"\nimport {\n cleanClientName,\n isMetadataDocumentClientId,\n parseGrantTypes,\n} from \"../helpers/client-fields.js\"\nimport { redirectUriProblem } from \"../helpers/redirect-uris.js\"\nimport { hasHiddenCharacters } from \"../helpers/text.js\"\nimport type { Limits } from \"../interfaces/options.js\"\nimport type { StaticClient } from \"../interfaces/policy.js\"\nimport type { ClientRecord } from \"../interfaces/records.js\"\n\n/**\n * Check scope descriptions: each key must be a supported scope and each text\n * a visible string of at most 200 characters.\n *\n * @param scopes - The supported scopes after defaults were applied; every description key must be one of them.\n * @param descriptions - Configured descriptions.\n * @returns The descriptions.\n * @throws {McpAuthConfigError} When a key or text is invalid.\n * @internal\n */\nexport function resolveScopeDescriptions(\n scopes: readonly string[],\n descriptions: Readonly<Record<string, string>> = {},\n): Readonly<Record<string, string>> {\n for (const [scope, text] of Object.entries(descriptions)) {\n if (!scopes.includes(scope)) {\n throw new McpAuthConfigError(\n `scopeDescriptions names ${scope}, which is not in scopes`,\n )\n }\n\n if (text.trim() === \"\" || text.length > 200 || hasHiddenCharacters(text)) {\n throw new McpAuthConfigError(\n `scopeDescriptions.${scope} must be 1 to 200 visible characters`,\n )\n }\n }\n\n // A null-prototype copy, so lookups never reach Object.prototype.\n const own: Record<string, string> = Object.fromEntries(\n Object.entries(descriptions),\n )\n\n Object.setPrototypeOf(own, null)\n\n return Object.freeze(own)\n}\n\n/**\n * Check the pre-registered clients with the same rules as registration.\n *\n * @param clients - Configured clients.\n * @param scopes - The supported scopes after defaults were applied; a client's scopes must be a subset.\n * @param limits - Checked limits.\n * @param now - Clock.\n * @returns The clients by id.\n * @throws {McpAuthConfigError} When a client is invalid or listed twice.\n * @internal\n */\nexport function resolveStaticClients(\n clients: readonly StaticClient[] = [],\n scopes: readonly string[],\n limits: Required<Limits>,\n now: () => number,\n): ReadonlyMap<string, ClientRecord> {\n const byId = new Map<string, ClientRecord>()\n\n for (const client of clients) {\n const id = client.clientId\n const label = `clients[${JSON.stringify(id)}]`\n\n if (id.trim() === \"\" || id.length > 255 || hasHiddenCharacters(id)) {\n throw new McpAuthConfigError(\n `${label}: clientId must be 1 to 255 visible characters`,\n )\n }\n\n if (isMetadataDocumentClientId(id) || /^https:/i.test(id)) {\n throw new McpAuthConfigError(\n `${label}: clientId must not be an https URL`,\n )\n }\n\n if (byId.has(id)) throw new McpAuthConfigError(`${label}: listed twice`)\n\n if (\n client.redirectUris.length === 0 ||\n client.redirectUris.length > limits.redirectUris\n ) {\n throw new McpAuthConfigError(\n `${label}: list 1 to ${limits.redirectUris} redirectUris`,\n )\n }\n\n for (const uri of client.redirectUris) {\n const problem = redirectUriProblem(uri, limits)\n\n if (problem !== undefined) {\n throw new McpAuthConfigError(\n `${label}: redirect URI refused (${problem})`,\n )\n }\n }\n\n const clientName =\n client.clientName === undefined\n ? undefined\n : cleanClientName(client.clientName, limits.clientNameLength)\n\n if (client.clientName !== undefined && clientName === undefined) {\n throw new McpAuthConfigError(\n `${label}: clientName is not a valid display name`,\n )\n }\n\n const grantTypes = parseGrantTypes(\n client.grantTypes ?? [\"authorization_code\", \"refresh_token\"],\n true,\n )\n\n if (grantTypes === undefined) {\n throw new McpAuthConfigError(\n `${label}: grantTypes must include authorization_code`,\n )\n }\n\n if (client.scopes?.some((scope) => !scopes.includes(scope)) === true) {\n throw new McpAuthConfigError(\n `${label}: scopes must be a subset of scopes`,\n )\n }\n\n if (client.scopes?.length === 0) {\n throw new McpAuthConfigError(`${label}: scopes must not be empty`)\n }\n\n // Frozen: the record is shared with every request and every hook.\n byId.set(\n id,\n freezeDeep({\n clientId: id,\n redirectUris: [...new Set(client.redirectUris)],\n kind: \"static\",\n createdAt: now(),\n grantTypes,\n ...(client.scopes !== undefined ? { scopes: [...client.scopes] } : {}),\n ...(clientName !== undefined ? { clientName } : {}),\n }),\n )\n }\n\n return byId\n}\n", "import { trimTrailing } from \"../helpers/text.js\"\nimport { DEFAULT_LIMITS } from \"../constants/defaults.js\"\nimport { LOOPBACK_HOSTS } from \"../constants/protocol.js\"\nimport { McpAuthConfigError } from \"../errors/mcp-auth-config-error.js\"\nimport type { Limits, RoutePaths } from \"../interfaces/options.js\"\n\n/**\n * Parse and check the issuer or resource URL.\n *\n * @param name - Option name, for error messages.\n * @param value - The configured URL.\n * @param allowInsecureLocalhost - Whether plain `http` on loopback is allowed.\n * @returns The parsed URL, known to be `https` (or loopback `http` when allowed) without query, fragment or credentials.\n * @throws {McpAuthConfigError} When the URL is not absolute, carries a query,\n * fragment or credentials, or is not `https`.\n * @internal\n */\nexport function parseServerUrl(\n name: string,\n value: string,\n allowInsecureLocalhost: boolean,\n): URL {\n let url: URL\n\n try {\n url = new URL(value)\n } catch {\n throw new McpAuthConfigError(`${name} must be an absolute URL`)\n }\n\n if (value.includes(\"#\") || url.search !== \"\" || value.includes(\"?\")) {\n throw new McpAuthConfigError(`${name} must have no query or fragment`)\n }\n\n if (url.username !== \"\" || url.password !== \"\") {\n throw new McpAuthConfigError(`${name} must have no userinfo`)\n }\n\n const local = url.protocol === \"http:\" && LOOPBACK_HOSTS.has(url.hostname)\n\n if (url.protocol !== \"https:\" && !(local && allowInsecureLocalhost)) {\n throw new McpAuthConfigError(\n `${name} must use https (http is allowed only for localhost with allowInsecureLocalhost)`,\n )\n }\n\n return url\n}\n\n/**\n * Remove trailing slashes: `/a/b/` becomes `/a/b`, `/` becomes the empty string.\n *\n * @param pathname - A URL path.\n * @returns The path without trailing slashes.\n * @internal\n */\nexport function trimPath(pathname: string): string {\n return trimTrailing(pathname, \"/\")\n}\n\n/**\n * Turn a lifetime in seconds into milliseconds, checking it.\n *\n * @param name - Lifetime name, for error messages.\n * @param value - Configured seconds, if any.\n * @param fallback - Default seconds.\n * @param min - Smallest allowed value, in seconds.\n * @param max - Largest allowed value, in seconds.\n * @returns The lifetime in milliseconds.\n * @throws {McpAuthConfigError} When the value is not an integer from `min` to `max`.\n * @internal\n */\nexport function seconds(\n name: string,\n value: number | undefined,\n fallback: number,\n min: number,\n max: number,\n): number {\n const chosen = value ?? fallback\n\n if (!Number.isInteger(chosen) || chosen < min || chosen > max) {\n throw new McpAuthConfigError(\n `lifetimes.${name} must be an integer from ${min} to ${max}`,\n )\n }\n\n return chosen * 1000\n}\n\n/**\n * Check one size or count limit.\n *\n * @param name - Limit name, for error messages.\n * @param value - Configured value, if any.\n * @param fallback - Default value.\n * @returns The limit.\n * @throws {McpAuthConfigError} When the value is not a positive integer.\n * @internal\n */\nexport function positive(\n name: string,\n value: number | undefined,\n fallback: number,\n): number {\n const chosen = value ?? fallback\n\n if (!Number.isInteger(chosen) || chosen < 1) {\n throw new McpAuthConfigError(`limits.${name} must be a positive integer`)\n }\n\n return chosen\n}\n\n/**\n * Apply defaults to every limit.\n *\n * @param limits - Configured limits.\n * @returns Every limit, checked.\n * @throws {McpAuthConfigError} When a limit is not a positive integer.\n * @internal\n */\nexport function resolveLimits(limits: Limits = {}): Required<Limits> {\n const pick = (key: keyof Limits): number =>\n positive(key, limits[key], DEFAULT_LIMITS[key])\n\n return {\n clientNameLength: pick(\"clientNameLength\"),\n redirectUris: pick(\"redirectUris\"),\n redirectUriLength: pick(\"redirectUriLength\"),\n stateLength: pick(\"stateLength\"),\n scopeLength: pick(\"scopeLength\"),\n registrationBytes: pick(\"registrationBytes\"),\n formBytes: pick(\"formBytes\"),\n clientMetadataBytes: pick(\"clientMetadataBytes\"),\n clientMetadataTimeoutMs: pick(\"clientMetadataTimeoutMs\"),\n clientMetadataFailures: pick(\"clientMetadataFailures\"),\n grantContextBytes: pick(\"grantContextBytes\"),\n consentDetailsBytes: pick(\"consentDetailsBytes\"),\n grantDataBytes: pick(\"grantDataBytes\"),\n }\n}\n\n/**\n * Check one endpoint path.\n *\n * @param name - Path name, for error messages.\n * @param path - The configured path.\n * @returns The path.\n * @throws {McpAuthConfigError} When it does not start with exactly one `/`,\n * or contains `//`, a backslash, `?`, `#`, a dot segment or whitespace.\n * @internal\n */\nexport function checkPath(name: string, path: string): string {\n // `//host/x` and `\\\\host\\x` are protocol-relative on many stacks; a path\n // must be unambiguous wherever it is echoed or compared.\n const segments = path.split(\"/\").slice(1)\n\n if (\n !path.startsWith(\"/\") ||\n path.includes(\"//\") ||\n /[\\\\?#\\s]/.test(path) ||\n segments.some((segment) => segment === \".\" || segment === \"..\")\n ) {\n throw new McpAuthConfigError(\n `paths.${name} must be one absolute path without //, \\\\, ?, #, dot segments or whitespace`,\n )\n }\n\n return path\n}\n\n/**\n * Work out every endpoint path, defaulting to paths under the issuer's path.\n *\n * @param issuerPath - The issuer's path without a trailing slash.\n * @param paths - Configured paths.\n * @returns Every path.\n * @throws {McpAuthConfigError} When a path is invalid or two paths are equal.\n * @internal\n */\nexport function resolvePaths(\n issuerPath: string,\n paths: RoutePaths = {},\n): Required<RoutePaths> {\n const pick = (key: keyof RoutePaths): string =>\n checkPath(key, paths[key] ?? `${issuerPath}/${key}`)\n const resolved: Required<RoutePaths> = {\n authorize: pick(\"authorize\"),\n callback: pick(\"callback\"),\n consent: pick(\"consent\"),\n token: pick(\"token\"),\n register: pick(\"register\"),\n revoke: pick(\"revoke\"),\n }\n const all = Object.values(resolved)\n\n if (new Set(all).size !== all.length) {\n throw new McpAuthConfigError(\"paths must be distinct\")\n }\n\n return resolved\n}\n", "import {\n DEFAULT_CONSENT_REPLAY_SECONDS,\n DEFAULT_COOKIE_PREFIX,\n DEFAULT_LIFETIMES,\n DEFAULT_REFRESH_REUSE_GRACE_SECONDS,\n DEFAULT_SCOPES,\n LIFETIME_BOUNDS,\n MAX_CONSENT_REPLAY_SECONDS,\n MAX_REFRESH_REUSE_GRACE_SECONDS,\n} from \"../constants/defaults.js\"\nimport {\n AUTHORIZATION_SERVER_METADATA_PATH,\n PROTECTED_RESOURCE_METADATA_PATH,\n} from \"../constants/protocol.js\"\nimport { McpAuthConfigError } from \"../errors/mcp-auth-config-error.js\"\nimport { isScopeToken } from \"../helpers/text.js\"\nimport type { McpAuthOptions, ResolvedOptions } from \"../interfaces/options.js\"\nimport {\n resolveScopeDescriptions,\n resolveStaticClients,\n} from \"./resolve-clients.js\"\nimport {\n parseServerUrl,\n resolveLimits,\n resolvePaths,\n seconds,\n trimPath,\n} from \"./resolve-parts.js\"\nimport type {\n ConsentPageContext,\n ErrorPageContext,\n} from \"../interfaces/pages.js\"\nimport { checkedClock } from \"../helpers/time.js\"\n\n/** A CSP script hash: `sha256-` and the base64 of a 32-byte digest. */\nconst SCRIPT_HASH = /^sha256-[A-Za-z0-9+/]{43}=$/\n\n/** A `__Host-` cookie name prefix made of cookie-token characters. */\nconst COOKIE_PREFIX = /^__Host-[A-Za-z0-9_.-]{0,40}$/\n\n/**\n * Apply defaults to the options of `createMcpAuth` and check them.\n *\n * @param options - The options as given.\n * @param renderers - The built-in page renderers, used when none is given.\n * @param renderers.consent - Built-in consent page.\n * @param renderers.error - Built-in error page.\n * @returns The complete, checked options.\n * @throws {McpAuthConfigError} When any option is invalid.\n * @internal\n */\nexport function resolveOptions(\n options: McpAuthOptions,\n renderers: {\n readonly consent: (context: ConsentPageContext) => string\n readonly error: (context: ErrorPageContext) => string\n },\n): ResolvedOptions {\n const insecure = options.allowInsecureLocalhost === true\n const issuerUrl = parseServerUrl(\"issuer\", options.issuer, insecure)\n const issuerPath = trimPath(issuerUrl.pathname)\n const issuer = `${issuerUrl.origin}${issuerPath}`\n\n if (\n options.issuer !== issuer &&\n !(issuerPath === \"\" && options.issuer === `${issuer}/`)\n ) {\n throw new McpAuthConfigError(\n `issuer must be in normalized form, for example ${issuer}`,\n )\n }\n\n const resourceUrl = parseServerUrl(\"resource\", options.resource, insecure)\n const resourcePath = trimPath(resourceUrl.pathname)\n const scopes = options.scopes ?? DEFAULT_SCOPES\n\n if (scopes.length === 0 || !scopes.every(isScopeToken)) {\n throw new McpAuthConfigError(\"scopes must be non-empty scope tokens\")\n }\n\n const defaultScopes = options.defaultScopes ?? scopes\n\n if (\n defaultScopes.length === 0 ||\n !defaultScopes.every((scope) => scopes.includes(scope))\n ) {\n throw new McpAuthConfigError(\"defaultScopes must be a subset of scopes\")\n }\n\n const graceSeconds =\n options.refreshReuseGraceSeconds ?? DEFAULT_REFRESH_REUSE_GRACE_SECONDS\n\n if (\n !Number.isInteger(graceSeconds) ||\n graceSeconds < 0 ||\n graceSeconds > MAX_REFRESH_REUSE_GRACE_SECONDS\n ) {\n throw new McpAuthConfigError(\n `refreshReuseGraceSeconds must be an integer from 0 to ${MAX_REFRESH_REUSE_GRACE_SECONDS}`,\n )\n }\n\n const lifetimes = options.lifetimes ?? {}\n const lifetime = (key: keyof typeof LIFETIME_BOUNDS): number =>\n seconds(\n key,\n lifetimes[key],\n DEFAULT_LIFETIMES[key],\n LIFETIME_BOUNDS[key].min,\n LIFETIME_BOUNDS[key].max,\n )\n const paths = resolvePaths(issuerPath, options.paths)\n const cookiePrefix = options.cookiePrefix ?? DEFAULT_COOKIE_PREFIX\n\n if (!COOKIE_PREFIX.test(cookiePrefix)) {\n throw new McpAuthConfigError(\n \"cookiePrefix must start with __Host- and use only letters, digits, '.', '_' and '-'\",\n )\n }\n\n const limits = resolveLimits(options.limits)\n const now = checkedClock(options.now ?? Date.now)\n const scriptHashes = [...(options.scriptHashes ?? [])]\n\n for (const hash of scriptHashes) {\n if (!SCRIPT_HASH.test(hash)) {\n throw new McpAuthConfigError(\n \"scriptHashes entries must look like sha256-<base64 of 32 bytes>\",\n )\n }\n }\n\n const replaySeconds =\n options.consentReplaySeconds ?? DEFAULT_CONSENT_REPLAY_SECONDS\n\n if (\n !Number.isInteger(replaySeconds) ||\n replaySeconds < 0 ||\n replaySeconds > MAX_CONSENT_REPLAY_SECONDS\n ) {\n throw new McpAuthConfigError(\n `consentReplaySeconds must be an integer from 0 to ${MAX_CONSENT_REPLAY_SECONDS}`,\n )\n }\n\n const prm = PROTECTED_RESOURCE_METADATA_PATH\n const asm = AUTHORIZATION_SERVER_METADATA_PATH\n const resourceMetadataPaths =\n resourcePath === \"\" ? [prm] : [`${prm}${resourcePath}`, prm]\n const authorizationServerMetadataPaths =\n issuerPath === \"\" ? [asm] : [`${asm}${issuerPath}`, asm]\n // An endpoint must not shadow a metadata document or the MCP endpoint\n // itself: `handle()` would answer it and the real route would be unreachable.\n const reserved = new Set([\n ...resourceMetadataPaths,\n ...authorizationServerMetadataPaths,\n resourceUrl.pathname,\n resourcePath === \"\" ? \"/\" : resourcePath,\n ])\n\n for (const [name, path] of Object.entries(paths)) {\n if (reserved.has(path)) {\n throw new McpAuthConfigError(\n `paths.${name} collides with a metadata path or the resource path`,\n )\n }\n }\n\n return {\n issuer,\n issuerOrigin: issuerUrl.origin,\n resource: options.resource,\n resourceMetadataUrl: `${resourceUrl.origin}${prm}${resourcePath}`,\n resourceMetadataPaths,\n authorizationServerMetadataPaths,\n scopes: [...scopes],\n defaultScopes: [...defaultScopes],\n identityProvider: options.identityProvider,\n store: options.store,\n sealer: options.sealer,\n renderConsentPage: options.renderConsentPage ?? renderers.consent,\n renderErrorPage: options.renderErrorPage ?? renderers.error,\n accessTokenTtlMs: lifetime(\"accessToken\"),\n refreshTokenTtlMs: lifetime(\"refreshToken\"),\n grantTtlMs: lifetime(\"grant\"),\n codeTtlMs: lifetime(\"authorizationCode\"),\n pendingTtlMs: lifetime(\"pendingAuthorization\"),\n clientMetadataCacheMs: lifetime(\"clientMetadataCache\"),\n clientMetadataFailureMs: lifetime(\"clientMetadataFailure\"),\n limits,\n cookiePrefix,\n scriptHashes,\n consentReplayMs: replaySeconds * 1000,\n refreshReuseGraceMs: graceSeconds * 1000,\n clientIdMetadataDocuments: options.clientIdMetadataDocuments ?? true,\n dynamicClientRegistration: options.dynamicClientRegistration ?? true,\n paths,\n callbackUrl: `${issuerUrl.origin}${paths.callback}`,\n trustProxy: options.trustProxy ?? false,\n now,\n scopeDescriptions: resolveScopeDescriptions(\n scopes,\n options.scopeDescriptions,\n ),\n staticClients: resolveStaticClients(options.clients, scopes, limits, now),\n ...(options.resourceName !== undefined\n ? { resourceName: options.resourceName }\n : {}),\n ...(options.rateLimit ? { rateLimit: options.rateLimit } : {}),\n ...(options.getClientIp ? { getClientIp: options.getClientIp } : {}),\n ...(options.onEvent ? { onEvent: options.onEvent } : {}),\n ...(options.fetchClientMetadata\n ? { fetchClientMetadata: options.fetchClientMetadata }\n : {}),\n ...(options.authorizeClient\n ? { authorizeClient: options.authorizeClient }\n : {}),\n ...(options.allowRegistration\n ? { allowRegistration: options.allowRegistration }\n : {}),\n ...(options.mapSubject ? { mapSubject: options.mapSubject } : {}),\n ...(options.allowSubject ? { allowSubject: options.allowSubject } : {}),\n ...(options.allowClientMetadataUrl\n ? { allowClientMetadataUrl: options.allowClientMetadataUrl }\n : {}),\n ...(options.getGrantContext\n ? { getGrantContext: options.getGrantContext }\n : {}),\n ...(options.getConsentDetails\n ? { getConsentDetails: options.getConsentDetails }\n : {}),\n ...(options.readConsentFields\n ? { readConsentFields: options.readConsentFields }\n : {}),\n ...(options.beforeGrant ? { beforeGrant: options.beforeGrant } : {}),\n ...(options.afterGrant ? { afterGrant: options.afterGrant } : {}),\n ...(options.beforeTokenIssue\n ? { beforeTokenIssue: options.beforeTokenIssue }\n : {}),\n }\n}\n", "import { safeFetch } from \"@gydr-ai/safe-fetch\"\n\nimport { ClientMetadataError } from \"../errors/client-metadata-error.js\"\nimport {\n cleanClientName,\n parseClientScopes,\n parseGrantTypes,\n} from \"../helpers/client-fields.js\"\nimport { isRecord, isStringArray } from \"../helpers/guards.js\"\nimport { isAcceptableRedirectUri } from \"../helpers/redirect-uris.js\"\nimport type {\n ClientMetadataFetcher,\n ResolvedOptions,\n} from \"../interfaces/options.js\"\nimport type { ClientRecord } from \"../interfaces/records.js\"\n\n/**\n * Turn a fetched document into a client record, or throw\n * {@link ClientMetadataError}. The document must name its own URL as\n * `client_id` and describe a public client. Redirect URIs this server would\n * never use (custom schemes, plain `http` off loopback) are left out; at\n * least one usable URI must remain.\n *\n * @param url - The `client_id` the document was downloaded from; the document's `client_id` must equal it exactly.\n * @param document - The parsed JSON body, of unknown shape.\n * @param now - Time of the download, in milliseconds since the epoch; stored as `createdAt`.\n * @param options - Supported scopes and size limits used to filter the document's values.\n * @returns A `metadata_document` client record with only usable redirect URIs, supported grant types and scopes, and a cleaned name.\n * @throws {ClientMetadataError} When the document is not an object, names another `client_id`, describes a confidential client, or has no usable grant type, response type, scope or redirect URI.\n * @internal\n */\nexport function validateClientMetadata(\n url: string,\n document: unknown,\n now: number,\n options: Pick<ResolvedOptions, \"scopes\" | \"limits\">,\n): ClientRecord {\n if (!isRecord(document)) throw new ClientMetadataError(\"not_an_object\")\n\n if (document.client_id !== url) {\n throw new ClientMetadataError(\"client_id_differs_from_url\")\n }\n\n const methods = document.token_endpoint_auth_methods_supported\n\n // A client that names any way to authenticate other than `none` (singular,\n // or a plural list without `none`) is confidential.\n if (\n (document.token_endpoint_auth_method !== undefined &&\n document.token_endpoint_auth_method !== \"none\") ||\n (methods !== undefined &&\n (!isStringArray(methods) || !methods.includes(\"none\"))) ||\n document.client_secret !== undefined\n ) {\n throw new ClientMetadataError(\"confidential_client\")\n }\n\n const grantTypes = parseGrantTypes(document.grant_types, false)\n\n if (grantTypes === undefined) {\n throw new ClientMetadataError(\"grant_types_unusable\")\n }\n\n if (\n document.response_types !== undefined &&\n (!isStringArray(document.response_types) ||\n !document.response_types.includes(\"code\"))\n ) {\n throw new ClientMetadataError(\"response_types_unusable\")\n }\n\n const scopes = parseClientScopes(\n document.scope,\n options.scopes,\n false,\n options.limits.scopeLength,\n )\n\n if (scopes === undefined) throw new ClientMetadataError(\"scope_unusable\")\n\n if (!isStringArray(document.redirect_uris)) {\n throw new ClientMetadataError(\"redirect_uris_absent\")\n }\n\n const redirectUris = document.redirect_uris\n .filter((uri) => isAcceptableRedirectUri(uri, options.limits))\n .slice(0, options.limits.redirectUris)\n\n if (redirectUris.length === 0) {\n throw new ClientMetadataError(\"redirect_uris_unusable\")\n }\n\n const clientName = cleanClientName(\n document.client_name,\n options.limits.clientNameLength,\n )\n\n return {\n clientId: url,\n redirectUris,\n kind: \"metadata_document\",\n createdAt: now,\n grantTypes,\n ...(scopes !== null ? { scopes } : {}),\n ...(clientName !== undefined ? { clientName } : {}),\n }\n}\n\n/**\n * The bundled fetcher: `@gydr-ai/safe-fetch`, redirects refused.\n *\n * @param options - Size limits supplying the download deadline and the largest accepted body.\n * @returns A fetcher with the configured deadline and size cap.\n */\nfunction bundledFetcher(\n options: Pick<ResolvedOptions, \"limits\">,\n): ClientMetadataFetcher {\n return (url, init) =>\n safeFetch(\n url,\n {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n redirect: \"error\",\n signal: init.signal,\n },\n {\n timeoutMs: options.limits.clientMetadataTimeoutMs,\n maxResponseBytes: options.limits.clientMetadataBytes,\n label: \"client metadata\",\n },\n )\n}\n\n/**\n * Resolve `work`, or reject as soon as `signal` aborts.\n *\n * @param work - Pending read or fetch to race against the deadline.\n * @param signal - Deadline signal created from `limits.clientMetadataTimeoutMs`.\n * @returns Whatever `work` resolves to.\n */\nfunction beforeAbort<T>(work: Promise<T>, signal: AbortSignal): Promise<T> {\n if (signal.aborted) return Promise.reject(new ClientMetadataError(\"timeout\"))\n\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => {\n reject(new ClientMetadataError(\"timeout\"))\n }\n\n signal.addEventListener(\"abort\", onAbort, { once: true })\n work.then(\n (value) => {\n signal.removeEventListener(\"abort\", onAbort)\n resolve(value)\n },\n (error: unknown) => {\n signal.removeEventListener(\"abort\", onAbort)\n reject(\n error instanceof Error\n ? error\n : new ClientMetadataError(failureReason(error)),\n )\n },\n )\n })\n}\n\n/**\n * Read at most `limit` bytes of a body as UTF-8, before the deadline.\n *\n * @param response - Response from the metadata fetcher with status 200.\n * @param limit - Largest body accepted, in bytes (`limits.clientMetadataBytes`); checked against `Content-Length` and while streaming.\n * @param signal - Deadline signal; reading stops with `timeout` when it fires.\n * @returns The body decoded as UTF-8.\n * @throws {ClientMetadataError} When the body is too large or the deadline passes.\n */\nasync function readLimited(\n response: Response,\n limit: number,\n signal: AbortSignal,\n): Promise<string> {\n const declared = Number(response.headers.get(\"content-length\"))\n\n if (Number.isFinite(declared) && declared > limit) {\n await response.body?.cancel().catch(() => {})\n throw new ClientMetadataError(\"too_large\")\n }\n\n if (response.body === null) return \"\"\n\n const reader = response.body.getReader()\n const parts: Uint8Array[] = []\n let size = 0\n\n try {\n for (;;) {\n const chunk = await beforeAbort(reader.read(), signal)\n\n if (chunk.done) break\n\n const bytes: unknown = chunk.value\n\n if (!(bytes instanceof Uint8Array)) {\n throw new ClientMetadataError(\"not_bytes\")\n }\n\n size += bytes.byteLength\n\n if (size > limit) throw new ClientMetadataError(\"too_large\")\n\n parts.push(bytes)\n }\n } catch (error) {\n await reader.cancel().catch(() => {})\n throw error\n }\n\n return Buffer.concat(parts).toString(\"utf8\")\n}\n\n/**\n * A short reason for a failed download, for events.\n *\n * @param error - What was thrown.\n * @returns The reason.\n * @internal\n */\nexport function failureReason(error: unknown): string {\n if (error instanceof ClientMetadataError) return error.reason\n\n if (!isRecord(error)) return \"unreachable\"\n\n if (typeof error.code === \"string\") return error.code\n\n return typeof error.reason === \"string\" ? error.reason : \"unreachable\"\n}\n\n/** A downloaded metadata document and how long it may be cached. */\nexport interface FetchedClient {\n /** The validated client. */\n readonly client: ClientRecord\n /**\n * The document's own `Cache-Control` lifetime in seconds: `max-age`, or\n * 0 for `no-store` / `no-cache`; `undefined` when it states none.\n */\n readonly maxAgeSeconds: number | undefined\n}\n\n/**\n * The cache lifetime a `Cache-Control` header states.\n *\n * @param header - The header value, if any.\n * @returns Seconds (0 for `no-store` / `no-cache`), or `undefined` when the\n * header states no lifetime.\n * @internal\n */\nexport function cacheControlSeconds(header: string | null): number | undefined {\n if (header === null) return undefined\n\n let seconds: number | undefined\n\n for (const part of header.split(\",\")) {\n const [rawName = \"\", rawValue = \"\"] = part.split(\"=\", 2)\n const name = rawName.trim().toLowerCase()\n\n switch (name) {\n case \"no-store\":\n case \"no-cache\":\n return 0\n case \"max-age\": {\n const value = rawValue.trim().replace(/^\"(.*)\"$/, \"$1\")\n\n seconds = /^\\d{1,10}$/.test(value) ? Number(value) : 0\n break\n }\n default:\n break\n }\n }\n\n return seconds\n}\n\n/**\n * Download and validate one metadata document, without any caching. The\n * deadline, size cap, status check and document checks always apply, also\n * with a custom fetcher.\n *\n * @param url - A `client_id` already confirmed to be a metadata-document URL.\n * @param now - Time of the download, in milliseconds since the epoch.\n * @param options - Supported scopes, size limits, and the optional custom fetcher.\n * @returns The `metadata_document` client record and its stated cache lifetime.\n * @throws {ClientMetadataError} When the download fails, times out, is not status 200, is too large, is not JSON, or fails validation.\n * @internal\n */\nexport async function fetchClientMetadata(\n url: string,\n now: number,\n options: Pick<ResolvedOptions, \"scopes\" | \"limits\" | \"fetchClientMetadata\">,\n): Promise<FetchedClient> {\n const fetcher = options.fetchClientMetadata ?? bundledFetcher(options)\n const signal = AbortSignal.timeout(options.limits.clientMetadataTimeoutMs)\n let text: string\n let maxAgeSeconds: number | undefined\n\n try {\n const response = await beforeAbort(fetcher(url, { signal }), signal)\n\n if (response.status !== 200) {\n await response.body?.cancel().catch(() => {})\n throw new ClientMetadataError(`status_${response.status}`)\n }\n\n maxAgeSeconds = cacheControlSeconds(response.headers.get(\"cache-control\"))\n text = await readLimited(\n response,\n options.limits.clientMetadataBytes,\n signal,\n )\n } catch (error) {\n throw error instanceof ClientMetadataError\n ? error\n : new ClientMetadataError(failureReason(error))\n }\n\n let document: unknown\n\n try {\n document = JSON.parse(text)\n } catch {\n throw new ClientMetadataError(\"invalid_json\")\n }\n\n return {\n client: validateClientMetadata(url, document, now, options),\n maxAgeSeconds,\n }\n}\n", "/**\n * A Client ID Metadata Document was not accepted. Carries no document\n * content.\n *\n * @internal\n */\nexport class ClientMetadataError extends Error {\n /** Short machine-readable reason, reported in events. */\n readonly reason: string\n\n /**\n * @param reason - Short machine-readable reason.\n */\n constructor(reason: string) {\n super(`Client ID Metadata Document not accepted (${reason})`)\n this.name = \"ClientMetadataError\"\n this.reason = reason\n }\n}\n", "import { trimTrailing } from \"../helpers/text.js\"\nimport { CLIENT_ID_LENGTH_LIMIT } from \"../constants/defaults.js\"\nimport { isMetadataDocumentClientId } from \"../helpers/client-fields.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { ClientRecord } from \"../interfaces/records.js\"\nimport {\n failureReason,\n fetchClientMetadata,\n type FetchedClient,\n} from \"./client-metadata.js\"\nimport { permits } from \"./policy.js\"\n\n/**\n * The host of a URL, for events.\n *\n * @param url - Any URL.\n * @returns Its host, or an empty string.\n */\nfunction hostOf(url: string): string {\n try {\n return new URL(url).host\n } catch {\n return \"\"\n }\n}\n\n/**\n * A URL reduced to what identifies a server: scheme, host without a\n * trailing dot, port (the parser drops a default one) and path without\n * trailing slashes. Query and fragment are ignored.\n *\n * @param value - An absolute URL.\n * @returns The reduced form, or `undefined` when it does not parse.\n */\nfunction serverIdentity(value: string): string | undefined {\n let url: URL\n\n try {\n url = new URL(value)\n } catch {\n return undefined\n }\n\n const host = trimTrailing(url.hostname, \".\")\n const path = trimTrailing(url.pathname, \"/\")\n\n return `${url.protocol}//${host}:${url.port}${path}`\n}\n\n/**\n * Whether a URL names the issuer itself, compared after parsing.\n *\n * @param url - A metadata-document `client_id`.\n * @param issuer - The configured issuer.\n * @returns `true` when both identify the same server URL.\n * @internal\n */\nexport function isIssuerUrl(url: string, issuer: string): boolean {\n const given = serverIdentity(url)\n\n return given !== undefined && given === serverIdentity(issuer)\n}\n\n/**\n * Find the client behind a `client_id`. Opaque ids are looked up among the\n * pre-registered clients, then in the store; a URL id is checked against\n * `allowClientMetadataUrl`, then served from the metadata cache or\n * downloaded.\n *\n * @param context - Context supplying the store, pre-registered clients, metadata policy hook and the per-instance download state.\n * @param clientId - The `client_id` as sent.\n * @returns The client, or `undefined` when it is unknown or unusable.\n * @internal\n */\nexport async function resolveClient(\n context: HandlerContext,\n clientId: string,\n): Promise<ClientRecord | undefined> {\n const { options } = context\n\n if (clientId.length > CLIENT_ID_LENGTH_LIMIT) return undefined\n\n if (!isMetadataDocumentClientId(clientId)) {\n const listed = options.staticClients.get(clientId)\n\n if (listed !== undefined) return listed\n\n const stored = await options.store.getClient(clientId)\n\n // Only a real download may produce a metadata-document client.\n return stored?.kind === \"metadata_document\" ? undefined : stored\n }\n\n if (!options.clientIdMetadataDocuments) return undefined\n\n // The authorization server's own URL can never be a client of it\n // (CIMD-03 security considerations).\n if (isIssuerUrl(clientId, options.issuer)) {\n await context.emit({\n type: \"client_metadata_refused\",\n host: hostOf(clientId),\n reason: \"client_id_is_issuer\",\n })\n\n return undefined\n }\n\n const allow = options.allowClientMetadataUrl\n\n if (allow !== undefined && !(await permits(() => allow(clientId)))) {\n await context.emit({\n type: \"client_metadata_refused\",\n host: hostOf(clientId),\n reason: \"not_allowed_by_policy\",\n })\n\n return undefined\n }\n\n const cached = await options.store.getCachedClientMetadata(clientId)\n\n if (cached?.clientId === clientId) return cached\n\n const { failures, inflight } = context.cimd\n const retryAfter = failures.get(clientId)\n\n // A URL that recently failed is not contacted again until its back-off\n // ends: repeated authorization attempts cannot multiply outbound requests.\n if (retryAfter !== undefined) {\n if (retryAfter > options.now()) return undefined\n\n failures.delete(clientId)\n }\n\n // Simultaneous requests for the same URL wait on one download.\n const pending = inflight.get(clientId)\n\n if (pending !== undefined) return pending\n\n const download = downloadAndRemember(context, clientId).finally(() => {\n inflight.delete(clientId)\n })\n\n inflight.set(clientId, download)\n\n return download\n}\n\n/**\n * Download a metadata document, cache it on success and remember a failure.\n *\n * @param context - Context supplying the clock, cache lifetime, fetcher and event hook.\n * @param clientId - The document URL.\n * @returns The client, or `undefined` on failure.\n */\nasync function downloadAndRemember(\n context: HandlerContext,\n clientId: string,\n): Promise<ClientRecord | undefined> {\n const { options } = context\n const startedAt = options.now()\n let fetched: FetchedClient\n\n try {\n fetched = await fetchClientMetadata(clientId, startedAt, options)\n } catch (error) {\n rememberFailure(context, clientId)\n await context.emit({\n type: \"client_metadata_refused\",\n host: hostOf(clientId),\n reason: failureReason(error),\n })\n\n return undefined\n }\n\n // The document's own `Cache-Control` is honoured, never beyond the\n // configured lifetime (CIMD-03 \u00A7 4.2).\n const stated = fetched.maxAgeSeconds\n const cacheMs =\n stated === undefined\n ? options.clientMetadataCacheMs\n : Math.min(stated * 1000, options.clientMetadataCacheMs)\n\n if (cacheMs > 0) {\n await options.store.putCachedClientMetadata(\n clientId,\n fetched.client,\n startedAt + cacheMs,\n )\n }\n\n return fetched.client\n}\n\n/**\n * Remember that a URL failed, evicting the oldest entry when the list is full.\n *\n * @param context - Context holding the per-instance failure map and its configured size and back-off.\n * @param clientId - The document URL.\n */\nfunction rememberFailure(context: HandlerContext, clientId: string): void {\n const { options } = context\n const { failures } = context.cimd\n\n if (options.clientMetadataFailureMs === 0) return\n\n failures.delete(clientId)\n\n while (failures.size >= options.limits.clientMetadataFailures) {\n const oldest = failures.keys().next()\n\n if (oldest.done === true) break\n\n failures.delete(oldest.value)\n }\n\n failures.set(clientId, options.now() + options.clientMetadataFailureMs)\n}\n", "import { sha256Hex } from \"./tokens.js\"\n\n/**\n * Read one cookie from the `Cookie` header; the first occurrence wins.\n *\n * @param request - The request.\n * @param name - Cookie name.\n * @returns The cookie value without surrounding spaces, or `undefined` when absent.\n * @internal\n */\nexport function readCookie(request: Request, name: string): string | undefined {\n const header = request.headers.get(\"cookie\")\n\n if (header === null) return undefined\n\n for (const part of header.split(\";\")) {\n const index = part.indexOf(\"=\")\n\n if (index !== -1 && part.slice(0, index).trim() === name) {\n return part.slice(index + 1).trim()\n }\n }\n\n return undefined\n}\n\n/**\n * A `Set-Cookie` value for a `__Host-` cookie: `Secure`, `HttpOnly`,\n * `SameSite=Lax`, `Path=/` and no `Domain`, so it is bound to this exact host.\n *\n * @param name - Cookie name.\n * @param value - Cookie value.\n * @param maxAgeSeconds - Lifetime.\n * @returns The header value.\n * @internal\n */\nexport function hostCookie(\n name: string,\n value: string,\n maxAgeSeconds: number,\n): string {\n return `${name}=${value}; Path=/; Max-Age=${maxAgeSeconds}; HttpOnly; Secure; SameSite=Lax`\n}\n\n/**\n * A `Set-Cookie` value that deletes a `__Host-` cookie.\n *\n * @param name - Cookie name.\n * @returns The header value.\n * @internal\n */\nexport function clearHostCookie(name: string): string {\n return `${name}=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Lax`\n}\n\n/**\n * Cookie name for one flow: the configured prefix plus a tag derived from\n * the flow id. Separate names let a browser run several authorizations side\n * by side.\n *\n * @param prefix - Configured cookie name prefix.\n * @param flowId - The flow or consent id.\n * @returns The cookie name.\n * @internal\n */\nexport function bindingCookieName(prefix: string, flowId: string): string {\n return prefix + sha256Hex(`binding:${flowId}`).slice(0, 20)\n}\n", "/**\n * Parameters grouped by name, each with all its values in order.\n *\n * @internal\n */\nexport type Params = ReadonlyMap<string, readonly string[]>\n\n/**\n * Group URL-encoded parameters by name, keeping repeated values.\n *\n * @param search - Parsed query or form body.\n * @returns The grouped parameters.\n * @internal\n */\nexport function groupParams(search: URLSearchParams): Params {\n const params = new Map<string, string[]>()\n\n for (const [key, value] of search) {\n const values = params.get(key)\n\n if (values) values.push(value)\n else params.set(key, [value])\n }\n\n return params\n}\n\n/**\n * Names that appear more than once, which RFC 6749 \u00A7 3.1 and \u00A7 3.2 forbid.\n *\n * @param params - Grouped parameters.\n * @returns The repeated names.\n * @internal\n */\nexport function duplicatedNames(params: Params): string[] {\n return [...params].filter(([, values]) => values.length > 1).map(([k]) => k)\n}\n\n/**\n * The value of a parameter; an empty value counts as absent.\n *\n * @param params - Grouped parameters.\n * @param name - Parameter name.\n * @returns The first value, or `undefined`.\n * @internal\n */\nexport function single(params: Params, name: string): string | undefined {\n const value = params.get(name)?.[0]\n\n return value === undefined || value === \"\" ? undefined : value\n}\n\n/**\n * Which of the parameters a protocol defines appear more than once. RFC 6749\n * \u00A7 3.1 and \u00A7 3.2 (OAuth 2.1 \u00A7 3.1) forbid repeating a defined parameter but\n * require unknown parameters to be ignored, even when repeated.\n *\n * @param params - Grouped query or form parameters, unvalidated.\n * @param defined - Names the endpoint defines.\n * @returns The defined names that were sent more than once.\n * @internal\n */\nexport function repeatedDefined(\n params: Params,\n defined: readonly string[],\n): string[] {\n return defined.filter((name) => (params.get(name)?.length ?? 0) > 1)\n}\n", "/**\n * Split an absolute URL into its lower-cased `scheme://authority` and the\n * untouched remainder (path, query).\n *\n * @param value - An absolute URL as written.\n * @returns The two parts, or `undefined` when there is no `://`.\n */\nfunction splitAuthority(\n value: string,\n): { authority: string; rest: string } | undefined {\n const start = value.indexOf(\"://\")\n\n if (start === -1) return undefined\n\n const pathStart = value.indexOf(\"/\", start + 3)\n const end = pathStart === -1 ? value.length : pathStart\n\n return {\n authority: value.slice(0, end).toLowerCase(),\n rest: value.slice(end),\n }\n}\n\n/**\n * Whether a `resource` parameter names the configured resource. Scheme and\n * host are compared case-insensitively (RFC 3986 \u00A7 6.2.2.1); the path and\n * query must match exactly.\n *\n * @param configured - The configured `resource` option.\n * @param given - The raw `resource` parameter, unvalidated.\n * @returns `true` when both name the same resource.\n * @internal\n */\nexport function sameResource(configured: string, given: string): boolean {\n if (given === configured) return true\n\n const a = splitAuthority(configured)\n const b = splitAuthority(given)\n\n return (\n a !== undefined &&\n b !== undefined &&\n a.authority === b.authority &&\n a.rest === b.rest\n )\n}\n", "import { isScopeToken } from \"./text.js\"\n\n/**\n * The scopes a `scope` parameter asks for, each listed once in the order\n * given. An absent parameter yields `fallback`. Returns `undefined` when the\n * value is longer than `maxLength`, empty, or names a scope outside\n * `permitted`.\n *\n * @param requested - The raw `scope` query value; `undefined` when absent.\n * @param permitted - Scopes this client may receive: its registered scopes, or all supported scopes.\n * @param fallback - Scopes granted when `requested` is absent; a subset of `permitted`.\n * @param maxLength - Longest `scope` value accepted, in characters (`limits.scopeLength`).\n * @returns The distinct requested scopes in request order (or `fallback` when none were requested), or `undefined` when the value is too long, empty, or names a scope outside `permitted`.\n * @internal\n */\nexport function resolveScopes(\n requested: string | undefined,\n permitted: readonly string[],\n fallback: readonly string[],\n maxLength: number,\n): readonly string[] | undefined {\n if (requested === undefined) return fallback\n\n if (requested.length > maxLength) return undefined\n\n const asked = [...new Set(requested.split(\" \").filter((s) => s.length > 0))]\n\n if (asked.length === 0) return undefined\n\n const allKnown = asked.every((s) => isScopeToken(s) && permitted.includes(s))\n\n return allKnown ? asked : undefined\n}\n", "import type { ErrorPageReason } from \"../constants/error-pages.js\"\nimport { resolveClient } from \"../core/clients.js\"\nimport { decide } from \"../core/policy.js\"\nimport { bindingCookieName, hostCookie } from \"../helpers/cookies.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\nimport { groupParams, repeatedDefined, single } from \"../helpers/params.js\"\nimport { matchesAnyRedirectUri } from \"../helpers/redirect-uris.js\"\nimport { sameResource } from \"../helpers/resource.js\"\nimport { redirect } from \"../helpers/responses.js\"\nimport { resolveScopes } from \"../helpers/scopes.js\"\nimport {\n isWellFormedChallenge,\n pkceChallenge,\n randomToken,\n sha256Hex,\n} from \"../helpers/tokens.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { PendingAuthorization } from \"../interfaces/records.js\"\n\n/** Parameters this endpoint defines; only these may not be repeated. */\nconst AUTHORIZE_PARAMETERS = [\n \"client_id\",\n \"redirect_uri\",\n \"response_type\",\n \"code_challenge\",\n \"code_challenge_method\",\n \"resource\",\n \"scope\",\n \"state\",\n \"prompt\",\n] as const\n\n/**\n * `GET /authorize`: validate the request and send the browser to the\n * identity provider.\n *\n * Every problem found here is shown as a page in the browser, never sent to\n * the client's redirect URI: the person has not signed in yet, and\n * redirecting an unauthenticated request with an error would make this\n * endpoint an open redirector for any registered (or self-registered) client\n * (RFC 9700 \u00A7 4.11.2, OAuth 2.1 \u00A7 7.13.2). Errors are returned to the client\n * only after sign-in, from the callback and consent steps.\n *\n * On success the request is saved under a fresh flow id, a cookie ties the\n * flow to this browser, and the identity provider receives the flow id as\n * `state` together with this server's own PKCE challenge and nonce.\n *\n * @param context - Context supplying clients, policy hooks, the identity provider, store and cookie prefix.\n * @param request - The `GET /authorize` request opened in the person's browser; its query is unvalidated.\n * @returns A redirect to the identity provider, or an error page.\n * @internal\n */\nexport async function handleAuthorize(\n context: HandlerContext,\n request: Request,\n): Promise<Response> {\n const { options } = context\n const { limits } = options\n\n if (!(await context.allowed(request, \"authorize\"))) {\n return context.errorPage(request, \"rate_limited\")\n }\n\n const params = groupParams(new URL(request.url).searchParams)\n const refuse = async (\n reason: ErrorPageReason,\n clientId?: string,\n extra: { detail?: string; policyReason?: string } = {},\n ) => {\n await context.emit({\n type: \"authorization_refused\",\n reason,\n ...(clientId !== undefined ? { clientId } : {}),\n })\n\n return context.errorPage(request, reason, extra)\n }\n\n // A defined parameter may appear once (OAuth 2.1 \u00A7 3.1). Two different\n // `redirect_uri` or `client_id` values could be read differently by\n // different parsers. Unknown parameters are ignored, even when repeated.\n if (repeatedDefined(params, AUTHORIZE_PARAMETERS).length > 0) {\n return refuse(\"repeated_parameter\")\n }\n\n const clientId = single(params, \"client_id\")\n const client =\n clientId === undefined ? undefined : await resolveClient(context, clientId)\n\n if (clientId === undefined || client === undefined) {\n return refuse(\"client_unknown\")\n }\n\n // OAuth 2.1 \u00A7 4.1.1: `redirect_uri` may be left out when the client has\n // exactly one registered; that one is then used.\n const sent = single(params, \"redirect_uri\")\n const [onlyRegistered] =\n client.redirectUris.length === 1 ? client.redirectUris : []\n const redirectUri = sent ?? onlyRegistered\n\n if (\n redirectUri === undefined ||\n !matchesAnyRedirectUri(client.redirectUris, redirectUri, limits)\n ) {\n return refuse(\"redirect_uri_not_registered\", clientId)\n }\n\n const state = single(params, \"state\")\n\n if (state !== undefined && state.length > limits.stateLength) {\n return refuse(\"invalid_request\", clientId, {\n detail: `The state value exceeds ${limits.stateLength} characters`,\n })\n }\n\n if (single(params, \"response_type\") !== \"code\") {\n return refuse(\"unsupported_response_type\", clientId, {\n detail: \"Only the authorization code flow is offered\",\n })\n }\n\n const codeChallenge = single(params, \"code_challenge\")\n\n if (codeChallenge === undefined || !isWellFormedChallenge(codeChallenge)) {\n return refuse(\"invalid_request\", clientId, {\n detail: \"A 43-character S256 code_challenge must accompany the request\",\n })\n }\n\n if (single(params, \"code_challenge_method\") !== \"S256\") {\n return refuse(\"invalid_request\", clientId, {\n detail: \"The only accepted code_challenge_method is S256\",\n })\n }\n\n const resource = single(params, \"resource\")\n\n if (resource === undefined || !sameResource(options.resource, resource)) {\n return refuse(\"invalid_target\", clientId, {\n detail: `Tokens from this server are only for ${options.resource}`,\n })\n }\n\n // Clients that declared a scope at registration stay inside it.\n const permitted = client.scopes ?? options.scopes\n const fallback = options.defaultScopes.filter((s) => permitted.includes(s))\n const scopes = resolveScopes(\n params.get(\"scope\")?.[0],\n permitted,\n fallback.length > 0 ? fallback : permitted,\n limits.scopeLength,\n )\n\n if (scopes === undefined) {\n return refuse(\"invalid_scope\", clientId)\n }\n\n // The person is always asked, so a request that forbids any interaction\n // (`prompt=none`) can never succeed.\n if (single(params, \"prompt\")?.split(\" \").includes(\"none\")) {\n return refuse(\"consent_required\", clientId)\n }\n\n // The operator's own policy runs last and can only refuse.\n const policy = options.authorizeClient\n\n if (policy !== undefined) {\n const outcome = await decide(() =>\n policy({\n client: frozenCopy(client),\n redirectUri,\n scopes: frozenCopy(scopes),\n request,\n }),\n )\n\n if (!outcome.allowed) {\n return refuse(\n \"access_denied\",\n clientId,\n outcome.reason === undefined ? {} : { policyReason: outcome.reason },\n )\n }\n }\n\n const flowId = randomToken()\n const binding = randomToken()\n const upstreamVerifier = randomToken()\n const nonce = randomToken()\n const pending: PendingAuthorization = {\n id: flowId,\n clientId: client.clientId,\n clientKind: client.kind,\n redirectUri,\n codeChallenge,\n // Always the configured spelling, whatever case the client used.\n resource: options.resource,\n scopes,\n upstreamCodeVerifier: upstreamVerifier,\n upstreamNonce: nonce,\n bindingHash: sha256Hex(binding),\n allowRefresh: client.grantTypes?.includes(\"refresh_token\") ?? true,\n expiresAt: options.now() + options.pendingTtlMs,\n ...(client.clientName !== undefined\n ? { clientName: client.clientName }\n : {}),\n ...(state !== undefined ? { state } : {}),\n }\n\n await options.store.putPendingAuthorization(pending)\n\n const location = await options.identityProvider.authorizationUrl({\n state: flowId,\n codeChallenge: pkceChallenge(upstreamVerifier),\n redirectUri: options.callbackUrl,\n nonce,\n })\n\n await context.emit({\n type: \"authorization_started\",\n clientId: client.clientId,\n scopes,\n })\n\n return redirect(location, [\n hostCookie(\n bindingCookieName(options.cookiePrefix, flowId),\n binding,\n Math.ceil(options.pendingTtlMs / 1000),\n ),\n ])\n}\n", "import { randomUUID } from \"node:crypto\"\n\nimport { UPSTREAM_LIFETIME_CEILING_SECONDS } from \"../constants/defaults.js\"\nimport { consentPageResponse } from \"../core/consent-page.js\"\nimport { revokeUpstreamToken } from \"../core/grants.js\"\nimport { resolvePerson } from \"../core/subject.js\"\nimport {\n bindingCookieName,\n clearHostCookie,\n readCookie,\n} from \"../helpers/cookies.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\nimport { jsonCopy } from \"../helpers/json.js\"\nimport { groupParams, repeatedDefined, single } from \"../helpers/params.js\"\nimport { buildRedirect } from \"../helpers/redirect-uris.js\"\nimport { redirect } from \"../helpers/responses.js\"\nimport { hasExpired } from \"../helpers/time.js\"\nimport { constantTimeEqual, randomToken, sha256Hex } from \"../helpers/tokens.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { UpstreamLogin } from \"../interfaces/identity-provider.js\"\nimport type { ConsentRequest } from \"../interfaces/records.js\"\n\n/** Parameters an authorization response defines (RFC 6749 \u00A7 4.1.2, RFC 9207). */\nconst CALLBACK_PARAMETERS = [\n \"state\",\n \"code\",\n \"error\",\n \"error_description\",\n \"error_uri\",\n \"iss\",\n] as const\n\n/**\n * `GET /callback`: the identity provider returns the browser here.\n *\n * 1. The browser must carry the cookie set when the flow began; without it\n * the request is refused and the flow is left untouched, so a person who\n * only saw the `state` value cannot cancel someone else's sign-in.\n * 2. The flow is then taken from the store, once; a second callback finds\n * nothing.\n * 3. The provider's code is exchanged, the upstream tokens are sealed for\n * the grant id the person may approve, and the consent page is shown with\n * a one-time form token.\n *\n * @param context - Context supplying the store, identity provider, sealer, hooks and pages.\n * @param request - The browser's return from the identity provider, with `state`, `code` or `error` in the query and the binding cookie.\n * @returns The consent page, a redirect to the client, or an error page.\n * @internal\n */\nexport async function handleCallback(\n context: HandlerContext,\n request: Request,\n): Promise<Response> {\n const { options } = context\n\n if (!(await context.allowed(request, \"callback\"))) {\n return context.errorPage(request, \"rate_limited\")\n }\n\n const params = groupParams(new URL(request.url).searchParams)\n\n if (repeatedDefined(params, CALLBACK_PARAMETERS).length > 0) {\n return context.errorPage(request, \"malformed_response\")\n }\n\n const flowId = single(params, \"state\")\n\n if (flowId === undefined) return context.errorPage(request, \"flow_ended\")\n\n const cookieName = bindingCookieName(options.cookiePrefix, flowId)\n const dropCookie = clearHostCookie(cookieName)\n const observed = await options.store.getPendingAuthorization(flowId)\n\n if (observed === undefined || hasExpired(observed.expiresAt, options.now())) {\n return context.errorPage(request, \"flow_ended\")\n }\n\n const binding = readCookie(request, cookieName)\n\n if (\n binding === undefined ||\n !constantTimeEqual(sha256Hex(binding), observed.bindingHash)\n ) {\n await context.emit({ type: \"flow_binding_mismatch\", step: \"callback\" })\n\n return context.errorPage(request, \"browser_mismatch\", {\n setCookies: [dropCookie],\n })\n }\n\n const pending = await options.store.consumePendingAuthorization(flowId)\n\n if (\n pending === undefined ||\n hasExpired(pending.expiresAt, options.now()) ||\n pending.bindingHash !== observed.bindingHash\n ) {\n return context.errorPage(request, \"flow_ended\")\n }\n\n const providerError = single(params, \"error\")\n\n // The person went to the identity provider from this browser (the\n // binding cookie proves it) and came back; telling the client is not an\n // automatic redirect of an unauthenticated request.\n if (providerError !== undefined) {\n const cancelled = providerError === \"access_denied\"\n\n await context.emit({\n type: \"identity_provider_error\",\n clientId: pending.clientId,\n reason: cancelled ? \"person_cancelled\" : \"provider_reported_error\",\n })\n\n return redirect(\n buildRedirect(pending.redirectUri, {\n error: cancelled ? \"access_denied\" : \"server_error\",\n error_description: cancelled\n ? \"The person stopped at the sign-in step\"\n : \"The identity provider could not complete the sign-in\",\n state: pending.state,\n iss: options.issuer,\n }),\n [dropCookie],\n )\n }\n\n const code = single(params, \"code\")\n\n if (code === undefined) {\n return context.errorPage(request, \"malformed_response\", {\n setCookies: [dropCookie],\n })\n }\n\n const iss = single(params, \"iss\")\n let login: UpstreamLogin\n\n try {\n login = await options.identityProvider.exchangeCode({\n code,\n codeVerifier: pending.upstreamCodeVerifier,\n redirectUri: options.callbackUrl,\n nonce: pending.upstreamNonce,\n ...(iss !== undefined ? { issuer: iss } : {}),\n })\n } catch (error) {\n await context.emit({\n type: \"identity_provider_error\",\n clientId: pending.clientId,\n reason: error instanceof Error ? error.name : \"exchange_failed\",\n })\n\n return context.errorPage(request, \"sign_in_not_confirmed\", {\n setCookies: [dropCookie],\n })\n }\n\n const releaseUpstream = async () => {\n if (login.refreshToken !== undefined) {\n await revokeUpstreamToken(context, login.refreshToken)\n }\n }\n\n if (typeof login.subject !== \"string\" || login.subject.length === 0) {\n await releaseUpstream()\n await context.emit({\n type: \"identity_provider_error\",\n clientId: pending.clientId,\n reason: \"subject_missing\",\n })\n\n return context.errorPage(request, \"sign_in_not_confirmed\", {\n setCookies: [dropCookie],\n })\n }\n\n const client = {\n clientId: pending.clientId,\n kind: pending.clientKind,\n ...(pending.clientName !== undefined\n ? { clientName: pending.clientName }\n : {}),\n }\n const resolved = await resolvePerson(context, login, client)\n\n if (!resolved.ok) {\n await releaseUpstream()\n await context.emit({\n type: \"identity_provider_error\",\n clientId: pending.clientId,\n reason: \"subject_not_permitted\",\n })\n\n return context.errorPage(request, \"subject_not_permitted\", {\n setCookies: [dropCookie],\n ...(resolved.policyReason !== undefined\n ? { policyReason: resolved.policyReason }\n : {}),\n })\n }\n\n const { person } = resolved\n let details: unknown\n\n if (options.getConsentDetails !== undefined) {\n try {\n details = jsonCopy(\n await options.getConsentDetails(\n Object.freeze({\n ...frozenCopy({\n subject: person.subject,\n client,\n scopes: [...pending.scopes],\n }),\n request,\n }),\n ),\n options.limits.consentDetailsBytes,\n \"getConsentDetails\",\n )\n } catch {\n await releaseUpstream()\n\n return context.errorPage(request, \"server_error\", {\n setCookies: [dropCookie],\n })\n }\n }\n\n const now = options.now()\n const grantId = randomUUID()\n const formToken = randomToken()\n const { sealer } = options\n const lifetime =\n login.expiresIn !== undefined && Number.isFinite(login.expiresIn)\n ? Math.min(\n Math.max(login.expiresIn, 0),\n UPSTREAM_LIFETIME_CEILING_SECONDS,\n )\n : undefined\n const consent: ConsentRequest = {\n id: randomToken(),\n grantId,\n clientId: pending.clientId,\n clientKind: pending.clientKind,\n redirectUri: pending.redirectUri,\n codeChallenge: pending.codeChallenge,\n resource: pending.resource,\n scopes: pending.scopes,\n subject: person.subject,\n csrfHash: sha256Hex(formToken),\n bindingHash: pending.bindingHash,\n expiresAt: now + options.pendingTtlMs,\n ...(pending.allowRefresh !== undefined\n ? { allowRefresh: pending.allowRefresh }\n : {}),\n ...(pending.clientName !== undefined\n ? { clientName: pending.clientName }\n : {}),\n ...(pending.state !== undefined ? { state: pending.state } : {}),\n ...(person.displayName !== undefined\n ? { displayName: person.displayName }\n : {}),\n ...(details !== undefined ? { details } : {}),\n ...(login.refreshToken !== undefined\n ? {\n sealedUpstreamRefreshToken: await sealer.seal(\n login.refreshToken,\n grantId,\n ),\n }\n : {}),\n ...(login.accessToken !== undefined && lifetime !== undefined\n ? {\n sealedUpstreamAccessToken: await sealer.seal(\n login.accessToken,\n `${grantId}#access`,\n ),\n upstreamAccessTokenExpiresAt: now + lifetime * 1000,\n }\n : {}),\n }\n let response: Response\n\n // Render before storing: if a custom renderer throws, nothing holding the\n // sealed upstream token is left behind, and the token itself is revoked.\n try {\n response = consentPageResponse(context, request, consent, formToken, {\n binding,\n setCookies: [dropCookie],\n })\n } catch (error) {\n await releaseUpstream()\n throw error\n }\n\n await options.store.putConsentRequest(consent)\n\n return response\n}\n", "import { CONSENT_FIELDS } from \"../constants/protocol.js\"\nimport { bindingCookieName, hostCookie } from \"../helpers/cookies.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\nimport { parseLocales } from \"../helpers/locales.js\"\nimport {\n isLoopbackRedirectUri,\n originOf,\n redirectHostOf,\n} from \"../helpers/redirect-uris.js\"\nimport { html } from \"../helpers/responses.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { ConsentRequest } from \"../interfaces/records.js\"\n\n/**\n * Host of a metadata-document URL, shown so people see who vouches for it.\n *\n * @param url - A metadata-document `client_id` (an `https` URL).\n * @returns The host, or an empty string.\n */\nfunction documentHost(url: string): string {\n try {\n return new URL(url).host\n } catch {\n return \"\"\n }\n}\n\n/** Optional parts of a consent page response. */\ninterface ConsentPageExtras {\n /** HTTP status. Default: 200. */\n readonly status?: number\n /** Problems `readConsentFields` reported, by field name. */\n readonly fieldErrors?: Readonly<Record<string, string>>\n /** Extra field values to fill the form with again. */\n readonly submitted?: Readonly<Record<string, string>>\n /** The plaintext binding cookie value, to set the consent-step cookie. */\n readonly binding?: string\n /** Other `Set-Cookie` values, such as clearing the sign-in cookie. */\n readonly setCookies?: readonly string[]\n}\n\n/**\n * Render the consent page for a stored (or about to be stored) consent\n * request and wrap it in a response with the page security headers.\n *\n * The renderer receives a deeply frozen copy of its context. The form may\n * submit (and be redirected) only to this server and the client's redirect\n * origin; inline scripts run only when listed in `scriptHashes`.\n *\n * @param context - Context supplying the renderer, paths, cookie prefix, lifetimes and script hashes.\n * @param request - The request that leads to this page; its `Accept-Language` sets `locales`.\n * @param consent - The consent request the page is for.\n * @param formToken - The plaintext one-time form token, embedded as a hidden field.\n * @param extras - Status, field errors, submitted values and cookies.\n * @returns The HTML response.\n * @throws {Error} Whatever a custom `renderConsentPage` throws.\n * @internal\n */\nexport function consentPageResponse(\n context: HandlerContext,\n request: Request,\n consent: ConsentRequest,\n formToken: string,\n extras: ConsentPageExtras = {},\n): Response {\n const { options } = context\n const page = options.renderConsentPage(\n frozenCopy({\n clientId: consent.clientId,\n clientKind: consent.clientKind,\n redirectUri: consent.redirectUri,\n redirectHost: redirectHostOf(consent.redirectUri),\n redirectIsLoopback: isLoopbackRedirectUri(consent.redirectUri),\n scopes: consent.scopes,\n scopeDescriptions: { ...options.scopeDescriptions },\n resource: consent.resource,\n formAction: options.paths.consent,\n hiddenFields: {\n [CONSENT_FIELDS.id]: consent.id,\n [CONSENT_FIELDS.csrf]: formToken,\n },\n decisionField: CONSENT_FIELDS.decision,\n locales: parseLocales(request.headers.get(\"accept-language\")),\n ...(consent.clientName !== undefined\n ? { clientName: consent.clientName }\n : {}),\n ...(consent.clientKind === \"metadata_document\"\n ? { clientHost: documentHost(consent.clientId) }\n : {}),\n ...(consent.displayName !== undefined\n ? { subjectDisplayName: consent.displayName }\n : {}),\n ...(consent.details !== undefined ? { details: consent.details } : {}),\n ...(extras.fieldErrors !== undefined\n ? { fieldErrors: extras.fieldErrors }\n : {}),\n ...(extras.submitted !== undefined\n ? { submitted: extras.submitted }\n : {}),\n }),\n )\n const target = originOf(consent.redirectUri)\n const cookies = [...(extras.setCookies ?? [])]\n\n if (extras.binding !== undefined) {\n cookies.push(\n hostCookie(\n bindingCookieName(options.cookiePrefix, consent.id),\n extras.binding,\n Math.ceil(options.pendingTtlMs / 1000),\n ),\n )\n }\n\n return html(extras.status ?? 200, page, {\n formActionOrigins: target === undefined ? [] : [target],\n setCookies: cookies,\n scriptHashes: options.scriptHashes,\n })\n}\n", "import { cleanClientName } from \"../helpers/client-fields.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\nimport { hasHiddenCharacters } from \"../helpers/text.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { UpstreamLogin } from \"../interfaces/identity-provider.js\"\nimport type { MappedSubject } from \"../interfaces/policy.js\"\nimport type { ClientKind } from \"../interfaces/records.js\"\nimport { decide } from \"./policy.js\"\n\n/** Longest subject accepted, from the identity provider or `mapSubject`. */\nconst SUBJECT_LENGTH_LIMIT = 512\n\n/**\n * Whether a subject is safe to store and compare: a non-empty string of at\n * most 512 characters, without surrounding whitespace, already in Unicode\n * NFC form, and free of invisible or formatting characters. Two subjects\n * that look the same must be the same string.\n *\n * @param subject - A candidate subject, of unknown type.\n * @returns `true` when it may be stored.\n */\nfunction isUsableSubject(subject: unknown): subject is string {\n return (\n typeof subject === \"string\" &&\n subject !== \"\" &&\n subject.length <= SUBJECT_LENGTH_LIMIT &&\n subject.trim() === subject &&\n subject.normalize(\"NFC\") === subject &&\n !hasHiddenCharacters(subject)\n )\n}\n\n/**\n * Read what `mapSubject` returned. Each field is read exactly once, so an\n * object with getters cannot pass validation with one value and be stored\n * with another.\n *\n * @param value - The hook's result, of unknown shape.\n * @returns The subject and the raw display name, or `undefined` when the\n * result is not an object.\n */\nfunction readMapped(\n value: unknown,\n): { subject: unknown; displayName: unknown } | undefined {\n if (typeof value !== \"object\" || value === null) return undefined\n\n const subject: unknown = Reflect.get(value, \"subject\")\n const displayName: unknown = Reflect.get(value, \"displayName\")\n\n return { subject, displayName }\n}\n\n/**\n * Decide who signed in: apply `mapSubject`, check the subject, clean the\n * display name, then ask `allowSubject`. Every step fails closed: an invalid\n * mapping, an unusable subject, a refusal or a thrown error means nobody is\n * signed in. Hooks receive deeply frozen copies.\n *\n * @param context - Context supplying the `mapSubject` and `allowSubject`\n * hooks and `limits.clientNameLength` for display names.\n * @param login - What the identity provider reported, after its own checks.\n * @param client - The connecting client.\n * @param client.clientId - Its id.\n * @param client.clientName - Its display name.\n * @param client.kind - How it made itself known.\n * @returns The person to record, or a refusal with the reason\n * `allowSubject` gave, if any.\n * @internal\n */\nexport async function resolvePerson(\n context: HandlerContext,\n login: UpstreamLogin,\n client: { clientId: string; clientName?: string; kind: ClientKind },\n): Promise<\n | { readonly ok: true; readonly person: MappedSubject }\n | { readonly ok: false; readonly policyReason?: string }\n> {\n const { mapSubject, allowSubject, limits } = context.options\n const claims = login.claims ?? {}\n let subject: unknown = login.subject\n let rawName: unknown = login.displayName\n\n if (mapSubject !== undefined) {\n let mapped: { subject: unknown; displayName: unknown } | undefined\n\n try {\n mapped = readMapped(\n await mapSubject(\n frozenCopy({\n subject: login.subject,\n claims,\n ...(login.displayName !== undefined\n ? { displayName: login.displayName }\n : {}),\n }),\n ),\n )\n } catch {\n return { ok: false }\n }\n\n if (mapped === undefined) return { ok: false }\n\n subject = mapped.subject\n rawName = mapped.displayName\n }\n\n if (!isUsableSubject(subject)) return { ok: false }\n\n // A display name is cosmetic: an unusable one is dropped, not fatal.\n const displayName =\n rawName === undefined\n ? undefined\n : cleanClientName(rawName, limits.clientNameLength)\n const person: MappedSubject =\n displayName === undefined ? { subject } : { subject, displayName }\n\n if (allowSubject !== undefined) {\n const outcome = await decide(() =>\n allowSubject(frozenCopy({ ...person, claims, client })),\n )\n\n if (!outcome.allowed) {\n return outcome.reason === undefined\n ? { ok: false }\n : { ok: false, policyReason: outcome.reason }\n }\n }\n\n return { ok: true, person }\n}\n", "/**\n * A request body went past the size limit while it was being read.\n *\n * @internal\n */\nexport class BodyTooLargeError extends Error {\n /** Creates the error with a fixed message. */\n constructor() {\n super(\"Body exceeds the configured size limit\")\n this.name = \"BodyTooLargeError\"\n }\n}\n", "import { BodyTooLargeError } from \"../errors/body-too-large-error.js\"\n\n/**\n * Whether the request's media type is `expected`, ignoring parameters such\n * as `charset`.\n *\n * @param request - The request.\n * @param expected - Lower-case media type.\n * @returns `true` on a match.\n * @internal\n */\nexport function hasContentType(request: Request, expected: string): boolean {\n const value = request.headers.get(\"content-type\")\n\n if (value === null) return false\n\n const [type] = value.split(\";\")\n\n return (type ?? \"\").trim().toLowerCase() === expected\n}\n\n/**\n * Read a request body as UTF-8, stopping as soon as it passes `maxBytes`, so\n * an oversized body is never held in memory.\n *\n * @param request - The request.\n * @param maxBytes - Largest body accepted, in bytes; checked against `Content-Length` and while streaming.\n * @returns The body decoded as UTF-8; an empty string when there is no body.\n * @throws {BodyTooLargeError} When the body is larger than `maxBytes`.\n * @throws {TypeError} When the body stream yields something other than bytes.\n * @internal\n */\nexport async function readBody(\n request: Request,\n maxBytes: number,\n): Promise<string> {\n const declared = Number(request.headers.get(\"content-length\"))\n\n if (Number.isFinite(declared) && declared > maxBytes) {\n throw new BodyTooLargeError()\n }\n\n if (request.body === null) return \"\"\n\n const reader = request.body.getReader()\n const chunks: Uint8Array[] = []\n let total = 0\n\n for (;;) {\n const chunk = await reader.read()\n\n if (chunk.done) break\n\n const bytes: unknown = chunk.value\n\n // Request bodies are byte streams; anything else is not a valid body.\n if (!(bytes instanceof Uint8Array)) throw new TypeError(\"Body is not bytes\")\n\n total += bytes.byteLength\n\n if (total > maxBytes) {\n await reader.cancel()\n throw new BodyTooLargeError()\n }\n\n chunks.push(bytes)\n }\n\n return Buffer.concat(chunks).toString(\"utf8\")\n}\n", "import {\n CONSENT_APPROVE,\n CONSENT_FIELDS,\n FORM_MEDIA_TYPE,\n} from \"../constants/protocol.js\"\nimport { consentPageResponse } from \"../core/consent-page.js\"\nimport { grantSummary } from \"../core/grant-summary.js\"\nimport { revokeSealedUpstreamToken } from \"../core/grants.js\"\nimport { decide } from \"../core/policy.js\"\nimport { BodyTooLargeError } from \"../errors/body-too-large-error.js\"\nimport { hasContentType, readBody } from \"../helpers/body.js\"\nimport {\n bindingCookieName,\n clearHostCookie,\n readCookie,\n} from \"../helpers/cookies.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\nimport { isRecord } from \"../helpers/guards.js\"\nimport { jsonCopy } from \"../helpers/json.js\"\nimport {\n groupParams,\n repeatedDefined,\n single,\n type Params,\n} from \"../helpers/params.js\"\nimport { buildRedirect } from \"../helpers/redirect-uris.js\"\nimport { redirect } from \"../helpers/responses.js\"\nimport { hasExpired } from \"../helpers/time.js\"\nimport { constantTimeEqual, randomToken, sha256Hex } from \"../helpers/tokens.js\"\nimport type { HookClient } from \"../interfaces/consent.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { ConsentRequest, GrantRecord } from \"../interfaces/records.js\"\n\n/** The package's own consent form fields; never passed to `readConsentFields`. */\nconst RESERVED_FIELDS: readonly string[] = Object.values(CONSENT_FIELDS)\n\n/** Most field errors `readConsentFields` may report. */\nconst FIELD_ERROR_LIMIT = 50\n\n/** Longest field name or field error message accepted from `readConsentFields`. */\nconst FIELD_TEXT_LIMIT = 200\n\n/**\n * The extra fields of a consent form: the first value of every field that is\n * not one of the package's own.\n *\n * @param form - The parsed consent form.\n * @returns Field name to value.\n */\nfunction extraFields(form: Params): Record<string, string> {\n const fields: [string, string][] = []\n\n for (const [name, values] of form) {\n const [first] = values\n\n if (!RESERVED_FIELDS.includes(name) && first !== undefined) {\n fields.push([name, first])\n }\n }\n\n // Own data properties only: a field named `__proto__` stays a field.\n return Object.fromEntries(fields)\n}\n\n/**\n * Check what `readConsentFields` reported as field errors.\n *\n * @param value - The hook's `fieldErrors`, of unknown shape.\n * @returns 1\u201350 non-empty string messages by field name, or `undefined`\n * when the value is not usable.\n */\nfunction readFieldErrors(\n value: unknown,\n): Readonly<Record<string, string>> | undefined {\n if (!isRecord(value)) return undefined\n\n const entries = Object.entries(value)\n\n if (entries.length === 0 || entries.length > FIELD_ERROR_LIMIT) {\n return undefined\n }\n\n const errors: [string, string][] = []\n\n for (const [name, message] of entries) {\n if (\n typeof message !== \"string\" ||\n message === \"\" ||\n message.length > FIELD_TEXT_LIMIT ||\n name.length > FIELD_TEXT_LIMIT\n ) {\n return undefined\n }\n\n errors.push([name, message])\n }\n\n return Object.fromEntries(errors)\n}\n\n/** What `readConsentFields` decided. */\ntype FieldsVerdict =\n | { readonly kind: \"accepted\"; readonly grantData: unknown }\n | {\n readonly kind: \"correct\"\n readonly fieldErrors: Readonly<Record<string, string>>\n }\n | { readonly kind: \"failed\" }\n\n/**\n * The client of a consent request, as hooks see it.\n *\n * @param consent - The consent request.\n * @returns The client id, kind and name.\n */\nfunction hookClient(consent: ConsentRequest): HookClient {\n return {\n clientId: consent.clientId,\n kind: consent.clientKind,\n ...(consent.clientName !== undefined\n ? { clientName: consent.clientName }\n : {}),\n }\n}\n\n/**\n * Run `readConsentFields`, if set, and validate its result.\n *\n * @param context - Context supplying the hook and `limits.grantDataBytes`.\n * @param consent - The verified consent request.\n * @param fields - The extra form fields.\n * @returns Accepted with grant data, a request to correct fields, or failed\n * (the hook threw or returned something unusable).\n */\nasync function readFields(\n context: HandlerContext,\n consent: ConsentRequest,\n fields: Readonly<Record<string, string>>,\n): Promise<FieldsVerdict> {\n const hook = context.options.readConsentFields\n\n if (hook === undefined) return { kind: \"accepted\", grantData: undefined }\n\n let result: unknown\n\n try {\n result = await hook(\n frozenCopy({\n form: { ...fields },\n details: consent.details,\n subject: consent.subject,\n client: hookClient(consent),\n scopes: [...consent.scopes],\n }),\n )\n } catch {\n return { kind: \"failed\" }\n }\n\n if (!isRecord(result)) return { kind: \"failed\" }\n\n switch (result.ok) {\n case true:\n try {\n return {\n kind: \"accepted\",\n grantData: jsonCopy(\n result.grantData,\n context.options.limits.grantDataBytes,\n \"readConsentFields\",\n ),\n }\n } catch {\n return { kind: \"failed\" }\n }\n case false: {\n const fieldErrors = readFieldErrors(result.fieldErrors)\n\n return fieldErrors === undefined\n ? { kind: \"failed\" }\n : { kind: \"correct\", fieldErrors }\n }\n default:\n return { kind: \"failed\" }\n }\n}\n\n/**\n * `POST /consent`: the person approves or declines.\n *\n * 1. The saved consent request is read, and the one-time form token and the\n * browser cookie are compared in constant time before anything else runs.\n * A mismatch, or an expired request, ends the flow and revokes the\n * upstream token.\n * 2. On approval, `readConsentFields` may ask for corrections: the page is\n * shown again and nothing is used up.\n * 3. The request is then taken from the store, once. A repeat of the same\n * submission from the same browser within `consentReplaySeconds` gets the\n * same redirect again, when the store supports it.\n * 4. `beforeGrant` may refuse; the grant and a one-time code are stored;\n * `afterGrant` runs and, if it throws, the grant is removed again.\n *\n * Every answer to the client is a 303 redirect (OAuth 2.1 \u00A7 7.5.4: a\n * redirect after a form `POST` must not repeat the `POST`) carrying `iss`\n * (RFC 9207). Every outcome without a grant revokes the upstream refresh\n * token obtained at sign-in.\n *\n * @param context - Context supplying the store, sealer, clock, lifetimes, hooks, cookie prefix and pages.\n * @param request - The `POST` from the consent page, with a form body and the binding cookie.\n * @returns A 303 redirect to the client, the consent page again, or an error page.\n * @internal\n */\nexport async function handleConsent(\n context: HandlerContext,\n request: Request,\n): Promise<Response> {\n const { options } = context\n\n if (!(await context.allowed(request, \"consent\"))) {\n return context.errorPage(request, \"rate_limited\")\n }\n\n // An extra check on top of the form token and the SameSite cookie. Pages\n // here use `Referrer-Policy: no-referrer`, under which browsers report\n // `Origin: null` for the form post; any concrete foreign origin means a\n // different site submitted the form.\n const origin = request.headers.get(\"origin\")\n\n if (origin !== null && origin !== \"null\" && origin !== options.issuerOrigin) {\n return context.errorPage(request, \"consent_unavailable\")\n }\n\n if (!hasContentType(request, FORM_MEDIA_TYPE)) {\n return context.errorPage(request, \"unsupported_submission\")\n }\n\n let form: Params\n\n try {\n form = groupParams(\n new URLSearchParams(await readBody(request, options.limits.formBytes)),\n )\n } catch (error) {\n if (error instanceof BodyTooLargeError) {\n return context.errorPage(request, \"consent_unavailable\")\n }\n\n throw error\n }\n\n if (repeatedDefined(form, RESERVED_FIELDS).length > 0) {\n return context.errorPage(request, \"consent_unavailable\")\n }\n\n const interactionId = single(form, CONSENT_FIELDS.id)\n const formToken = single(form, CONSENT_FIELDS.csrf)\n\n if (interactionId === undefined || formToken === undefined) {\n return context.errorPage(request, \"consent_unavailable\")\n }\n\n const { store, sealer } = options\n const cookieName = bindingCookieName(options.cookiePrefix, interactionId)\n const dropCookie = clearHostCookie(cookieName)\n const binding = readCookie(request, cookieName)\n const ended = () =>\n context.errorPage(request, \"consent_unavailable\", {\n setCookies: [dropCookie],\n })\n // Names one submission from one browser: only the same form token sent\n // with the same cookie can see the answer it got.\n const replayKey =\n binding === undefined\n ? undefined\n : sha256Hex(`${sha256Hex(binding)}:${sha256Hex(formToken)}`)\n const replay = async (): Promise<Response> => {\n if (replayKey === undefined || store.getConsentOutcome === undefined) {\n return ended()\n }\n\n const sealed = await store.getConsentOutcome(replayKey)\n\n if (sealed === undefined) return ended()\n\n let outcome: unknown\n\n try {\n outcome = JSON.parse(await sealer.open(sealed, replayKey))\n } catch {\n return ended()\n }\n\n // The window is enforced here, never left to the store's expiry.\n if (\n !isRecord(outcome) ||\n typeof outcome.location !== \"string\" ||\n typeof outcome.expiresAt !== \"number\" ||\n hasExpired(outcome.expiresAt, options.now())\n ) {\n return ended()\n }\n\n return redirect(outcome.location, [dropCookie], 303)\n }\n const consent = await store.getConsentRequest(interactionId)\n\n if (consent === undefined) return replay()\n\n const releaseUpstream = () =>\n revokeSealedUpstreamToken(\n context,\n consent.sealedUpstreamRefreshToken,\n consent.grantId,\n )\n const abandon = async (): Promise<void> => {\n await store.consumeConsentRequest(interactionId)\n await releaseUpstream()\n }\n\n if (hasExpired(consent.expiresAt, options.now())) {\n await abandon()\n\n return ended()\n }\n\n const tokenOk = constantTimeEqual(sha256Hex(formToken), consent.csrfHash)\n const browserOk =\n binding !== undefined &&\n constantTimeEqual(sha256Hex(binding), consent.bindingHash)\n\n if (!tokenOk || !browserOk) {\n await context.emit({ type: \"flow_binding_mismatch\", step: \"consent\" })\n await abandon()\n\n return ended()\n }\n\n const approve = single(form, CONSENT_FIELDS.decision) === CONSENT_APPROVE\n let grantData: unknown\n\n if (approve) {\n const fields = extraFields(form)\n const verdict = await readFields(context, consent, fields)\n\n switch (verdict.kind) {\n case \"correct\":\n // Shown again; the request stays usable for the corrected answer.\n return consentPageResponse(context, request, consent, formToken, {\n status: 400,\n fieldErrors: verdict.fieldErrors,\n submitted: { ...fields },\n })\n case \"failed\":\n await abandon()\n\n return context.errorPage(request, \"server_error\", {\n setCookies: [dropCookie],\n })\n case \"accepted\":\n grantData = verdict.grantData\n break\n }\n }\n\n // Taking the request is the one atomic step: of two concurrent\n // submissions only one gets it, and the other is answered like the first.\n const taken = await store.consumeConsentRequest(interactionId)\n\n if (taken?.csrfHash !== consent.csrfHash) return replay()\n\n const answer = async (\n params: Readonly<Record<string, string | undefined>>,\n ): Promise<Response> => {\n const location = buildRedirect(consent.redirectUri, {\n ...params,\n state: consent.state,\n iss: options.issuer,\n })\n\n if (\n replayKey !== undefined &&\n options.consentReplayMs > 0 &&\n store.putConsentOutcome !== undefined &&\n store.getConsentOutcome !== undefined\n ) {\n const expiresAt = options.now() + options.consentReplayMs\n\n // Sealed: the location may carry an authorization code. The expiry\n // travels inside, so a store that keeps it longer changes nothing.\n await store.putConsentOutcome(\n replayKey,\n await sealer.seal(JSON.stringify({ location, expiresAt }), replayKey),\n expiresAt,\n )\n }\n\n return redirect(location, [dropCookie], 303)\n }\n\n if (!approve) {\n await releaseUpstream()\n await context.emit({\n type: \"consent_declined\",\n clientId: consent.clientId,\n subject: consent.subject,\n })\n\n return answer({\n error: \"access_denied\",\n error_description: \"The person declined to grant access\",\n })\n }\n\n const before = options.beforeGrant\n\n if (before !== undefined) {\n const outcome = await decide(() =>\n before(\n Object.freeze({\n ...frozenCopy({\n subject: consent.subject,\n client: hookClient(consent),\n scopes: [...consent.scopes],\n grantData,\n }),\n request,\n }),\n ),\n )\n\n if (!outcome.allowed) {\n await releaseUpstream()\n await context.emit({\n type: \"authorization_refused\",\n clientId: consent.clientId,\n reason: outcome.reason ?? \"before_grant\",\n })\n\n return answer({\n error: \"access_denied\",\n error_description: \"Access was not granted\",\n })\n }\n }\n\n const now = options.now()\n const grant: GrantRecord = {\n grantId: consent.grantId,\n clientId: consent.clientId,\n subject: consent.subject,\n scopes: consent.scopes,\n resource: consent.resource,\n createdAt: now,\n expiresAt: now + options.grantTtlMs,\n ...(grantData !== undefined ? { data: grantData } : {}),\n ...(consent.allowRefresh !== undefined\n ? { allowRefresh: consent.allowRefresh }\n : {}),\n ...(consent.clientName !== undefined\n ? { clientName: consent.clientName }\n : {}),\n ...(consent.displayName !== undefined\n ? { displayName: consent.displayName }\n : {}),\n ...(consent.sealedUpstreamRefreshToken !== undefined\n ? { sealedUpstreamRefreshToken: consent.sealedUpstreamRefreshToken }\n : {}),\n ...(consent.sealedUpstreamAccessToken !== undefined &&\n consent.upstreamAccessTokenExpiresAt !== undefined\n ? {\n sealedUpstreamAccessToken: consent.sealedUpstreamAccessToken,\n upstreamAccessTokenExpiresAt: consent.upstreamAccessTokenExpiresAt,\n }\n : {}),\n }\n\n await store.putGrant(grant)\n\n const code = randomToken()\n\n await store.putAuthorizationCode({\n codeHash: sha256Hex(code),\n grantId: grant.grantId,\n clientId: consent.clientId,\n redirectUri: consent.redirectUri,\n codeChallenge: consent.codeChallenge,\n resource: consent.resource,\n scopes: consent.scopes,\n expiresAt: now + options.codeTtlMs,\n })\n\n const after = options.afterGrant\n\n if (after !== undefined) {\n try {\n await after(Object.freeze({ grant: grantSummary(grant) }))\n } catch (error) {\n // No grant may outlive a failed `afterGrant`: deleting the grant\n // makes its code worthless too.\n await store.deleteGrant(grant.grantId)\n await releaseUpstream()\n await context.emit({\n type: \"unexpected_failure\",\n where: \"afterGrant\",\n errorName: error instanceof Error ? error.name : typeof error,\n })\n\n return answer({\n error: \"server_error\",\n error_description: \"The connection could not be completed\",\n })\n }\n }\n\n await context.emit({\n type: \"consent_approved\",\n grantId: grant.grantId,\n clientId: grant.clientId,\n subject: grant.subject,\n scopes: grant.scopes,\n })\n\n return answer({ code })\n}\n", "import { permits } from \"../core/policy.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\nimport { BodyTooLargeError } from \"../errors/body-too-large-error.js\"\nimport { hasContentType, readBody } from \"../helpers/body.js\"\nimport {\n cleanClientName,\n parseClientScopes,\n parseGrantTypes,\n} from \"../helpers/client-fields.js\"\nimport { redirectHostOf, redirectUriProblem } from \"../helpers/redirect-uris.js\"\nimport { json, oauthError } from \"../helpers/responses.js\"\nimport { randomToken } from \"../helpers/tokens.js\"\nimport { isRecord, isStringArray } from \"../helpers/guards.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { ClientRecord } from \"../interfaces/records.js\"\n\nfunction badMetadata(description: string): Response {\n return oauthError(400, \"invalid_client_metadata\", description)\n}\n\nfunction badRedirect(description: string): Response {\n return oauthError(400, \"invalid_redirect_uri\", description)\n}\n\n/**\n * `POST /register`: RFC 7591 dynamic registration of a public client. The\n * response never contains a secret; metadata this server does not use is\n * accepted and discarded.\n *\n * @param context - Context supplying the store, supported scopes, limits and `allowRegistration` hook.\n * @param request - The `POST` to the registration endpoint, with a JSON body of client metadata.\n * @returns The registration response or an OAuth error.\n * @internal\n */\nexport async function handleRegister(\n context: HandlerContext,\n request: Request,\n): Promise<Response> {\n const { options } = context\n const { limits } = options\n\n if (!(await context.allowed(request, \"register\"))) {\n return oauthError(\n 429,\n \"temporarily_unavailable\",\n \"Registration rate exceeded; retry later\",\n )\n }\n\n if (!hasContentType(request, \"application/json\")) {\n return badMetadata(\"Send the client metadata as application/json\")\n }\n\n let body: unknown\n\n try {\n body = JSON.parse(await readBody(request, limits.registrationBytes))\n } catch (error) {\n return badMetadata(\n error instanceof BodyTooLargeError\n ? `The metadata exceeds ${limits.registrationBytes} bytes`\n : \"The body is not a JSON object\",\n )\n }\n\n if (!isRecord(body)) return badMetadata(\"The body is not a JSON object\")\n\n const uris = body.redirect_uris\n\n if (!isStringArray(uris) || uris.length === 0) {\n return badRedirect(\"List at least one redirect URI in redirect_uris\")\n }\n\n if (uris.length > limits.redirectUris) {\n return badRedirect(`Register at most ${limits.redirectUris} redirect URIs`)\n }\n\n for (const uri of uris) {\n const problem = redirectUriProblem(uri, limits)\n\n if (problem !== undefined) {\n return badRedirect(\n `Redirect URI refused (${problem}). Allowed: https URLs, or http on 127.0.0.1, [::1] or localhost`,\n )\n }\n }\n\n const authMethod = body.token_endpoint_auth_method\n\n if (authMethod !== undefined && authMethod !== \"none\") {\n return badMetadata(\n 'This server registers public clients only; token_endpoint_auth_method must be \"none\"',\n )\n }\n\n // Omitted grant_types means authorization_code alone (RFC 7591 \u00A7 2): a\n // client has to ask for refresh tokens explicitly.\n const grantTypes = parseGrantTypes(body.grant_types, true)\n\n if (grantTypes === undefined) {\n return badMetadata(\n 'grant_types must include \"authorization_code\" and may add \"refresh_token\"; nothing else is offered',\n )\n }\n\n const responseTypes = body.response_types ?? [\"code\"]\n\n if (\n !isStringArray(responseTypes) ||\n responseTypes.length !== 1 ||\n responseTypes[0] !== \"code\"\n ) {\n return badMetadata('The only response type offered is \"code\"')\n }\n\n const scopes = parseClientScopes(\n body.scope,\n options.scopes,\n true,\n limits.scopeLength,\n )\n\n if (scopes === undefined) {\n return badMetadata(`Available scopes: ${options.scopes.join(\" \")}`)\n }\n\n let clientName: string | undefined\n\n if (body.client_name !== undefined) {\n clientName = cleanClientName(body.client_name, limits.clientNameLength)\n\n if (clientName === undefined) {\n return badMetadata(\n `client_name needs 1 to ${limits.clientNameLength} visible characters`,\n )\n }\n }\n\n const policy = options.allowRegistration\n\n if (\n policy !== undefined &&\n !(await permits(() => policy({ metadata: frozenCopy(body), request })))\n ) {\n return oauthError(\n 403,\n \"invalid_client_metadata\",\n \"This server does not accept the registration of this client\",\n )\n }\n\n const issuedAt = options.now()\n const redirectUris = [...new Set(uris)]\n const client: ClientRecord = {\n clientId: `client-${randomToken()}`,\n redirectUris,\n kind: \"dynamic\",\n createdAt: issuedAt,\n grantTypes,\n ...(scopes !== null ? { scopes } : {}),\n ...(clientName !== undefined ? { clientName } : {}),\n }\n\n await options.store.putClient(client)\n await context.emit({\n type: \"client_registered\",\n clientId: client.clientId,\n redirectHosts: [...new Set(redirectUris.map(redirectHostOf))],\n ...(clientName !== undefined ? { clientName } : {}),\n })\n\n return json(\n 201,\n {\n client_id: client.clientId,\n client_id_issued_at: Math.floor(issuedAt / 1000),\n redirect_uris: redirectUris,\n grant_types: grantTypes,\n response_types: [\"code\"],\n token_endpoint_auth_method: \"none\",\n ...(scopes !== null ? { scope: scopes.join(\" \") } : {}),\n ...(clientName !== undefined ? { client_name: clientName } : {}),\n },\n { cors: true },\n )\n}\n", "/**\n * A request an OAuth endpoint turns down, converted into an RFC 6749 \u00A7 5.2\n * JSON error in a single place.\n *\n * @internal\n */\nexport class EndpointRefusal extends Error {\n /**\n * @param error - The OAuth error code.\n * @param description - Human-readable `error_description`.\n * @param status - HTTP status. Default: 400.\n * @param challenge - `WWW-Authenticate` value to send with the error.\n * @param detail - Why, for the `token_request_refused` event only; never sent to the caller.\n */\n constructor(\n readonly error: string,\n readonly description: string,\n readonly status: number = 400,\n readonly challenge?: string,\n readonly detail?: string,\n ) {\n super(description)\n this.name = \"EndpointRefusal\"\n }\n}\n", "import { FORM_MEDIA_TYPE } from \"../constants/protocol.js\"\nimport { BodyTooLargeError } from \"../errors/body-too-large-error.js\"\nimport { EndpointRefusal } from \"../errors/endpoint-refusal.js\"\nimport { hasContentType, readBody } from \"../helpers/body.js\"\nimport {\n groupParams,\n type Params,\n repeatedDefined,\n single,\n} from \"../helpers/params.js\"\nimport { sameResource } from \"../helpers/resource.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\n\n/** HTTP auth-scheme syntax (RFC 9110 \u00A7 11.1, `token`). */\nconst AUTH_SCHEME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,32}$/\n\n/** Parameters every client-authenticating form defines. */\nconst CLIENT_PARAMETERS: readonly string[] = [\n \"client_id\",\n \"client_secret\",\n \"client_assertion\",\n \"client_assertion_type\",\n]\n\n/**\n * The scheme of an `Authorization` header, for echoing in a challenge.\n *\n * @param header - The header value.\n * @returns The scheme, or `Basic` when it is not a valid scheme token.\n */\nfunction schemeOf(header: string): string {\n const scheme = header.trim().split(\" \")[0] ?? \"\"\n\n return AUTH_SCHEME.test(scheme) ? scheme : \"Basic\"\n}\n\n/**\n * Parse the form body shared by the token and revocation endpoints and make\n * sure the caller acts as a public client.\n *\n * @param request - A POST to the token or revocation endpoint; its body is consumed.\n * @param maxBytes - Largest body accepted, in bytes (`limits.formBytes`).\n * @param defined - The parameters the endpoint defines; only these may not be repeated.\n * @returns The grouped form and the `client_id`, if sent.\n * @throws {EndpointRefusal} When the media type is wrong, the body is too large, a defined parameter is repeated, or client credentials are present.\n * @internal\n */\nexport async function readPublicClientForm(\n request: Request,\n maxBytes: number,\n defined: readonly string[],\n): Promise<{ form: Params; clientId: string | undefined }> {\n if (!hasContentType(request, FORM_MEDIA_TYPE)) {\n throw new EndpointRefusal(\n \"invalid_request\",\n \"Send the parameters as an application/x-www-form-urlencoded body\",\n )\n }\n\n let form: Params\n\n try {\n form = groupParams(new URLSearchParams(await readBody(request, maxBytes)))\n } catch (error) {\n if (error instanceof BodyTooLargeError) {\n throw new EndpointRefusal(\n \"invalid_request\",\n `The body exceeds ${maxBytes} bytes`,\n )\n }\n\n throw error\n }\n\n // RFC 6749 \u00A7 3.2 forbids repeating a defined parameter; unknown ones are\n // ignored, repeated or not.\n const repeated = repeatedDefined(form, [...CLIENT_PARAMETERS, ...defined])\n\n if (repeated.length > 0) {\n throw new EndpointRefusal(\n \"invalid_request\",\n `Parameter sent more than once: ${repeated.join(\", \")}`,\n )\n }\n\n // Credentials of any kind mean the client thinks it is confidential, which\n // no client of this server is. Saying so beats silently ignoring them.\n // RFC 6749 \u00A7 5.2: 401 with a matching `WWW-Authenticate` when the client\n // used the Authorization header; otherwise a plain 400 `invalid_client`.\n const authorization = request.headers.get(\"authorization\")\n const refusal =\n \"Clients here authenticate with PKCE alone; remove the client credentials\"\n\n if (authorization !== null) {\n throw new EndpointRefusal(\n \"invalid_client\",\n refusal,\n 401,\n schemeOf(authorization),\n )\n }\n\n if (form.has(\"client_secret\") || form.has(\"client_assertion\")) {\n throw new EndpointRefusal(\"invalid_client\", refusal)\n }\n\n return { form, clientId: single(form, \"client_id\") }\n}\n\n/**\n * An explicit `resource` must be this server's (RFC 8707).\n *\n * @param context - Context supplying the configured resource URL.\n * @param form - Parsed token-endpoint form; only its optional `resource` value is read.\n * @throws {EndpointRefusal} `invalid_target` when `resource` names another resource.\n * @internal\n */\nexport function requireOwnResource(\n context: HandlerContext,\n form: Params,\n): void {\n const resource = single(form, \"resource\")\n\n if (\n resource !== undefined &&\n !sameResource(context.options.resource, resource)\n ) {\n throw new EndpointRefusal(\n \"invalid_target\",\n `Tokens from this server are only for ${context.options.resource}`,\n )\n }\n}\n", "import { readPublicClientForm } from \"../core/client-form.js\"\nimport { EndpointRefusal } from \"../errors/endpoint-refusal.js\"\nimport { single } from \"../helpers/params.js\"\nimport { oauthError } from \"../helpers/responses.js\"\nimport { isOpaqueToken, sha256Hex } from \"../helpers/tokens.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { RevokeGrant } from \"../interfaces/routes.js\"\n\nconst DONE_HEADERS: Readonly<Record<string, string>> = {\n \"Cache-Control\": \"no-store\",\n Pragma: \"no-cache\",\n \"Access-Control-Allow-Origin\": \"*\",\n}\n\n/** Parameters the revocation endpoint defines (RFC 7009 \u00A7 2.1). */\nconst REVOKE_PARAMETERS: readonly string[] = [\"token\", \"token_type_hint\"]\n\n/**\n * `POST /revoke` (RFC 7009). The answer is `200` whether or not the token was\n * known, so the endpoint cannot be used to probe for tokens (\u00A7 2.2).\n *\n * A refresh token ends its whole grant. An access token is deleted on its\n * own. If the caller names a `client_id`, only that client's tokens are\n * affected.\n *\n * @param context - Context supplying the store and size limits.\n * @param request - The `POST` to the revocation endpoint, with a form body.\n * @param revoke - Ends a grant; called when a refresh token is revoked.\n * @returns A 200 response or an OAuth error.\n * @internal\n */\nexport async function handleRevoke(\n context: HandlerContext,\n request: Request,\n revoke: RevokeGrant,\n): Promise<Response> {\n const { store, limits } = context.options\n const done = () => new Response(null, { status: 200, headers: DONE_HEADERS })\n\n try {\n if (!(await context.allowed(request, \"revoke\"))) {\n return oauthError(\n 429,\n \"temporarily_unavailable\",\n \"Request rate exceeded; retry later\",\n )\n }\n\n const { form, clientId } = await readPublicClientForm(\n request,\n limits.formBytes,\n REVOKE_PARAMETERS,\n )\n const token = single(form, \"token\")\n\n if (token === undefined) {\n throw new EndpointRefusal(\"invalid_request\", \"Missing parameter: token\")\n }\n\n if (!isOpaqueToken(token)) return done()\n\n const hash = sha256Hex(token)\n const callerMayAct = (owner: string) =>\n clientId === undefined || clientId === owner\n const asRefreshToken = async () => {\n const found = await store.getRefreshToken(hash)\n\n if (found === undefined || !callerMayAct(found.clientId)) return false\n\n await revoke(found.grantId, \"revocation_request\")\n\n return true\n }\n const asAccessToken = async () => {\n const found = await store.getAccessToken(hash)\n\n if (found === undefined || !callerMayAct(found.clientId)) return false\n\n await store.deleteAccessToken(hash)\n\n return true\n }\n const order =\n single(form, \"token_type_hint\") === \"access_token\"\n ? [asAccessToken, asRefreshToken]\n : [asRefreshToken, asAccessToken]\n\n for (const attempt of order) {\n if (await attempt()) break\n }\n\n return done()\n } catch (error) {\n if (!(error instanceof EndpointRefusal)) throw error\n\n return oauthError(\n error.status,\n error.error,\n error.description,\n error.challenge,\n )\n }\n}\n", "import { EndpointRefusal } from \"../errors/endpoint-refusal.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { GrantRecord } from \"../interfaces/records.js\"\nimport { grantSummary } from \"./grant-summary.js\"\nimport { judge } from \"./policy.js\"\n\n/**\n * Run `beforeTokenIssue`, if set. It runs before a code or refresh token is\n * used up, so a refusal never strands the client.\n *\n * @param context - Context supplying the hook.\n * @param grant - The live grant tokens would be issued under.\n * @param grantType - Which token request this is.\n * @throws {EndpointRefusal} `invalid_grant` when the hook refuses, or\n * `temporarily_unavailable` (503) when it throws.\n * @internal\n */\nexport async function requireTokenIssue(\n context: HandlerContext,\n grant: GrantRecord,\n grantType: \"authorization_code\" | \"refresh_token\",\n): Promise<void> {\n const hook = context.options.beforeTokenIssue\n\n if (hook === undefined) return\n\n let verdict: unknown\n\n try {\n verdict = await hook(\n Object.freeze({ grant: grantSummary(grant), grantType }),\n )\n } catch {\n // A failing check is not a decision: the client may retry later with\n // the same, still unused, code or refresh token.\n throw new EndpointRefusal(\n \"temporarily_unavailable\",\n \"Tokens cannot be issued right now; retry later\",\n 503,\n )\n }\n\n if (!judge(verdict).allowed) {\n throw new EndpointRefusal(\n \"invalid_grant\",\n \"Tokens are not issued for this grant\",\n )\n }\n}\n", "import { EndpointRefusal } from \"../errors/endpoint-refusal.js\"\nimport { single, type Params } from \"../helpers/params.js\"\nimport { sameResource } from \"../helpers/resource.js\"\nimport { hasExpired } from \"../helpers/time.js\"\nimport { isOpaqueToken, pkceMatches, sha256Hex } from \"../helpers/tokens.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { RevokeGrant } from \"../interfaces/routes.js\"\nimport type { TokenResponse } from \"../interfaces/token-response.js\"\nimport { requireOwnResource } from \"./client-form.js\"\nimport { issueTokenPair, liveGrant } from \"./grants.js\"\nimport { requireTokenIssue } from \"./token-policy.js\"\n\n/**\n * Exchange an authorization code (OAuth 2.1 \u00A7 4.1.3).\n *\n * The code is first READ, and the client, `redirect_uri` (when sent),\n * `resource` (when sent) and PKCE verifier are compared with it. A mismatch\n * is refused without using the code up or touching the grant: whoever holds\n * a stolen code can neither spend it nor end the person's session by\n * guessing verifiers, and guessing one (256 bits for a 43-character\n * verifier, under the token endpoint's rate limit) is hopeless. Only a\n * fully matching request consumes the code, atomically: the first gets\n * tokens; a later, fully valid one is a replay and ends the grant\n * (OAuth 2.1 \u00A7 4.1.3, RFC 9700 \u00A7 4.5.3.1).\n *\n * @param context - Context supplying the store, clock, hooks and token lifetimes.\n * @param form - Parsed token-endpoint form, already checked for repeated parameters and client credentials.\n * @param clientId - The `client_id` sent with the request; for stored clients it was confirmed to exist.\n * @param revoke - Ends a grant; used when a code is replayed or presented after it expired.\n * @returns The RFC 6749 \u00A7 5.1 token response for the code's grant.\n * @throws {EndpointRefusal} When a parameter is missing, the code is unknown, replayed or expired, a value differs from the authorization, `beforeTokenIssue` refuses, or the grant is gone.\n * @internal\n */\nexport async function exchangeCode(\n context: HandlerContext,\n form: Params,\n clientId: string,\n revoke: RevokeGrant,\n): Promise<TokenResponse> {\n const { options } = context\n const code = single(form, \"code\")\n const verifier = single(form, \"code_verifier\")\n // Optional: OAuth 2.1 binds the code to the client and PKCE instead.\n const redirectUri = single(form, \"redirect_uri\")\n const missing = [\n code === undefined ? \"code\" : undefined,\n verifier === undefined ? \"code_verifier\" : undefined,\n ].filter((name) => name !== undefined)\n\n if (code === undefined || verifier === undefined) {\n throw new EndpointRefusal(\n \"invalid_request\",\n `Missing parameter: ${missing.join(\", \")}`,\n )\n }\n\n requireOwnResource(context, form)\n\n // One answer for an unknown, expired, replayed or mismatched code, so\n // the response says nothing about which check failed; the event does.\n const unusable = (detail: string) =>\n new EndpointRefusal(\n \"invalid_grant\",\n \"This authorization code cannot be exchanged\",\n 400,\n undefined,\n detail,\n )\n\n if (!isOpaqueToken(code)) throw unusable(\"code_malformed\")\n\n const codeHash = sha256Hex(code)\n const stored = await options.store.getAuthorizationCode(codeHash)\n\n if (stored === undefined) throw unusable(\"code_unknown\")\n\n const issued = stored.record\n const resource = single(form, \"resource\")\n // Checked in this order; the first that differs is reported.\n const comparisons: readonly (readonly [string, boolean])[] = [\n [\"client_id\", issued.clientId !== clientId],\n [\n \"redirect_uri\",\n redirectUri !== undefined && issued.redirectUri !== redirectUri,\n ],\n [\n \"resource\",\n resource !== undefined && !sameResource(issued.resource, resource),\n ],\n [\"code_verifier\", !pkceMatches(verifier, issued.codeChallenge)],\n ]\n const differs = comparisons.find(([, mismatch]) => mismatch)?.[0]\n\n if (differs !== undefined) {\n throw unusable(`${differs}_mismatch`)\n }\n\n const replayed = async () => {\n // A second fully valid redemption means someone else holds the code\n // and its verifier too: undo everything it led to.\n await context.emit({\n type: \"authorization_code_replayed\",\n grantId: issued.grantId,\n clientId: issued.clientId,\n })\n await revoke(issued.grantId, \"authorization_code_replay\")\n\n return unusable(\"code_replayed\")\n }\n\n if (stored.consumed) throw await replayed()\n\n if (hasExpired(issued.expiresAt, options.now())) {\n await revoke(issued.grantId, \"authorization_code_expired\")\n\n throw unusable(\"code_expired\")\n }\n\n const grant = await liveGrant(context, issued.grantId, clientId)\n\n if (grant === undefined) {\n throw new EndpointRefusal(\"invalid_grant\", \"Access has been withdrawn\")\n }\n\n await requireTokenIssue(context, grant, \"authorization_code\")\n\n const redeemed = await options.store.consumeAuthorizationCode(codeHash)\n\n if (redeemed === undefined) throw unusable(\"code_unknown\")\n\n if (!redeemed.firstUse) throw await replayed()\n\n const tokens = await issueTokenPair(context, grant, issued.scopes)\n\n await context.emit({\n type: \"token_issued\",\n grantId: grant.grantId,\n clientId,\n subject: grant.subject,\n grantType: \"authorization_code\",\n })\n\n return tokens\n}\n", "import { EndpointRefusal } from \"../errors/endpoint-refusal.js\"\nimport { single, type Params } from \"../helpers/params.js\"\nimport { isOpaqueToken, sha256Hex } from \"../helpers/tokens.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { RevokeGrant } from \"../interfaces/routes.js\"\nimport type { TokenResponse } from \"../interfaces/token-response.js\"\nimport { requireOwnResource } from \"./client-form.js\"\nimport { issueTokenPair, liveGrant } from \"./grants.js\"\nimport { requireTokenIssue } from \"./token-policy.js\"\nimport { hasExpired } from \"../helpers/time.js\"\n\n/**\n * Exchange a refresh token for a new access token and a rotated refresh\n * token (OAuth 2.1 \u00A7 4.3).\n *\n * @param context - Context supplying the store, clock, retry window, hooks and token lifetimes.\n * @param form - Parsed token-endpoint form, already checked for repeated parameters and client credentials.\n * @param clientId - The `client_id` sent with the request; the refresh token must have been issued to it.\n * @param revoke - Ends a grant; used when a spent refresh token is presented again outside the retry window.\n * @returns The RFC 6749 \u00A7 5.1 token response with a rotated refresh token.\n * @throws {EndpointRefusal} When the token is missing, unknown, expired, another client's, replayed, asks for wider scopes, or its grant is gone.\n * @internal\n */\nexport async function exchangeRefreshToken(\n context: HandlerContext,\n form: Params,\n clientId: string,\n revoke: RevokeGrant,\n): Promise<TokenResponse> {\n const { options } = context\n const presented = single(form, \"refresh_token\")\n\n if (presented === undefined) {\n throw new EndpointRefusal(\n \"invalid_request\",\n \"Missing parameter: refresh_token\",\n )\n }\n\n requireOwnResource(context, form)\n\n const unusable = () =>\n new EndpointRefusal(\"invalid_grant\", \"This refresh token cannot be used\")\n\n if (!isOpaqueToken(presented)) throw unusable()\n\n const hash = sha256Hex(presented)\n const now = options.now()\n // Look before marking: a different client presenting the token must not\n // be able to use it up.\n const known = await options.store.getRefreshToken(hash)\n\n if (\n known === undefined ||\n known.clientId !== clientId ||\n hasExpired(known.expiresAt, now)\n ) {\n throw unusable()\n }\n\n let scopes = known.scopes\n const narrowed = single(form, \"scope\")\n\n if (narrowed !== undefined) {\n const asked = [...new Set(narrowed.split(\" \").filter((s) => s.length > 0))]\n\n if (asked.length === 0 || asked.some((s) => !known.scopes.includes(s))) {\n throw new EndpointRefusal(\n \"invalid_scope\",\n \"A refreshed token can only carry scopes already granted\",\n )\n }\n\n scopes = asked\n }\n\n // Checked before the token is marked used, so a refusal or a failing\n // check leaves it usable.\n const current = await liveGrant(context, known.grantId, clientId)\n\n if (current === undefined || current.allowRefresh === false) {\n throw new EndpointRefusal(\"invalid_grant\", \"Access has been withdrawn\")\n }\n\n await requireTokenIssue(context, current, \"refresh_token\")\n\n const marked = await options.store.useRefreshToken(hash, now)\n\n if (marked === undefined) throw unusable()\n\n const earlier = marked.previousUsedAt\n const withinRetryWindow =\n earlier !== undefined &&\n options.refreshReuseGraceMs > 0 &&\n now - earlier <= options.refreshReuseGraceMs\n\n if (earlier !== undefined && !withinRetryWindow) {\n // Presented again after it was spent, outside the retry window: assume\n // it was copied and withdraw everything issued under it (OAuth 2.1 \u00A7 4.3.1,\n // RFC 9700 \u00A7 4.14).\n await context.emit({\n type: \"refresh_token_replayed\",\n grantId: marked.record.grantId,\n clientId,\n })\n await revoke(marked.record.grantId, \"refresh_token_replay\")\n\n throw unusable()\n }\n\n const grant = await liveGrant(context, marked.record.grantId, clientId)\n\n if (grant === undefined || grant.allowRefresh === false) {\n throw new EndpointRefusal(\"invalid_grant\", \"Access has been withdrawn\")\n }\n\n // A narrowed request narrows only this access token; the new refresh\n // token keeps the scopes it replaces (OAuth 2.1 \u00A7 4.3.1, RFC 6749 \u00A7 6).\n const tokens = await issueTokenPair(context, grant, scopes, known.scopes)\n\n await context.emit({\n type: \"token_issued\",\n grantId: grant.grantId,\n clientId,\n subject: grant.subject,\n grantType: \"refresh_token\",\n })\n\n return tokens\n}\n", "import { readPublicClientForm } from \"../core/client-form.js\"\nimport { resolveClient } from \"../core/clients.js\"\nimport { exchangeCode } from \"../core/code-exchange.js\"\nimport { exchangeRefreshToken } from \"../core/refresh-exchange.js\"\nimport { EndpointRefusal } from \"../errors/endpoint-refusal.js\"\nimport { isMetadataDocumentClientId } from \"../helpers/client-fields.js\"\nimport { single } from \"../helpers/params.js\"\nimport { json, oauthError } from \"../helpers/responses.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { RevokeGrant } from \"../interfaces/routes.js\"\nimport type { TokenResponse } from \"../interfaces/token-response.js\"\n\n/** Parameters the token endpoint defines (besides client authentication). */\nconst TOKEN_PARAMETERS: readonly string[] = [\n \"grant_type\",\n \"code\",\n \"redirect_uri\",\n \"code_verifier\",\n \"refresh_token\",\n \"scope\",\n \"resource\",\n]\n\n/**\n * `POST /token`. Callers identify themselves by `client_id` alone and prove\n * possession with PKCE; codes and refresh tokens are exchanged here. Errors use\n * the RFC 6749 \u00A7 5.2 format. `invalid_client` is a 400, except a 401 with a\n * `WWW-Authenticate` challenge in the scheme the client used when it sent an\n * `Authorization` header (RFC 6749 \u00A7 5.2).\n *\n * @param context - Context supplying the store, clients, clock and token settings.\n * @param request - The `POST` to the token endpoint, with a form body.\n * @param revoke - Ends a grant; used for code and refresh-token replay and mismatches.\n * @returns The token response or an OAuth error.\n * @internal\n */\nexport async function handleToken(\n context: HandlerContext,\n request: Request,\n revoke: RevokeGrant,\n): Promise<Response> {\n let clientId: string | undefined\n\n try {\n if (!(await context.allowed(request, \"token\"))) {\n return oauthError(\n 429,\n \"temporarily_unavailable\",\n \"Request rate exceeded; retry later\",\n )\n }\n\n const parsed = await readPublicClientForm(\n request,\n context.options.limits.formBytes,\n TOKEN_PARAMETERS,\n )\n\n clientId = parsed.clientId\n\n if (clientId === undefined) {\n throw new EndpointRefusal(\n \"invalid_client\",\n \"Missing parameter: client_id\",\n )\n }\n\n // Registered clients must still exist. A metadata-document client was\n // checked at /authorize, and its code and tokens are tied to it.\n const byUrl = isMetadataDocumentClientId(clientId)\n const client = byUrl ? undefined : await resolveClient(context, clientId)\n\n if (!byUrl && client === undefined) {\n throw new EndpointRefusal(\n \"invalid_client\",\n \"No client is registered under this client_id\",\n )\n }\n\n const grantType = single(parsed.form, \"grant_type\")\n\n if (\n grantType === \"refresh_token\" &&\n client?.grantTypes !== undefined &&\n !client.grantTypes.includes(\"refresh_token\")\n ) {\n throw new EndpointRefusal(\n \"unauthorized_client\",\n \"refresh_token is not among this client's registered grant_types\",\n )\n }\n\n let tokens: TokenResponse\n\n switch (grantType) {\n case \"authorization_code\":\n tokens = await exchangeCode(context, parsed.form, clientId, revoke)\n break\n case \"refresh_token\":\n tokens = await exchangeRefreshToken(\n context,\n parsed.form,\n clientId,\n revoke,\n )\n break\n case undefined:\n throw new EndpointRefusal(\n \"invalid_request\",\n \"Missing parameter: grant_type\",\n )\n default:\n throw new EndpointRefusal(\n \"unsupported_grant_type\",\n \"This server only exchanges authorization codes and refresh tokens\",\n )\n }\n\n return json(200, tokens, { cors: true })\n } catch (error) {\n if (!(error instanceof EndpointRefusal)) throw error\n\n await context.emit({\n type: \"token_request_refused\",\n error: error.error,\n reason: error.detail ?? error.description,\n ...(clientId !== undefined ? { clientId } : {}),\n })\n\n return oauthError(\n error.status,\n error.error,\n error.description,\n error.challenge,\n )\n }\n}\n", "import type { ResolvedOptions } from \"../interfaces/options.js\"\n\n/**\n * RFC 8414 authorization server metadata.\n *\n * @param options - Checked options; supplies the issuer, endpoint paths, scopes and enabled features.\n * @returns The RFC 8414 document, serialized as JSON by the caller.\n * @internal\n */\nexport function authorizationServerMetadata(\n options: ResolvedOptions,\n): Record<string, unknown> {\n const at = (path: string) => `${options.issuerOrigin}${path}`\n\n return {\n issuer: options.issuer,\n authorization_endpoint: at(options.paths.authorize),\n token_endpoint: at(options.paths.token),\n ...(options.dynamicClientRegistration\n ? { registration_endpoint: at(options.paths.register) }\n : {}),\n revocation_endpoint: at(options.paths.revoke),\n scopes_supported: options.scopes,\n response_types_supported: [\"code\"],\n response_modes_supported: [\"query\"],\n grant_types_supported: [\"authorization_code\", \"refresh_token\"],\n code_challenge_methods_supported: [\"S256\"],\n token_endpoint_auth_methods_supported: [\"none\"],\n revocation_endpoint_auth_methods_supported: [\"none\"],\n client_id_metadata_document_supported: options.clientIdMetadataDocuments,\n authorization_response_iss_parameter_supported: true,\n }\n}\n\n/**\n * RFC 9728 protected resource metadata.\n *\n * @param options - Checked options; supplies the resource, issuer, scopes and resource name.\n * @returns The RFC 9728 document, serialized as JSON by the caller.\n * @internal\n */\nexport function protectedResourceMetadata(\n options: ResolvedOptions,\n): Record<string, unknown> {\n return {\n resource: options.resource,\n authorization_servers: [options.issuer],\n scopes_supported: options.scopes,\n bearer_methods_supported: [\"header\"],\n ...(options.resourceName !== undefined\n ? { resource_name: options.resourceName }\n : {}),\n }\n}\n", "import { handleAuthorize } from \"../handlers/authorize.js\"\nimport { handleCallback } from \"../handlers/callback.js\"\nimport { handleConsent } from \"../handlers/consent.js\"\nimport { handleRegister } from \"../handlers/register.js\"\nimport { handleRevoke } from \"../handlers/revoke.js\"\nimport { handleToken } from \"../handlers/token.js\"\nimport { json, oauthError } from \"../helpers/responses.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { RevokeGrant, RouteEntry } from \"../interfaces/routes.js\"\nimport {\n authorizationServerMetadata,\n protectedResourceMetadata,\n} from \"./metadata.js\"\n\n/**\n * Run a handler, turning any unexpected error into a generic response and\n * an `unexpected_failure` event, so no internal detail reaches the client.\n *\n * @param context - Context used to emit `unexpected_failure` when the handler throws.\n * @param where - Handler name for the event.\n * @param run - The handler.\n * @param fail - Builds the generic response.\n * @returns The handler's response, or the generic one.\n */\nasync function guarded(\n context: HandlerContext,\n where: string,\n run: () => Promise<Response>,\n fail: () => Response,\n): Promise<Response> {\n try {\n return await run()\n } catch (error) {\n await context.emit({\n type: \"unexpected_failure\",\n where,\n errorName: error instanceof Error ? error.name : \"unknown\",\n })\n\n return fail()\n }\n}\n\n/**\n * Build the route table: metadata documents, registration (when enabled),\n * authorization, callback, consent, token and revocation. When two paths\n * coincide, the first entry wins.\n *\n * @param context - Context with the checked options; paths, enabled endpoints and metadata come from it.\n * @param revoke - Ends a grant with a reason; passed to the token and revocation handlers for replay and revocation.\n * @returns Routes by exact path.\n * @internal\n */\nexport function buildRoutes(\n context: HandlerContext,\n revoke: RevokeGrant,\n): ReadonlyMap<string, RouteEntry> {\n const { options } = context\n const serverError = () =>\n oauthError(\n 500,\n \"server_error\",\n \"An unexpected condition stopped the request\",\n )\n const pageError = (request: Request) =>\n context.errorPage(request, \"server_error\")\n const routes = new Map<string, RouteEntry>()\n const add = (path: string, entry: RouteEntry) => {\n if (!routes.has(path)) routes.set(path, entry)\n }\n const document = (body: Record<string, unknown>): RouteEntry => ({\n methods: [\"GET\"],\n cors: true,\n run: () =>\n Promise.resolve(json(200, body, { cors: true, cacheable: true })),\n })\n\n for (const path of options.resourceMetadataPaths) {\n add(path, document(protectedResourceMetadata(options)))\n }\n\n for (const path of options.authorizationServerMetadataPaths) {\n add(path, document(authorizationServerMetadata(options)))\n }\n\n if (options.dynamicClientRegistration) {\n add(options.paths.register, {\n methods: [\"POST\"],\n cors: true,\n run: (request) =>\n guarded(\n context,\n \"register\",\n () => handleRegister(context, request),\n serverError,\n ),\n })\n }\n\n add(options.paths.authorize, {\n methods: [\"GET\"],\n cors: false,\n run: (request) =>\n guarded(\n context,\n \"authorize\",\n () => handleAuthorize(context, request),\n () => pageError(request),\n ),\n })\n add(options.paths.callback, {\n methods: [\"GET\"],\n cors: false,\n run: (request) =>\n guarded(\n context,\n \"callback\",\n () => handleCallback(context, request),\n () => pageError(request),\n ),\n })\n add(options.paths.consent, {\n methods: [\"POST\"],\n cors: false,\n run: (request) =>\n guarded(\n context,\n \"consent\",\n () => handleConsent(context, request),\n () => pageError(request),\n ),\n })\n add(options.paths.token, {\n methods: [\"POST\"],\n cors: true,\n run: (request) =>\n guarded(\n context,\n \"token\",\n () => handleToken(context, request, revoke),\n serverError,\n ),\n })\n add(options.paths.revoke, {\n methods: [\"POST\"],\n cors: true,\n run: (request) =>\n guarded(\n context,\n \"revoke\",\n () => handleRevoke(context, request, revoke),\n serverError,\n ),\n })\n\n return routes\n}\n", "/**\n * The grant behind a token has been removed or has expired, so no upstream\n * token can be produced for it.\n *\n * @example\n * ```ts\n * import { type AuthSuccess, type McpAuth, GrantInactiveError } from \"@gydr-ai/mcp-oauth\"\n *\n * export async function upstreamOrChallenge(\n * auth: McpAuth,\n * request: Request,\n * result: AuthSuccess,\n * ): Promise<string | Response> {\n * try {\n * return await result.getUpstreamAccessToken()\n * } catch (error) {\n * if (error instanceof GrantInactiveError) return auth.unauthorized(request)\n * throw error\n * }\n * }\n * ```\n */\nexport class GrantInactiveError extends Error {\n /** Creates the error with a fixed message. */\n constructor() {\n super(\"The grant this token belonged to has been removed\")\n this.name = \"GrantInactiveError\"\n }\n}\n", "/**\n * No upstream access token could be obtained for a grant. The message never\n * contains a token.\n *\n * @example\n * ```ts\n * import { type AuthSuccess, UpstreamTokenError } from \"@gydr-ai/mcp-oauth\"\n *\n * export async function upstreamToken(result: AuthSuccess): Promise<string | undefined> {\n * try {\n * return await result.getUpstreamAccessToken()\n * } catch (error) {\n * if (error instanceof UpstreamTokenError) return undefined\n * throw error\n * }\n * }\n * ```\n */\nexport class UpstreamTokenError extends Error {\n /**\n * @param message - What failed.\n * @param options - Standard error options, such as `cause`.\n * @param options.cause - The error that caused the failure, such as the provider's rejection.\n */\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options)\n this.name = \"UpstreamTokenError\"\n }\n}\n", "import {\n UPSTREAM_CACHE_GRANTS,\n UPSTREAM_EXPIRY_SKEW_MS,\n UPSTREAM_LIFETIME_CEILING_SECONDS,\n} from \"../constants/defaults.js\"\nimport { GrantInactiveError } from \"../errors/grant-inactive-error.js\"\nimport { UpstreamTokenError } from \"../errors/upstream-token-error.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { GrantRecord } from \"../interfaces/records.js\"\nimport { hasExpired } from \"../helpers/time.js\"\n\n/** A load of the upstream token for one grant that is under way. */\ninterface Flight {\n readonly promise: Promise<string>\n /** Whether it renews at the identity provider whatever is cached. */\n readonly forced: boolean\n}\n\n/** An upstream access token held in memory. */\ninterface CachedToken {\n readonly token: string\n readonly expiresAt: number\n}\n\n/**\n * Supplies an upstream access token per grant, trying in turn: this\n * process's memory, the sealed copy kept on the grant, and finally a renewal\n * at the identity provider. While a renewal for a grant is under way, other\n * callers for that grant wait for it instead of starting their own, so an\n * identity provider that rotates refresh tokens never sees one used twice\n * from here.\n *\n * @internal\n */\nexport class UpstreamTokenCache {\n readonly #context: HandlerContext\n readonly #cache = new Map<string, CachedToken>()\n readonly #inflight = new Map<string, Flight>()\n /** Grants being revoked in this process, oldest first (bounded). */\n readonly #revoking = new Set<string>()\n\n /**\n *\n * @param context - Context of the owning `createMcpAuth` instance; supplies the store, sealer, identity provider and clock.\n */\n constructor(context: HandlerContext) {\n this.#context = context\n }\n\n /**\n * Forget a grant that is being revoked. Its cached upstream access token\n * is dropped, and a renewal for it that is under way revokes the token\n * it obtains instead of storing it.\n *\n * @param grantId - Id of the grant being revoked; unknown ids are ignored.\n */\n forget(grantId: string): void {\n this.#cache.delete(grantId)\n\n if (this.#revoking.size >= UPSTREAM_CACHE_GRANTS) {\n const oldest = this.#revoking.values().next()\n\n if (oldest.done !== true) this.#revoking.delete(oldest.value)\n }\n\n this.#revoking.add(grantId)\n }\n\n /**\n * A valid upstream access token for the grant.\n *\n * @param grantId - Id of a grant already confirmed live by `authenticate`; it is looked up again before any renewal.\n * @param forceRefresh - Skip the memory and stored copies and renew at the identity provider. Joins a renewal already under way rather than starting a second.\n * @returns The upstream access token, valid for at least another minute.\n */\n get(grantId: string, forceRefresh = false): Promise<string> {\n const running = this.#inflight.get(grantId)\n\n if (!forceRefresh) {\n const now = this.#context.options.now()\n const cached = this.#cache.get(grantId)\n\n if (\n cached !== undefined &&\n !hasExpired(cached.expiresAt - UPSTREAM_EXPIRY_SKEW_MS, now)\n ) {\n return Promise.resolve(cached.token)\n }\n\n if (running !== undefined) return running.promise\n\n return this.#start(grantId, false, this.#load(grantId))\n }\n\n if (running?.forced === true) return running.promise\n\n // A plain load under way may hand back the very token the caller wants\n // replaced: let it settle (so one refresh token is never used twice at\n // once), then renew.\n const after =\n running === undefined\n ? this.#renew(grantId)\n : running.promise.then(\n () => this.#renew(grantId),\n () => this.#renew(grantId),\n )\n\n return this.#start(grantId, true, after)\n }\n\n #start(\n grantId: string,\n forced: boolean,\n work: Promise<string>,\n ): Promise<string> {\n const promise = work.finally(() => {\n if (this.#inflight.get(grantId)?.promise === promise) {\n this.#inflight.delete(grantId)\n }\n })\n\n this.#inflight.set(grantId, { promise, forced })\n\n return promise\n }\n\n /**\n * Drop every cached copy of the grant's upstream access token, then renew\n * it at the identity provider.\n *\n * @param grantId - The grant.\n * @returns The new upstream access token.\n */\n async #renew(grantId: string): Promise<string> {\n const { options } = this.#context\n\n this.#cache.delete(grantId)\n\n const grant = await options.store.getGrant(grantId)\n\n if (grant === undefined || hasExpired(grant.expiresAt, options.now())) {\n throw new GrantInactiveError()\n }\n\n // An expiry of 0 makes the stored copy unusable even if the renewal\n // below fails.\n if (grant.sealedUpstreamAccessToken !== undefined) {\n await options.store.updateGrant(grantId, {\n upstreamAccessTokenExpiresAt: 0,\n })\n }\n\n return this.#refresh(grant)\n }\n\n #remember(grantId: string, token: CachedToken): void {\n if (\n this.#cache.size >= UPSTREAM_CACHE_GRANTS &&\n !this.#cache.has(grantId)\n ) {\n const oldest = this.#cache.keys().next()\n\n if (oldest.done !== true) this.#cache.delete(oldest.value)\n }\n\n this.#cache.delete(grantId)\n this.#cache.set(grantId, token)\n }\n\n async #load(grantId: string): Promise<string> {\n const { options } = this.#context\n const grant = await options.store.getGrant(grantId)\n\n if (grant === undefined || hasExpired(grant.expiresAt, options.now())) {\n throw new GrantInactiveError()\n }\n\n const stored = await this.#fromGrant(grant)\n\n if (stored !== undefined) return stored\n\n return this.#refresh(grant)\n }\n\n async #fromGrant(grant: GrantRecord): Promise<string | undefined> {\n const { options } = this.#context\n\n if (\n grant.sealedUpstreamAccessToken === undefined ||\n grant.upstreamAccessTokenExpiresAt === undefined ||\n hasExpired(\n grant.upstreamAccessTokenExpiresAt - UPSTREAM_EXPIRY_SKEW_MS,\n options.now(),\n )\n ) {\n return undefined\n }\n\n try {\n const token = await options.sealer.open(\n grant.sealedUpstreamAccessToken,\n `${grant.grantId}#access`,\n )\n\n this.#remember(grant.grantId, {\n token,\n expiresAt: grant.upstreamAccessTokenExpiresAt,\n })\n\n return token\n } catch {\n // Cannot be opened (say, its key was retired): renew instead.\n return undefined\n }\n }\n\n async #refresh(grant: GrantRecord): Promise<string> {\n const { options } = this.#context\n const grantId = grant.grantId\n\n if (grant.sealedUpstreamRefreshToken === undefined) {\n throw new UpstreamTokenError(\n \"This grant has no upstream refresh token to renew with\",\n )\n }\n\n let refreshed: Awaited<ReturnType<typeof options.identityProvider.refresh>>\n\n try {\n const refreshToken = await options.sealer.open(\n grant.sealedUpstreamRefreshToken,\n grantId,\n )\n\n refreshed = await options.identityProvider.refresh(\n refreshToken,\n Object.freeze({ grantId, subject: grant.subject }),\n )\n } catch (error) {\n await this.#context.emit({\n type: \"upstream_token_refresh_failed\",\n grantId,\n reason: error instanceof Error ? error.name : \"unknown\",\n })\n\n throw new UpstreamTokenError(\n \"The identity provider would not renew the upstream token\",\n {\n cause: error,\n },\n )\n }\n\n const now = options.now()\n const expiresIn = Math.max(\n 0,\n Math.min(\n Number.isFinite(refreshed.expiresIn) ? refreshed.expiresIn : 0,\n UPSTREAM_LIFETIME_CEILING_SECONDS,\n ),\n )\n const expiresAt = now + expiresIn * 1000\n const updated = await options.store.updateGrant(grantId, {\n sealedUpstreamAccessToken: await options.sealer.seal(\n refreshed.accessToken,\n `${grantId}#access`,\n ),\n upstreamAccessTokenExpiresAt: expiresAt,\n ...(refreshed.refreshToken !== undefined\n ? {\n sealedUpstreamRefreshToken: await options.sealer.seal(\n refreshed.refreshToken,\n grantId,\n ),\n }\n : {}),\n })\n\n // The grant ended while the identity provider answered: whoever ended\n // it could only revoke the refresh token stored at that moment, so the\n // one just handed out is revoked here.\n if (!updated || this.#revoking.has(grantId)) {\n this.#cache.delete(grantId)\n\n if (\n refreshed.refreshToken !== undefined &&\n options.identityProvider.revoke !== undefined\n ) {\n try {\n await options.identityProvider.revoke(refreshed.refreshToken)\n } catch {\n // Best effort.\n }\n }\n\n throw new GrantInactiveError()\n }\n\n this.#remember(grantId, { token: refreshed.accessToken, expiresAt })\n\n return refreshed.accessToken\n }\n}\n", "import { McpAuthConfigError } from \"../errors/mcp-auth-config-error.js\"\nimport { jsonCopy } from \"../helpers/json.js\"\nimport { methodNotAllowed, preflight } from \"../helpers/responses.js\"\nimport type { McpAuth } from \"../interfaces/auth.js\"\nimport type { McpAuthOptions } from \"../interfaces/options.js\"\nimport type { RevokeGrant } from \"../interfaces/routes.js\"\nimport { authenticateRequest, bearerChallenge } from \"./authenticate.js\"\nimport { createContext } from \"./context.js\"\nimport { revokeGrantById } from \"./grants.js\"\nimport { defaultConsentPage, defaultErrorPage } from \"./pages.js\"\nimport { resolveOptions } from \"./resolve-options.js\"\nimport { buildRoutes } from \"./router.js\"\nimport { UpstreamTokenCache } from \"./upstream-tokens.js\"\n\n/**\n * Create the authorization server and resource-server guard for one MCP\n * endpoint.\n *\n * Every option that can be customised is checked here, once; extensions can\n * only add checks, enrich data or observe events, never relax a protocol or\n * security rule.\n *\n * @param options - Configuration; see {@link McpAuthOptions}.\n * @returns The request handler and guard.\n * @throws {McpAuthConfigError} When an option is invalid.\n * @example\n * ```ts\n * import { randomBytes } from \"node:crypto\"\n * import {\n * createAesGcmSealer,\n * createMcpAuth,\n * createMemoryStore,\n * createOidcProvider,\n * } from \"@gydr-ai/mcp-oauth\"\n *\n * const auth = createMcpAuth({\n * issuer: \"https://mcp.example.com\",\n * resource: \"https://mcp.example.com/mcp\",\n * identityProvider: createOidcProvider({\n * issuer: \"https://login.example.com\",\n * clientId: \"mcp-gateway\",\n * scopes: [\"openid\", \"offline_access\"],\n * redirectUri: \"https://mcp.example.com/callback\",\n * }),\n * store: createMemoryStore(),\n * sealer: createAesGcmSealer({ keys: { k1: randomBytes(32) }, currentKid: \"k1\" }),\n * })\n *\n * export async function serve(request: Request): Promise<Response> {\n * const oauth = await auth.handle(request)\n *\n * if (oauth) return oauth\n *\n * const result = await auth.authenticate(request)\n *\n * if (!result.ok) return result.response\n *\n * return Response.json({ hello: result.subject })\n * }\n * ```\n */\nexport function createMcpAuth(options: McpAuthOptions): McpAuth {\n const resolved = resolveOptions(options, {\n consent: defaultConsentPage,\n error: defaultErrorPage,\n })\n const context = createContext(resolved)\n const upstream = new UpstreamTokenCache(context)\n const revoke: RevokeGrant = (grantId, reason) =>\n revokeGrantById(context, grantId, reason, (id) => {\n upstream.forget(id)\n })\n const routes = buildRoutes(context, revoke)\n\n return {\n async handle(request) {\n const entry = routes.get(new URL(request.url).pathname)\n\n if (entry === undefined) return undefined\n\n if (request.method === \"OPTIONS\" && entry.cors) {\n return preflight([...entry.methods, \"OPTIONS\"].join(\", \"))\n }\n\n if (!entry.methods.includes(request.method)) {\n return methodNotAllowed(entry.methods.join(\", \"))\n }\n\n return entry.run(request)\n },\n authenticate(request) {\n return authenticateRequest(context, request, (grantId, forceRefresh) =>\n upstream.get(grantId, forceRefresh),\n )\n },\n unauthorized(_request, error) {\n return bearerChallenge(context, error)\n },\n revokeGrant(grantId) {\n return revoke(grantId, \"revoke_grant_call\")\n },\n renewUpstream(grantId) {\n return upstream.get(grantId, true)\n },\n async updateGrantData(grantId, data) {\n const copy = jsonCopy(\n data,\n resolved.limits.grantDataBytes,\n \"updateGrantData\",\n )\n\n return resolved.store.updateGrant(grantId, { data: copy })\n },\n async revokeGrantsForSubject(subject) {\n const { store } = resolved\n\n if (store.listGrantsBySubject === undefined) {\n throw new McpAuthConfigError(\n \"revokeGrantsForSubject needs a store with listGrantsBySubject\",\n )\n }\n\n const grants = await store.listGrantsBySubject(subject)\n let ended = 0\n\n for (const grant of grants) {\n // Never trust the store's filter to end someone else's grant.\n if (grant.subject !== subject) continue\n\n await revoke(grant.grantId, \"revoke_subject_call\")\n ended += 1\n }\n\n return ended\n },\n }\n}\n", "import { createCipheriv, createDecipheriv, randomBytes } from \"node:crypto\"\n\nimport { SealerError } from \"../errors/sealer-error.js\"\nimport type { AesGcmSealerOptions, SecretSealer } from \"../interfaces/sealer.js\"\n\n/** Length of the random initialisation vector (96 bits, as GCM recommends). */\nconst IV_BYTES = 12\n/** Length of the authentication tag. */\nconst TAG_BYTES = 16\n/** Allowed key ids. */\nconst KID_PATTERN = /^[A-Za-z0-9_-]{1,32}$/\n/** Version tag at the start of every sealed value. */\nconst FORMAT = \"v1\"\n\n/**\n * Run synchronous crypto and report its outcome as a promise.\n *\n * @param work - Synchronous encryption or decryption; anything it throws becomes the rejection.\n * @returns Resolves with `work`'s result, or rejects with what it threw (non-errors become a `SealerError`).\n */\nfunction attempt<T>(work: () => T): Promise<T> {\n try {\n return Promise.resolve(work())\n } catch (error) {\n return Promise.reject(\n error instanceof Error ? error : new SealerError(\"Sealing failed\"),\n )\n }\n}\n\n/**\n * The additional data actually authenticated: format, key id, caller AAD.\n *\n * @param kid - Key id the value is (or will be) encrypted with, already checked against the allowed pattern.\n * @param aad - Caller's owner label (a grant id, optionally suffixed with `#access`); any string.\n * @returns The bytes to authenticate.\n */\nfunction additionalData(kid: string, aad: string): Buffer {\n return Buffer.from(`${FORMAT}\\u0000${kid}\\u0000${aad}`, \"utf8\")\n}\n\n/**\n * AES-256-GCM {@link SecretSealer} with key rotation.\n *\n * Output: `v1.<kid>.<iv>.<ciphertext>.<tag>` (base64url parts). A random\n * 96-bit IV per value; the caller's AAD (the grant id) and the key id are\n * authenticated, so a sealed value moved to another grant, or relabelled with\n * another key id, fails to open.\n *\n * @param options - Keys by key id and the key id to encrypt with.\n * @returns A `SecretSealer` whose values look like `v1.<kid>.<iv>.<ciphertext>.<tag>` (base64url parts).\n * @throws {TypeError} When a key is not 32 bytes, a key id is invalid, or\n * `currentKid` is not among the keys.\n * @example\n * ```ts\n * import { createAesGcmSealer } from \"@gydr-ai/mcp-oauth\"\n *\n * const sealer = createAesGcmSealer({\n * keys: { k2026a: Buffer.from(process.env.SEALING_KEY ?? \"\", \"base64\") },\n * currentKid: \"k2026a\",\n * })\n * const sealed = await sealer.seal(\"upstream-refresh-token\", \"grant-123\")\n *\n * console.log(await sealer.open(sealed, \"grant-123\"))\n * ```\n */\nexport function createAesGcmSealer(options: AesGcmSealerOptions): SecretSealer {\n const keys = new Map<string, Buffer>()\n\n for (const [kid, key] of Object.entries(options.keys)) {\n if (!KID_PATTERN.test(kid)) {\n throw new TypeError(\n `Key id ${JSON.stringify(kid)} has characters outside A-Z a-z 0-9 _ -, or is too long`,\n )\n }\n\n if (!(key instanceof Uint8Array) || key.byteLength !== 32) {\n throw new TypeError(`Key ${kid} is not 32 bytes long`)\n }\n\n // Copy: a caller mutating its buffer must not change our key.\n keys.set(kid, Buffer.from(key))\n }\n\n const current = keys.get(options.currentKid)\n\n if (current === undefined) {\n throw new TypeError(\"currentKid has no matching entry in keys\")\n }\n\n const currentKid = options.currentKid\n\n return {\n seal(plaintext, aad) {\n return attempt(() => {\n const iv = randomBytes(IV_BYTES)\n const cipher = createCipheriv(\"aes-256-gcm\", current, iv, {\n authTagLength: TAG_BYTES,\n })\n\n cipher.setAAD(additionalData(currentKid, aad))\n\n const body = Buffer.concat([\n cipher.update(plaintext, \"utf8\"),\n cipher.final(),\n ])\n\n return [\n FORMAT,\n currentKid,\n iv.toString(\"base64url\"),\n body.toString(\"base64url\"),\n cipher.getAuthTag().toString(\"base64url\"),\n ].join(\".\")\n })\n },\n open(sealed, aad) {\n return attempt(() => {\n const parts = sealed.split(\".\")\n\n if (parts.length !== 5 || parts[0] !== FORMAT) {\n throw new SealerError(\"Value is not in sealed format\")\n }\n\n const [, kid = \"\", ivText = \"\", bodyText = \"\", tagText = \"\"] = parts\n const key = keys.get(kid)\n\n if (key === undefined)\n throw new SealerError(\n \"Value was sealed with a key that is not configured\",\n )\n\n const iv = Buffer.from(ivText, \"base64url\")\n const tag = Buffer.from(tagText, \"base64url\")\n\n if (iv.byteLength !== IV_BYTES || tag.byteLength !== TAG_BYTES) {\n throw new SealerError(\"Value is not in sealed format\")\n }\n\n try {\n const decipher = createDecipheriv(\"aes-256-gcm\", key, iv, {\n authTagLength: TAG_BYTES,\n })\n\n decipher.setAAD(additionalData(kid, aad))\n decipher.setAuthTag(tag)\n\n return Buffer.concat([\n decipher.update(Buffer.from(bodyText, \"base64url\")),\n decipher.final(),\n ]).toString(\"utf8\")\n } catch {\n throw new SealerError(\n \"Value was altered or belongs to a different owner\",\n )\n }\n })\n },\n }\n}\n", "/**\n * A sealed value could not be opened: it is malformed, was altered, belongs\n * to another owner, or was encrypted with a key that is not configured.\n *\n * @example\n * ```ts\n * import { type SecretSealer, SealerError } from \"@gydr-ai/mcp-oauth\"\n *\n * export async function openOrNothing(sealer: SecretSealer, value: string, grantId: string) {\n * try {\n * return await sealer.open(value, grantId)\n * } catch (error) {\n * if (error instanceof SealerError) return undefined\n * throw error\n * }\n * }\n * ```\n */\nexport class SealerError extends Error {\n /**\n * @param message - Why the value could not be opened.\n */\n constructor(message: string) {\n super(message)\n this.name = \"SealerError\"\n }\n}\n", "/**\n * A call to the identity provider failed. The message never contains a token.\n *\n * @example\n * ```ts\n * import { type IdentityProvider, IdentityProviderError } from \"@gydr-ai/mcp-oauth\"\n *\n * export async function renew(provider: IdentityProvider, refreshToken: string) {\n * try {\n * return await provider.refresh(refreshToken)\n * } catch (error) {\n * if (error instanceof IdentityProviderError) console.warn(error.code)\n * throw error\n * }\n * }\n * ```\n */\nexport class IdentityProviderError extends Error {\n /** The provider's OAuth `error` code, when it sent one. */\n readonly code: string | undefined\n\n /**\n * @param message - What failed.\n * @param code - The provider's OAuth `error` code, if any.\n * @param options - Standard error options, such as `cause`.\n * @param options.cause - The underlying network or verification error.\n */\n constructor(message: string, code?: string, options?: { cause?: unknown }) {\n super(message, options)\n this.name = \"IdentityProviderError\"\n this.code = code\n }\n}\n", "import { trimTrailing } from \"../helpers/text.js\"\nimport {\n createRemoteJWKSet,\n customFetch,\n type JWTPayload,\n jwtVerify,\n} from \"jose\"\n\nimport { DEFAULT_ALGORITHMS, RESERVED_PARAMS } from \"../constants/oidc.js\"\nimport { IdentityProviderError } from \"../errors/identity-provider-error.js\"\nimport { isRecord } from \"../helpers/guards.js\"\nimport type {\n IdentityProvider,\n UpstreamLogin,\n UpstreamTokens,\n} from \"../interfaces/identity-provider.js\"\nimport type {\n Discovery,\n OidcProviderOptions,\n TokenEndpointResponse,\n} from \"../interfaces/oidc.js\"\nimport {\n checkEndpoint,\n isDiscovery,\n isTokenResponse,\n} from \"./oidc-documents.js\"\n\n/**\n * An {@link IdentityProvider} for any OpenID Connect provider: discovery,\n * authorization code + PKCE S256 + nonce, and ID token verification (`iss`,\n * `aud`, `azp`, `exp`, `nonce`, signature against the IdP's JWKS with an\n * asymmetric-only algorithm allow-list).\n *\n * IdP requests use plain `fetch` with a timeout: the issuer is configured\n * by the operator and may legitimately sit on a private network.\n *\n * @param options - The provider's issuer, this server's client credentials,\n * scopes and callback URL.\n * @returns An `IdentityProvider`; discovery runs lazily on first use and is retried after a failure.\n * @throws {TypeError} When `scopes` lacks `openid`, an algorithm is not\n * asymmetric, a reserved parameter is overridden, or the issuer is not an\n * `https` URL.\n * @example\n * ```ts\n * import { createOidcProvider } from \"@gydr-ai/mcp-oauth\"\n *\n * export const identityProvider = createOidcProvider({\n * issuer: \"https://login.example.com\",\n * clientId: \"mcp-gateway\",\n * clientSecret: process.env.OIDC_CLIENT_SECRET ?? \"\",\n * scopes: [\"openid\", \"email\", \"offline_access\"],\n * redirectUri: \"https://mcp.example.com/callback\",\n * })\n * ```\n */\nexport function createOidcProvider(\n options: OidcProviderOptions,\n): IdentityProvider {\n const insecure = options.allowInsecureLocalhost === true\n const timeoutMs = options.timeoutMs ?? 10_000\n const doFetch = options.fetch ?? fetch\n const algorithms = [...(options.algorithms ?? DEFAULT_ALGORITHMS)]\n\n if (!options.scopes.includes(\"openid\")) {\n throw new TypeError(\"OIDC scopes need to contain openid\")\n }\n\n if (\n algorithms.length === 0 ||\n algorithms.some(\n (alg) =>\n alg.toLowerCase() === \"none\" || alg.toUpperCase().startsWith(\"HS\"),\n )\n ) {\n throw new TypeError(\n \"Only asymmetric signature algorithms are allowed (no none, no HS*)\",\n )\n }\n\n for (const key of Object.keys(options.extraAuthorizationParams ?? {})) {\n if (RESERVED_PARAMS.has(key)) {\n throw new TypeError(`extraAuthorizationParams may not override ${key}`)\n }\n }\n\n try {\n checkEndpoint(\"issuer\", options.issuer, insecure)\n } catch (error) {\n throw new TypeError(\n error instanceof Error\n ? error.message\n : \"Identity provider issuer is not usable\",\n )\n }\n\n const timedFetch = (url: string | URL, init: RequestInit = {}) =>\n doFetch(url, {\n ...init,\n redirect: \"error\",\n signal: AbortSignal.timeout(timeoutMs),\n })\n\n let discovery: Promise<{\n readonly document: Discovery\n readonly jwks: ReturnType<typeof createRemoteJWKSet>\n }> | null = null\n\n const discover = () => {\n if (discovery !== null) return discovery\n\n const running = (async () => {\n const base = trimTrailing(options.issuer, \"/\")\n let response: Response\n\n try {\n response = await timedFetch(\n `${base}/.well-known/openid-configuration`,\n {\n headers: { Accept: \"application/json\" },\n },\n )\n } catch (error) {\n throw new IdentityProviderError(\n \"Could not reach the identity provider's discovery document\",\n undefined,\n {\n cause: error,\n },\n )\n }\n\n if (!response.ok) {\n throw new IdentityProviderError(\n `Discovery document request returned HTTP ${response.status}`,\n )\n }\n\n const document: unknown = await response.json().catch(() => undefined)\n\n if (!isDiscovery(document)) {\n throw new IdentityProviderError(\n \"Discovery document lacks required fields\",\n )\n }\n\n // OIDC Discovery \u00A7 4.3: the document must name the configured issuer.\n if (document.issuer !== options.issuer) {\n throw new IdentityProviderError(\n \"Discovery document names a different issuer than configured\",\n )\n }\n\n if (\n document.code_challenge_methods_supported !== undefined &&\n !document.code_challenge_methods_supported.includes(\"S256\")\n ) {\n throw new IdentityProviderError(\n \"Identity provider does not offer S256 code challenges\",\n )\n }\n\n checkEndpoint(\n \"authorization_endpoint\",\n document.authorization_endpoint,\n insecure,\n )\n checkEndpoint(\"token_endpoint\", document.token_endpoint, insecure)\n\n const jwksUrl = checkEndpoint(\"jwks_uri\", document.jwks_uri, insecure)\n\n if (document.revocation_endpoint !== undefined) {\n checkEndpoint(\n \"revocation_endpoint\",\n document.revocation_endpoint,\n insecure,\n )\n }\n\n const jwks = createRemoteJWKSet(jwksUrl, {\n timeoutDuration: timeoutMs,\n [customFetch]: (url: string, init: RequestInit) =>\n doFetch(url, { ...init, redirect: \"error\" }),\n })\n\n return { document, jwks }\n })()\n\n discovery = running\n // A failed discovery is retried on the next call.\n running.catch(() => {\n if (discovery === running) discovery = null\n })\n\n return running\n }\n\n /**\n * Client authentication for the token and revocation endpoints.\n *\n * @param document - The provider's discovery document; its `token_endpoint_auth_methods_supported` picks the method.\n * @param body - Form body of the outgoing request; `client_id` (and, for `client_secret_post`, the secret) are added to it.\n * @returns Headers to add (empty when credentials go in the body).\n */\n const clientAuth = (\n document: Discovery,\n body: URLSearchParams,\n ): Record<string, string> => {\n body.set(\"client_id\", options.clientId)\n\n if (options.clientSecret === undefined) return {}\n\n const methods = document.token_endpoint_auth_methods_supported ?? [\n \"client_secret_basic\",\n ]\n\n if (methods.includes(\"client_secret_basic\")) {\n // RFC 6749 \u00A7 2.3.1: form-encode each part before base64.\n const user = encodeURIComponent(options.clientId)\n const pass = encodeURIComponent(options.clientSecret)\n\n return {\n Authorization: `Basic ${Buffer.from(`${user}:${pass}`).toString(\"base64\")}`,\n }\n }\n\n if (methods.includes(\"client_secret_post\")) {\n body.set(\"client_secret\", options.clientSecret)\n\n return {}\n }\n\n throw new IdentityProviderError(\n \"Identity provider accepts no client-secret method this library implements\",\n )\n }\n\n const postToken = async (\n document: Discovery,\n body: URLSearchParams,\n ): Promise<TokenEndpointResponse> => {\n const headers = {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n ...clientAuth(document, body),\n }\n let response: Response\n\n try {\n response = await timedFetch(document.token_endpoint, {\n method: \"POST\",\n headers,\n body: body.toString(),\n })\n } catch (error) {\n throw new IdentityProviderError(\n \"Could not reach the identity provider's token endpoint\",\n undefined,\n {\n cause: error,\n },\n )\n }\n\n const parsed: unknown = await response.json().catch(() => undefined)\n\n if (!response.ok) {\n const code =\n isRecord(parsed) && typeof parsed.error === \"string\"\n ? parsed.error.slice(0, 64)\n : undefined\n\n throw new IdentityProviderError(\n `Token endpoint returned HTTP ${response.status}`,\n code,\n )\n }\n\n if (!isTokenResponse(parsed)) {\n throw new IdentityProviderError(\n \"Token endpoint reply lacks an access_token\",\n )\n }\n\n return parsed\n }\n\n const verifyIdToken = async (\n document: Discovery,\n jwks: ReturnType<typeof createRemoteJWKSet>,\n idToken: string,\n nonce: string,\n ): Promise<JWTPayload> => {\n let payload: JWTPayload\n\n try {\n ;({ payload } = await jwtVerify(idToken, jwks, {\n issuer: document.issuer,\n audience: options.clientId,\n algorithms,\n clockTolerance: options.clockToleranceSeconds ?? 60,\n requiredClaims: [\"sub\", \"exp\", \"iat\"],\n }))\n } catch (error) {\n throw new IdentityProviderError(\n \"ID token signature or claims did not verify\",\n undefined,\n { cause: error },\n )\n }\n\n if (payload.nonce !== nonce) {\n throw new IdentityProviderError(\n \"ID token nonce differs from the one sent\",\n )\n }\n\n const audiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud]\n\n if (\n (audiences.length > 1 || payload.azp !== undefined) &&\n payload.azp !== options.clientId\n ) {\n throw new IdentityProviderError(\"ID token azp names some other client\")\n }\n\n return payload\n }\n\n return {\n async authorizationUrl({ state, codeChallenge, redirectUri, nonce }) {\n if (redirectUri !== options.redirectUri) {\n throw new IdentityProviderError(\n \"Callback URL differs from the redirectUri this provider was configured with\",\n )\n }\n\n const { document } = await discover()\n const url = new URL(document.authorization_endpoint)\n\n for (const [key, value] of Object.entries(\n options.extraAuthorizationParams ?? {},\n )) {\n url.searchParams.set(key, value)\n }\n\n url.searchParams.set(\"response_type\", \"code\")\n url.searchParams.set(\"client_id\", options.clientId)\n url.searchParams.set(\"redirect_uri\", redirectUri)\n url.searchParams.set(\"scope\", options.scopes.join(\" \"))\n url.searchParams.set(\"state\", state)\n url.searchParams.set(\"nonce\", nonce)\n url.searchParams.set(\"code_challenge\", codeChallenge)\n url.searchParams.set(\"code_challenge_method\", \"S256\")\n\n return url.toString()\n },\n\n async exchangeCode({ code, codeVerifier, redirectUri, nonce, issuer }) {\n const { document, jwks } = await discover()\n\n // RFC 9207: when the IdP sends `iss` it must be the expected issuer;\n // when it promises to send it, it must be there (mix-up defence).\n if (issuer !== undefined && issuer !== document.issuer) {\n throw new IdentityProviderError(\n \"Authorization response iss names another issuer\",\n )\n }\n\n if (\n issuer === undefined &&\n document.authorization_response_iss_parameter_supported === true\n ) {\n throw new IdentityProviderError(\n \"Authorization response omits the promised iss\",\n )\n }\n\n const tokens = await postToken(\n document,\n new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n code_verifier: codeVerifier,\n }),\n )\n\n if (tokens.id_token === undefined) {\n throw new IdentityProviderError(\"Token endpoint reply has no id_token\")\n }\n\n const claims = await verifyIdToken(document, jwks, tokens.id_token, nonce)\n const subject = claims.sub\n\n if (typeof subject !== \"string\" || subject === \"\") {\n throw new IdentityProviderError(\"ID token carries no sub claim\")\n }\n\n const displayName = [\n claims.name,\n claims.email,\n claims.preferred_username,\n ].find(\n (value): value is string => typeof value === \"string\" && value !== \"\",\n )\n const login: UpstreamLogin = {\n subject,\n claims: { ...claims },\n accessToken: tokens.access_token,\n expiresIn: tokens.expires_in ?? 300,\n ...(displayName !== undefined ? { displayName } : {}),\n ...(tokens.refresh_token !== undefined\n ? { refreshToken: tokens.refresh_token }\n : {}),\n }\n\n return login\n },\n\n async refresh(refreshToken): Promise<UpstreamTokens> {\n const { document } = await discover()\n const tokens = await postToken(\n document,\n new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n }),\n )\n\n return {\n accessToken: tokens.access_token,\n expiresIn: tokens.expires_in ?? 300,\n ...(tokens.refresh_token !== undefined\n ? { refreshToken: tokens.refresh_token }\n : {}),\n }\n },\n\n async revoke(refreshToken) {\n const { document } = await discover()\n\n if (document.revocation_endpoint === undefined) return\n\n const body = new URLSearchParams({\n token: refreshToken,\n token_type_hint: \"refresh_token\",\n })\n const headers = {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n ...clientAuth(document, body),\n }\n\n try {\n await timedFetch(document.revocation_endpoint, {\n method: \"POST\",\n headers,\n body: body.toString(),\n })\n } catch (error) {\n throw new IdentityProviderError(\n \"Could not reach the identity provider's revocation endpoint\",\n undefined,\n {\n cause: error,\n },\n )\n }\n },\n }\n}\n", "/**\n * ID token signature algorithms accepted by default: asymmetric only.\n *\n * @internal\n */\nexport const DEFAULT_ALGORITHMS = [\n \"RS256\",\n \"RS384\",\n \"RS512\",\n \"PS256\",\n \"PS384\",\n \"PS512\",\n \"ES256\",\n \"ES384\",\n \"ES512\",\n \"EdDSA\",\n]\n\n/**\n * Authorization parameters this library sets itself and never lets callers override.\n *\n * @internal\n */\nexport const RESERVED_PARAMS: ReadonlySet<string> = new Set([\n \"state\",\n \"nonce\",\n \"code_challenge\",\n \"code_challenge_method\",\n \"client_id\",\n \"redirect_uri\",\n \"response_type\",\n \"scope\",\n])\n", "import { LOOPBACK_HOSTS } from \"../constants/protocol.js\"\nimport { IdentityProviderError } from \"../errors/identity-provider-error.js\"\nimport { isRecord, isStringArray } from \"../helpers/guards.js\"\nimport type { Discovery, TokenEndpointResponse } from \"../interfaces/oidc.js\"\n\nfunction optionalString(value: unknown): value is string | undefined {\n return value === undefined || typeof value === \"string\"\n}\n\n/**\n *\n * @param value - Parsed JSON from the provider's `/.well-known/openid-configuration`.\n * @returns `true` for a usable discovery document.\n * @internal\n */\nexport function isDiscovery(value: unknown): value is Discovery {\n return (\n isRecord(value) &&\n typeof value.issuer === \"string\" &&\n typeof value.authorization_endpoint === \"string\" &&\n typeof value.token_endpoint === \"string\" &&\n typeof value.jwks_uri === \"string\" &&\n optionalString(value.revocation_endpoint) &&\n (value.token_endpoint_auth_methods_supported === undefined ||\n isStringArray(value.token_endpoint_auth_methods_supported)) &&\n (value.authorization_response_iss_parameter_supported === undefined ||\n typeof value.authorization_response_iss_parameter_supported ===\n \"boolean\") &&\n (value.code_challenge_methods_supported === undefined ||\n isStringArray(value.code_challenge_methods_supported))\n )\n}\n\n/**\n *\n * @param value - Parsed JSON from the provider's token endpoint.\n * @returns `true` for a usable token response.\n * @internal\n */\nexport function isTokenResponse(\n value: unknown,\n): value is TokenEndpointResponse {\n return (\n isRecord(value) &&\n typeof value.access_token === \"string\" &&\n value.access_token !== \"\" &&\n optionalString(value.id_token) &&\n optionalString(value.refresh_token) &&\n (value.expires_in === undefined ||\n (typeof value.expires_in === \"number\" &&\n Number.isFinite(value.expires_in)))\n )\n}\n\n/**\n *\n * @param name - Discovery field name, used in the error message (for example `token_endpoint`).\n * @param value - The endpoint URL as published by the provider.\n * @param insecure - Whether `http` is allowed for loopback hosts (`allowInsecureLocalhost`).\n * @returns The parsed URL, known to be `https` (or loopback `http` when allowed).\n * @throws {IdentityProviderError} When the value is not a URL or is not `https` (loopback `http` only when allowed).\n * @internal\n */\nexport function checkEndpoint(\n name: string,\n value: string,\n insecure: boolean,\n): URL {\n let url: URL\n\n try {\n url = new URL(value)\n } catch {\n throw new IdentityProviderError(\n `Identity provider ${name} cannot be parsed as a URL`,\n )\n }\n\n const local = url.protocol === \"http:\" && LOOPBACK_HOSTS.has(url.hostname)\n\n if (url.protocol !== \"https:\" && !(local && insecure)) {\n throw new IdentityProviderError(\n `Identity provider ${name} is not an https URL`,\n )\n }\n\n return url\n}\n", "import type { ClientRecord } from \"../interfaces/records.js\"\nimport { hasExpired } from \"../helpers/time.js\"\n\n/**\n * Clients, split so a registration flood can never push out a client people\n * are connected through:\n *\n * - `#held`: clients with at least one grant in the store, and static\n * clients. Never evicted; bounded indirectly by the grant collection.\n * - `#idle`: dynamic clients with no grant, in least-recently-used order.\n * Bounded by `max` (the least recently used is evicted, O(1)) and by the\n * idle lifetime.\n *\n * @internal\n */\nexport class ClientCollection {\n readonly #held = new Map<string, ClientRecord>()\n readonly #idle = new Map<string, { client: ClientRecord; usedAt: number }>()\n readonly #grantCounts = new Map<string, number>()\n readonly #max: number\n readonly #idleMs: number\n readonly #now: () => number\n\n /**\n *\n * @param max - Most grant-less dynamic clients held; the least recently used is evicted beyond it.\n * @param idleMs - Milliseconds a grant-less dynamic client may go unused before it is dropped.\n * @param now - Clock in milliseconds since the epoch.\n */\n constructor(max: number, idleMs: number, now: () => number) {\n this.#max = max\n this.#idleMs = idleMs\n this.#now = now\n }\n\n #toIdle(client: ClientRecord): void {\n this.#idle.delete(client.clientId)\n\n while (this.#idle.size >= this.#max) {\n const oldest = this.#idle.keys().next()\n\n if (oldest.done === true) break\n\n this.#idle.delete(oldest.value)\n }\n\n this.#idle.set(client.clientId, { client, usedAt: this.#now() })\n }\n\n /**\n *\n * @param client - Client to store; static clients and clients with grants are never evicted.\n */\n put(client: ClientRecord): void {\n this.#held.delete(client.clientId)\n this.#idle.delete(client.clientId)\n\n if (\n client.kind === \"static\" ||\n (this.#grantCounts.get(client.clientId) ?? 0) > 0\n ) {\n this.#held.set(client.clientId, client)\n } else {\n this.#toIdle(client)\n }\n }\n\n /**\n *\n * @param clientId - The `client_id` to look up; a hit counts as use of a grant-less client.\n * @returns The client, or `undefined` when unknown or idle for longer than the idle lifetime.\n */\n get(clientId: string): ClientRecord | undefined {\n const held = this.#held.get(clientId)\n\n if (held !== undefined) return held\n\n const idle = this.#idle.get(clientId)\n\n if (idle === undefined) return undefined\n\n this.#idle.delete(clientId)\n\n if (hasExpired(idle.usedAt + this.#idleMs, this.#now())) return undefined\n\n // A lookup is a use: re-insert at the most-recently-used end.\n this.#idle.set(clientId, { client: idle.client, usedAt: this.#now() })\n\n return idle.client\n }\n\n /**\n *\n * @param clientId - Client whose grant count increases; it becomes non-evictable.\n */\n grantAdded(clientId: string): void {\n this.#grantCounts.set(clientId, (this.#grantCounts.get(clientId) ?? 0) + 1)\n\n const idle = this.#idle.get(clientId)\n\n if (idle !== undefined) {\n this.#idle.delete(clientId)\n this.#held.set(clientId, idle.client)\n }\n }\n\n /**\n *\n * @param clientId - Client whose grant count decreases; at zero a dynamic client becomes evictable again.\n */\n grantRemoved(clientId: string): void {\n const count = (this.#grantCounts.get(clientId) ?? 0) - 1\n\n if (count > 0) {\n this.#grantCounts.set(clientId, count)\n\n return\n }\n\n this.#grantCounts.delete(clientId)\n\n const held = this.#held.get(clientId)\n\n if (held !== undefined && held.kind !== \"static\") {\n this.#held.delete(clientId)\n this.#toIdle(held)\n }\n }\n}\n", "import { hasExpired } from \"../helpers/time.js\"\n\n/**\n * A Map with expiry-on-read and oldest-first eviction. Eviction pops from\n * the head of the Map's insertion order: O(1) amortised.\n *\n * @internal\n */\nexport class Collection<V> {\n readonly #items = new Map<string, V>()\n readonly #max: number\n readonly #expiresAt: (value: V) => number\n readonly #now: () => number\n readonly #onRemove: (value: V) => void\n\n /**\n *\n * @param max - Most entries held; inserting beyond it evicts the oldest.\n * @param now - Clock in milliseconds since the epoch, compared with each entry's expiry.\n * @param expiresAt - Returns an entry's expiry in milliseconds since the epoch.\n * @param onRemove - Called for every entry that leaves the collection (replaced, expired, evicted or deleted).\n */\n constructor(\n max: number,\n now: () => number,\n expiresAt: (value: V) => number,\n onRemove: (value: V) => void = () => {},\n ) {\n this.#max = max\n this.#now = now\n this.#expiresAt = expiresAt\n this.#onRemove = onRemove\n }\n\n #remove(key: string, value: V): void {\n this.#items.delete(key)\n this.#onRemove(value)\n }\n\n /**\n *\n * @param key - Entry key, such as a token hash or record id.\n * @returns The entry, or `undefined` when absent or expired (an expired entry is removed).\n */\n get(key: string): V | undefined {\n const value = this.#items.get(key)\n\n if (value === undefined) return undefined\n\n if (hasExpired(this.#expiresAt(value), this.#now())) {\n this.#remove(key, value)\n\n return undefined\n }\n\n return value\n }\n\n /**\n *\n * @param key - Entry key; an existing entry under it is replaced.\n * @param value - The record to store.\n */\n set(key: string, value: V): void {\n const previous = this.#items.get(key)\n\n if (previous !== undefined) this.#remove(key, previous)\n\n while (this.#items.size >= this.#max) {\n const oldest = this.#items.entries().next()\n\n if (oldest.done === true) break\n\n this.#remove(oldest.value[0], oldest.value[1])\n }\n\n this.#items.set(key, value)\n }\n\n /**\n * Read an entry even if it has expired, without removing it.\n *\n * @param key - Entry key.\n * @returns The entry, or `undefined` when absent.\n */\n peek(key: string): V | undefined {\n return this.#items.get(key)\n }\n\n /**\n * Get and delete in one synchronous step.\n *\n * @param key - Entry key.\n * @param includeExpired - When `true`, an entry past its expiry is still returned and removed.\n * @returns The removed entry, or `undefined` when there was none to take.\n */\n take(key: string, includeExpired = false): V | undefined {\n const value = includeExpired ? this.#items.get(key) : this.get(key)\n\n if (value !== undefined) this.#remove(key, value)\n\n return value\n }\n\n /**\n *\n * @param key - Entry key; a missing key is ignored.\n */\n delete(key: string): void {\n const value = this.#items.get(key)\n\n if (value !== undefined) this.#remove(key, value)\n }\n\n /**\n * Every live (unexpired) entry.\n *\n * @returns The entries in insertion order.\n */\n values(): V[] {\n const now = this.#now()\n\n return [...this.#items.values()].filter(\n (value) => !hasExpired(this.#expiresAt(value), now),\n )\n }\n\n /**\n * Remove every entry that matches.\n *\n * @param test - Returns `true` for entries to remove.\n */\n deleteWhere(test: (value: V) => boolean): void {\n for (const [key, value] of this.#items) {\n if (test(value)) this.#remove(key, value)\n }\n }\n}\n", "import {\n DEFAULT_DYNAMIC_CLIENT_IDLE_SECONDS,\n DEFAULT_MEMORY_STORE_ENTRIES,\n} from \"../constants/defaults.js\"\nimport type { MemoryStoreOptions } from \"../interfaces/memory-store.js\"\nimport type {\n AccessTokenRecord,\n AuthorizationCodeRecord,\n ClientRecord,\n ConsentRequest,\n ConsumedAuthorizationCode,\n GrantRecord,\n GrantUpstreamUpdate,\n PendingAuthorization,\n RefreshTokenRecord,\n UsedRefreshToken,\n} from \"../interfaces/records.js\"\nimport type { OAuthStore } from \"../interfaces/store.js\"\nimport { ClientCollection } from \"./client-collection.js\"\nimport { Collection } from \"./collection.js\"\nimport { checkedClock } from \"../helpers/time.js\"\n\nconst expiry = (value: { readonly expiresAt: number }) => value.expiresAt\n\n/**\n * Run synchronous store logic and report its outcome as a promise.\n *\n * @param work - Synchronous store operation; anything it throws becomes the rejection.\n * @returns Resolves with `work`'s result, or rejects with what it threw.\n */\nfunction settle<T>(work: () => T): Promise<T> {\n try {\n return Promise.resolve(work())\n } catch (error) {\n return Promise.reject(\n error instanceof Error ? error : new Error(\"Memory store failure\"),\n )\n }\n}\n\n/**\n * An in-memory {@link OAuthStore}.\n *\n * **Single process, development and tests only.** Everything is lost on\n * restart (every client must register again and every person must\n * reconnect), and two processes never see each other's state. Its atomic\n * operations are correct only because Node.js runs them on one thread with no\n * `await` between the read and the write.\n *\n * @param options - Capacity, dynamic-client idle lifetime and clock.\n * @returns An `OAuthStore` held in this process's memory.\n * @example\n * ```ts\n * import { createMemoryStore } from \"@gydr-ai/mcp-oauth\"\n *\n * // Development and tests only: state is lost on restart.\n * export const store = createMemoryStore({ maxEntriesPerCollection: 10_000 })\n * ```\n */\nexport function createMemoryStore(\n options: MemoryStoreOptions = {},\n): OAuthStore {\n const max = options.maxEntriesPerCollection ?? DEFAULT_MEMORY_STORE_ENTRIES\n const now = checkedClock(options.now ?? Date.now)\n const clients = new ClientCollection(\n max,\n (options.dynamicClientIdleSeconds ?? DEFAULT_DYNAMIC_CLIENT_IDLE_SECONDS) *\n 1000,\n now,\n )\n const metadata = new Collection<{\n client: ClientRecord\n expiresAt: number\n }>(max, now, expiry)\n const pending = new Collection<PendingAuthorization>(max, now, expiry)\n const consents = new Collection<ConsentRequest>(max, now, expiry)\n const outcomes = new Collection<{\n sealedLocation: string\n expiresAt: number\n }>(max, now, expiry)\n const codes = new Collection<{\n record: AuthorizationCodeRecord\n consumed: boolean\n expiresAt: number\n }>(max, now, expiry)\n const grants = new Collection<GrantRecord>(max, now, expiry, (grant) => {\n clients.grantRemoved(grant.clientId)\n })\n const access = new Collection<AccessTokenRecord>(max, now, expiry)\n const refresh = new Collection<RefreshTokenRecord>(max, now, expiry)\n\n return {\n getClient(clientId) {\n return settle(() => {\n return clients.get(clientId)\n })\n },\n putClient(client) {\n return settle(() => {\n clients.put(client)\n })\n },\n getCachedClientMetadata(url) {\n return settle(() => {\n return metadata.get(url)?.client\n })\n },\n putCachedClientMetadata(url, client, expiresAt) {\n return settle(() => {\n metadata.set(url, { client, expiresAt })\n })\n },\n putPendingAuthorization(record) {\n return settle(() => {\n pending.set(record.id, record)\n })\n },\n getPendingAuthorization(id) {\n return settle(() => {\n return pending.get(id)\n })\n },\n consumePendingAuthorization(id) {\n return settle(() => {\n return pending.take(id)\n })\n },\n putConsentRequest(record) {\n return settle(() => {\n consents.set(record.id, record)\n })\n },\n getConsentRequest(id) {\n return settle(() => consents.peek(id))\n },\n putConsentOutcome(key, sealedLocation, expiresAt) {\n return settle(() => {\n outcomes.set(key, { sealedLocation, expiresAt })\n })\n },\n getConsentOutcome(key) {\n return settle(() => outcomes.get(key)?.sealedLocation)\n },\n listGrantsBySubject(subject) {\n return settle(() => grants.values().filter((g) => g.subject === subject))\n },\n consumeConsentRequest(id) {\n return settle(() => {\n // Returned even when expired, so its upstream token can be revoked.\n return consents.take(id, true)\n })\n },\n putAuthorizationCode(record) {\n return settle(() => {\n codes.set(record.codeHash, {\n record,\n consumed: false,\n expiresAt: record.expiresAt,\n })\n })\n },\n getAuthorizationCode(codeHash) {\n return settle(() => {\n const entry = codes.get(codeHash)\n\n return entry === undefined\n ? undefined\n : { record: entry.record, consumed: entry.consumed }\n })\n },\n consumeAuthorizationCode(codeHash) {\n return settle(() => {\n // The consumed record stays until it expires, so a replay is seen.\n const entry = codes.get(codeHash)\n\n if (entry === undefined) return undefined\n\n const result: ConsumedAuthorizationCode = {\n record: entry.record,\n firstUse: !entry.consumed,\n }\n\n codes.set(codeHash, { ...entry, consumed: true })\n\n return result\n })\n },\n putGrant(grant) {\n return settle(() => {\n // Count first: replacing or evicting a grant reports its removal\n // through the collection callback.\n clients.grantAdded(grant.clientId)\n grants.set(grant.grantId, grant)\n })\n },\n getGrant(grantId) {\n return settle(() => {\n return grants.get(grantId)\n })\n },\n updateGrant(grantId, update: GrantUpstreamUpdate) {\n return settle(() => {\n const grant = grants.get(grantId)\n\n if (grant === undefined) return false\n\n clients.grantAdded(grant.clientId)\n grants.set(grantId, { ...grant, ...update })\n\n return true\n })\n },\n deleteGrant(grantId) {\n return settle(() => {\n const removed = grants.take(grantId, true)\n\n access.deleteWhere((token) => token.grantId === grantId)\n refresh.deleteWhere((token) => token.grantId === grantId)\n codes.deleteWhere((code) => code.record.grantId === grantId)\n\n return removed\n })\n },\n putAccessToken(token) {\n return settle(() => {\n access.set(token.tokenHash, token)\n })\n },\n getAccessToken(tokenHash) {\n return settle(() => {\n return access.get(tokenHash)\n })\n },\n deleteAccessToken(tokenHash) {\n return settle(() => {\n access.delete(tokenHash)\n })\n },\n putRefreshToken(token) {\n return settle(() => {\n refresh.set(token.tokenHash, token)\n })\n },\n getRefreshToken(tokenHash) {\n return settle(() => {\n return refresh.get(tokenHash)\n })\n },\n useRefreshToken(tokenHash, usedAt): Promise<UsedRefreshToken | undefined> {\n return settle(() => {\n const token = refresh.get(tokenHash)\n\n if (token === undefined) return undefined\n\n if (token.usedAt !== undefined) {\n return { record: token, previousUsedAt: token.usedAt }\n }\n\n const marked = { ...token, usedAt }\n\n refresh.set(tokenHash, marked)\n\n return { record: marked }\n })\n },\n }\n}\n"],
5
- "mappings": "iFAiBO,IAAMA,EAAN,cAAiC,KAAM,CAjB9C,MAiB8C,CAAAC,EAAA,2BAI5C,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,oBACd,CACF,ECbO,SAASC,EACdC,EACAC,EACAC,EACS,CACT,GAAIF,IAAU,OAAW,OAGzB,IAAIG,EAEJ,GAAI,CACFA,EAAO,KAAK,UAAUH,CAAK,CAC7B,MAAQ,CACN,MAAM,IAAI,UAAU,GAAGE,CAAK,8CAA8C,CAC5E,CAEA,GAAI,OAAOC,GAAS,SAClB,MAAM,IAAI,UAAU,GAAGD,CAAK,8CAA8C,EAG5E,GAAI,OAAO,WAAWC,EAAM,MAAM,EAAIF,EACpC,MAAM,IAAI,UAAU,GAAGC,CAAK,uBAAuBD,CAAK,QAAQ,EAKlE,OAFsB,KAAK,MAAME,CAAI,CAGvC,CA3BgBC,EAAAL,EAAA,YCPT,IAAMM,GAAoB,CAC/B,YAAa,KACb,aAAc,QACd,MAAO,OACP,kBAAmB,GACnB,qBAAsB,IACtB,oBAAqB,KACrB,sBAAuB,EACzB,EASaC,GAAkB,CAC7B,YAAa,CAAE,IAAK,EAAG,IAAK,KAAO,EACnC,aAAc,CAAE,IAAK,EAAG,IAAK,MAAW,EACxC,MAAO,CAAE,IAAK,EAAG,IAAK,MAAW,EACjC,kBAAmB,CAAE,IAAK,EAAG,IAAK,GAAI,EACtC,qBAAsB,CAAE,IAAK,EAAG,IAAK,IAAK,EAC1C,oBAAqB,CAAE,IAAK,EAAG,IAAK,KAAO,EAC3C,sBAAuB,CAAE,IAAK,EAAG,IAAK,IAAK,CAC7C,EAOaC,GAAiB,CAC5B,iBAAkB,IAClB,aAAc,EACd,kBAAmB,KACnB,YAAa,KACb,YAAa,KACb,kBAAmB,GAAK,KACxB,UAAW,GAAK,KAChB,oBAAqB,EAAI,KACzB,wBAAyB,IACzB,uBAAwB,IACxB,kBAAmB,KACnB,oBAAqB,KACrB,eAAgB,IAClB,EAOaC,GAAoC,CAAC,KAAK,EAO1CC,GAAsC,GAOtCC,GAAkC,IAOlCC,GAAwB,mBAOxBC,GAA+B,IAO/BC,GAAsC,QAOtCC,GAA0B,IAO1BC,GAAwB,IAOxBC,GAAoC,MAOpCC,GAAyB,KAOzBC,GAA2B,IAQ3BC,GAAiC,GAOjCC,GAA6B,GC1InC,IAAMC,GACX,wCAOWC,GACX,0CAOWC,GAAsC,IAAI,IAAI,CACzD,YACA,QACA,WACF,CAAC,EAOYC,EAAiB,CAC5B,GAAI,cACJ,KAAM,aACN,SAAU,QACZ,EAOaC,GAAkB,UAOlBC,GAAkB,oCAOlBC,GAAkB,mBCrD/B,IAAMC,GAA6C,CACjD,gBAAiB,WACjB,OAAQ,UACV,EAGMC,GAAyC,CAC7C,8BAA+B,IAC/B,gCAAiC,kBACnC,EAqBO,SAASC,GACdC,EACAC,EACAC,EAAuB,CAAC,EACd,CACV,IAAMC,EAAU,IAAI,QAAQ,CAC1B,eAAgBC,GAChB,GAAIF,EAAQ,UACR,CAAE,gBAAiB,mBAAmBG,EAAwB,EAAG,EACjER,GACJ,GAAIK,EAAQ,KAAOJ,GAAO,CAAC,EAC3B,GAAGI,EAAQ,OACb,CAAC,EAED,OAAO,IAAI,SAAS,KAAK,UAAUD,CAAI,EAAG,CAAE,OAAAD,EAAQ,QAAAG,CAAQ,CAAC,CAC/D,CAfgBG,EAAAP,GAAA,QA2BT,SAASQ,EACdP,EACAQ,EACAC,EACAC,EACU,CACV,OAAOX,GACLC,EACAS,IAAgB,OACZ,CAAE,MAAAD,CAAM,EACR,CAAE,MAAAA,EAAO,kBAAmBC,CAAY,EAC5C,CACE,KAAM,GACN,GAAIC,IAAc,OACd,CAAE,QAAS,CAAE,mBAAoBA,CAAU,CAAE,EAC7C,CAAC,CACP,CACF,CACF,CAlBgBJ,EAAAC,EAAA,cA2BT,SAASI,GAAUC,EAA2B,CACnD,OAAO,IAAI,SAAS,KAAM,CACxB,OAAQ,IACR,QAAS,CACP,GAAGd,GACH,+BAAgCc,EAChC,+BACE,oDACF,yBAA0B,KAC5B,CACF,CAAC,CACH,CAXgBN,EAAAK,GAAA,aAoBT,SAASE,GAAiBC,EAAyB,CACxD,OAAO,IAAI,SAAS,KAAM,CACxB,OAAQ,IACR,QAAS,CAAE,MAAOA,EAAO,GAAGjB,EAAS,CACvC,CAAC,CACH,CALgBS,EAAAO,GAAA,oBAiBT,SAASE,GACdC,EAAuC,CAAC,EACxCC,EAAkC,CAAC,EACX,CACxB,IAAMC,EAAa,CAAC,SAAU,GAAGF,CAAiB,EAAE,KAAK,GAAG,EAM5D,MAAO,CACL,eAAgB,2BAChB,0BAA2B,sBAN3BC,EAAa,SAAW,EACpB,GACA,eAAeA,EAAa,IAAKE,GAAM,IAAIA,CAAC,GAAG,EAAE,KAAK,GAAG,CAAC,GAIN,0DAA0DD,CAAU,4CAC5H,kBAAmB,OACnB,yBAA0B,UAC1B,kBAAmB,cACnB,GAAGrB,EACL,CACF,CAlBgBS,EAAAS,GAAA,eAuCT,SAASK,GACdpB,EACAC,EACAC,EAAuB,CAAC,EACd,CACV,IAAMC,EAAU,IAAI,QAClBY,GAAYb,EAAQ,kBAAmBA,EAAQ,YAAY,CAC7D,EAEA,QAAWmB,KAAUnB,EAAQ,YAAc,CAAC,EAC1CC,EAAQ,OAAO,aAAckB,CAAM,EAGrC,OAAO,IAAI,SAASpB,EAAM,CAAE,OAAAD,EAAQ,QAAAG,CAAQ,CAAC,CAC/C,CAdgBG,EAAAc,GAAA,QA0BT,SAASE,GACdC,EACAC,EAAgC,CAAC,EACjCxB,EAAoB,IACV,CACV,IAAMG,EAAU,IAAI,QAAQ,CAC1B,SAAUoB,EACV,kBAAmB,cACnB,GAAG1B,EACL,CAAC,EAED,QAAWwB,KAAUG,EAAYrB,EAAQ,OAAO,aAAckB,CAAM,EAEpE,OAAO,IAAI,SAAS,KAAM,CAAE,OAAArB,EAAQ,QAAAG,CAAQ,CAAC,CAC/C,CAdgBG,EAAAgB,GAAA,YC9LhB,OAAS,cAAAG,GAAY,eAAAC,GAAa,mBAAAC,OAAuB,cAGzD,IAAMC,GAAe,GAGfC,GAAmB,GAQlB,SAASC,GAAsB,CACpC,OAAOC,GAAYH,EAAY,EAAE,SAAS,WAAW,CACvD,CAFgBI,EAAAF,EAAA,eAWT,SAASG,EAAUC,EAAuB,CAC/C,OAAOC,GAAW,QAAQ,EAAE,OAAOD,EAAO,MAAM,EAAE,OAAO,KAAK,CAChE,CAFgBF,EAAAC,EAAA,aAWT,SAASG,GAAcC,EAA0B,CACtD,OAAOF,GAAW,QAAQ,EAAE,OAAOE,EAAU,OAAO,EAAE,OAAO,WAAW,CAC1E,CAFgBL,EAAAI,GAAA,iBAYT,SAASE,GAAkBC,EAAWC,EAAoB,CAC/D,IAAMC,EAAO,OAAO,KAAKF,EAAG,MAAM,EAC5BG,EAAQ,OAAO,KAAKF,EAAG,MAAM,EAEnC,OAAOC,EAAK,SAAWC,EAAM,QAAUC,GAAgBF,EAAMC,CAAK,CACpE,CALgBV,EAAAM,GAAA,qBAOhB,IAAMM,GAAkB,KAClBC,GAAiB,OAEvB,SAASC,GAAaC,EAAe,CACnC,OAAQC,GAA0B,CAChC,QAASC,EAAI,EAAGA,EAAID,EAAK,OAAQC,GAAK,EAAG,CACvC,IAAMC,EAAIF,EAAK,WAAWC,CAAC,EAI3B,GAAI,EAFDC,GAAK,IAAMA,GAAK,IAAQA,GAAK,IAAMA,GAAK,IAAQA,GAAK,IAAMA,GAAK,MAE9C,CAACH,EAAM,SAASC,EAAK,OAAOC,CAAC,CAAC,EAAG,MAAO,EAC/D,CAEA,MAAO,EACT,CACF,CAZSjB,EAAAc,GAAA,gBAcT,IAAMK,GAAgBL,GAAaF,EAAe,EAC5CQ,GAAoBN,GAAaD,EAAc,EAS9C,SAASQ,GAAqBhB,EAA2B,CAC9D,OACEA,EAAS,QAAU,IACnBA,EAAS,QAAU,KACnBe,GAAkBf,CAAQ,CAE9B,CANgBL,EAAAqB,GAAA,wBAeT,SAASC,GAAsBC,EAA4B,CAChE,OAAOA,EAAU,SAAW1B,IAAoBsB,GAAcI,CAAS,CACzE,CAFgBvB,EAAAsB,GAAA,yBAWT,SAASE,EAActB,EAAwB,CACpD,OAAOA,EAAM,SAAWL,IAAoBsB,GAAcjB,CAAK,CACjE,CAFgBF,EAAAwB,EAAA,iBAYT,SAASC,GAAYpB,EAAkBkB,EAA4B,CACxE,OACEF,GAAqBhB,CAAQ,GAC7BC,GAAkBF,GAAcC,CAAQ,EAAGkB,CAAS,CAExD,CALgBvB,EAAAyB,GAAA,eClHhB,SAASC,GAAcC,EAAa,CAClC,GAAI,OAAOA,GAAU,UAAYA,IAAU,MAAQ,CAAC,OAAO,SAASA,CAAK,EAAG,CAC1E,OAAO,OAAOA,CAAK,EAEnB,QAAWC,KAAO,QAAQ,QAAQD,CAAK,EACrCD,GAAW,QAAQ,IAAIC,EAAOC,CAAG,CAAC,CAEtC,CAEA,OAAOD,CACT,CAVSE,EAAAH,GAAA,cAqBF,SAASI,EAAcH,EAAa,CACzC,OAAOD,GAAW,gBAAgBC,CAAK,CAAC,CAC1C,CAFgBE,EAAAC,EAAA,cAYT,SAASC,GAAcJ,EAAa,CACzC,OAAOD,GAAWC,CAAK,CACzB,CAFgBE,EAAAE,GAAA,cCpBhB,eAAsBC,GACpBC,EACAC,EACkB,CAClB,IAAMC,EAAOF,EAAQ,QAAQ,gBAE7B,GAAIE,IAAS,OAAW,OAExB,IAAMC,EAAS,MAAMD,EACnBE,EAAW,CACT,QAASH,EAAM,QACf,SAAUA,EAAM,SAChB,QAASA,EAAM,QACf,OAAQ,CAAC,GAAGA,EAAM,MAAM,EACxB,SAAUA,EAAM,SAChB,UAAWA,EAAM,UACjB,GAAIA,EAAM,OAAS,OAAY,CAAE,KAAMA,EAAM,IAAK,EAAI,CAAC,CACzD,CAAC,CACH,EAEA,OAAOI,EACLF,EACAH,EAAQ,QAAQ,OAAO,kBACvB,iBACF,CACF,CAzBsBM,EAAAP,GAAA,mBCZf,SAASQ,EAASC,EAAkD,CACzE,OAAO,OAAOA,GAAU,UAAYA,IAAU,MAAQ,CAAC,MAAM,QAAQA,CAAK,CAC5E,CAFgBC,EAAAF,EAAA,YAWT,SAASG,EAAcF,EAAmC,CAC/D,OAAO,MAAM,QAAQA,CAAK,GAAKA,EAAM,MAAOG,GAAS,OAAOA,GAAS,QAAQ,CAC/E,CAFgBF,EAAAC,EAAA,iBCNT,SAASE,GAAaC,EAAkC,CAG7D,OAAOC,EAAW,CAChB,QAASD,EAAM,QACf,SAAUA,EAAM,SAChB,QAASA,EAAM,QACf,OAAQA,EAAM,OACd,SAAUA,EAAM,SAChB,UAAWA,EAAM,UACjB,UAAWA,EAAM,UACjB,GAAIA,EAAM,aAAe,OAAY,CAAE,WAAYA,EAAM,UAAW,EAAI,CAAC,EACzE,GAAIA,EAAM,cAAgB,OACtB,CAAE,YAAaA,EAAM,WAAY,EACjC,CAAC,EACL,GAAIA,EAAM,eAAiB,OACvB,CAAE,aAAcA,EAAM,YAAa,EACnC,CAAC,EACL,GAAIA,EAAM,OAAS,OAAY,CAAE,KAAMA,EAAM,IAAK,EAAI,CAAC,CACzD,CAAC,CACH,CApBgBE,EAAAH,GAAA,gBCFT,SAASI,EAAWC,EAAmBC,EAAsB,CAClE,MAAO,EACL,OAAO,SAASD,CAAS,GACzB,OAAO,SAASC,CAAG,GACnBD,EAAYC,EAEhB,CANgBC,EAAAH,EAAA,cAiBT,SAASI,GAAaC,EAAmC,CAC9D,MAAO,IAAM,CACX,IAAMC,EAAiBD,EAAM,EAE7B,GAAI,OAAOC,GAAU,UAAY,CAAC,OAAO,SAASA,CAAK,EACrD,MAAM,IAAI,UAAU,iDAAiD,EAGvE,OAAOA,CACT,CACF,CAVgBH,EAAAC,GAAA,gBCNhB,eAAsBG,GACpBC,EACAC,EACAC,EACAC,EAAmCD,EACX,CACxB,GAAM,CAAE,QAAAE,CAAQ,EAAIJ,EACdK,EAAMD,EAAQ,IAAI,EAClBE,EAAcC,EAAY,EAC1BC,EAAkB,KAAK,IAC3BH,EAAMD,EAAQ,iBACdH,EAAM,SACR,EAEA,MAAMG,EAAQ,MAAM,eAAe,CACjC,UAAWK,EAAUH,CAAW,EAChC,QAASL,EAAM,QACf,SAAUA,EAAM,SAChB,OAAAC,EACA,SAAUD,EAAM,SAChB,UAAWO,CACb,CAAC,EAED,IAAME,EAAW,CACf,aAAcJ,EACd,WAAY,SACZ,WAAY,KAAK,IAAI,EAAG,KAAK,OAAOE,EAAkBH,GAAO,GAAI,CAAC,EAClE,MAAOH,EAAO,KAAK,GAAG,CACxB,EAEA,GAAID,EAAM,eAAiB,GAAO,OAAOS,EAEzC,IAAMC,EAAeJ,EAAY,EAEjC,aAAMH,EAAQ,MAAM,gBAAgB,CAClC,UAAWK,EAAUE,CAAY,EACjC,QAASV,EAAM,QACf,SAAUA,EAAM,SAChB,OAAQE,EACR,SAAUF,EAAM,SAChB,UAAW,KAAK,IAAII,EAAMD,EAAQ,kBAAmBH,EAAM,SAAS,CACtE,CAAC,EAEM,CAAE,GAAGS,EAAU,cAAeC,CAAa,CACpD,CA5CsBC,EAAAb,GAAA,kBAuDtB,eAAsBc,GACpBb,EACAc,EACAC,EACkC,CAClC,IAAMd,EAAQ,MAAMD,EAAQ,QAAQ,MAAM,SAASc,CAAO,EAE1D,GACE,EAAAb,IAAU,QACVA,EAAM,WAAac,GACnBC,EAAWf,EAAM,UAAWD,EAAQ,QAAQ,IAAI,CAAC,GAKnD,OAAOC,CACT,CAhBsBW,EAAAC,GAAA,aAyBtB,eAAsBI,GACpBjB,EACAW,EACe,CACf,IAAMO,EAAWlB,EAAQ,QAAQ,iBAEjC,GAAIkB,EAAS,SAAW,OAExB,GAAI,CACF,MAAMA,EAAS,OAAOP,CAAY,CACpC,MAAQ,CAER,CACF,CAbsBC,EAAAK,GAAA,uBAuBtB,eAAsBE,GACpBnB,EACAoB,EACAN,EACe,CACf,GACEM,IAAW,QACXpB,EAAQ,QAAQ,iBAAiB,SAAW,OAE5C,OAGF,IAAIqB,EAEJ,GAAI,CACFA,EAAY,MAAMrB,EAAQ,QAAQ,OAAO,KAAKoB,EAAQN,CAAO,CAC/D,MAAQ,CACN,MACF,CAEA,MAAMG,GAAoBjB,EAASqB,CAAS,CAC9C,CArBsBT,EAAAO,GAAA,6BAwCtB,eAAsBG,GACpBtB,EACAc,EACAS,EACAC,EACe,CACf,GAAM,CAAE,QAAApB,CAAQ,EAAIJ,EACdC,EAAQ,MAAMG,EAAQ,MAAM,SAASU,CAAO,EAI9Cb,IAAU,QACZ,MAAMD,EAAQ,KAAK,CACjB,KAAM,gBACN,QAAAc,EACA,OAAAS,EACA,MAAOE,GAAaxB,CAAK,CAC3B,CAAC,EAGHuB,EAAWV,CAAO,EAGlB,IAAMY,EAAS,MAAMtB,EAAQ,MAAM,SAASU,CAAO,EAC7Ca,EAAmB,MAAMvB,EAAQ,MAAM,YAAYU,CAAO,EAC1DM,EACJQ,EAASD,CAAO,GAAKA,EAAQ,UAAYb,EACrCa,EAAQ,4BACPD,GAAUzB,IAAQ,2BAErBmB,IAAW,QAAa,OAAOA,GAAW,UAE9C,MAAMD,GAA0BnB,EAASoB,EAAQN,CAAO,CAC1D,CAjCsBF,EAAAU,GAAA,mBCtJtB,SAASO,GAAOC,EAAuB,CACrC,MAAO,IAAIA,EAAM,QAAQ,SAAU,MAAM,EAAE,QAAQ,UAAW,GAAG,CAAC,GACpE,CAFSC,EAAAF,GAAA,UAUT,SAASG,GAAgBC,EAAqC,CAC5D,OAAQA,EAAO,CACb,IAAK,qBACH,MAAO,KACT,IAAK,kBACH,MAAO,KACT,IAAK,gBACL,KAAK,OACH,MAAO,IACX,CACF,CAVSF,EAAAC,GAAA,mBAsBF,SAASE,GACdC,EACAF,EAAqB,CAAC,EACZ,CACV,GAAM,CAAE,QAAAG,CAAQ,EAAID,EACdE,EAAS,CAAC,qBAAqBR,GAAOO,EAAQ,mBAAmB,CAAC,EAAE,EACpEE,EAAQL,EAAM,OAASG,EAAQ,cAEjCH,EAAM,QAAU,QAAWI,EAAO,KAAK,SAASR,GAAOI,EAAM,KAAK,CAAC,EAAE,EAErEA,EAAM,cAAgB,QACxBI,EAAO,KAAK,qBAAqBR,GAAOI,EAAM,WAAW,CAAC,EAAE,EAG9DI,EAAO,KAAK,SAASR,GAAOS,EAAM,KAAK,GAAG,CAAC,CAAC,EAAE,EAE9C,IAAMC,EAASP,GAAgBC,EAAM,KAAK,EAE1C,OAAO,IAAI,SACT,KAAK,UAAU,CACb,MAAOA,EAAM,OAAS,eACtB,GAAIA,EAAM,cAAgB,OACtB,CAAE,kBAAmBA,EAAM,WAAY,EACvC,CAAC,CACP,CAAC,EACD,CACE,OAAAM,EACA,QAAS,CACP,eAAgB,mBAChB,gBAAiB,WACjB,mBAAoB,UAAUF,EAAO,KAAK,IAAI,CAAC,GAC/C,8BAA+B,IAC/B,gCAAiC,kBACnC,CACF,CACF,CACF,CApCgBN,EAAAG,GAAA,mBA2ChB,SAASM,IAA0B,CACjC,OAAO,IAAI,SAAS,KAAK,UAAU,CAAE,MAAO,cAAe,CAAC,EAAG,CAC7D,OAAQ,IACR,QAAS,CACP,eAAgB,mBAChB,gBAAiB,UACnB,CACF,CAAC,CACH,CARST,EAAAS,GAAA,iBAkBT,SAASC,GAAYC,EAA6C,CAChE,IAAMC,EAASD,EAAQ,QAAQ,IAAI,eAAe,EAElD,GAAIC,IAAW,KAAM,OAAO,KAE5B,IAAMC,EAAUD,EAAO,KAAK,EACtBE,EAAQD,EAAQ,QAAQ,GAAG,EAKjC,IAJeC,IAAU,GAAKD,EAAUA,EAAQ,MAAM,EAAGC,CAAK,GAInD,YAAY,IAAM,SAAU,OAAO,KAE9C,IAAMC,EAAQD,IAAU,GAAK,GAAKD,EAAQ,MAAMC,EAAQ,CAAC,EAAE,KAAK,EAEhE,OAAOC,IAAU,GAAK,OAAYA,CACpC,CAhBSf,EAAAU,GAAA,eAgCT,eAAsBM,GACpBZ,EACAO,EACAM,EACqB,CACrB,GAAM,CAAE,QAAAZ,CAAQ,EAAID,EACdW,EAAQL,GAAYC,CAAO,EAEjC,GAAII,IAAU,KACZ,MAAO,CAAE,GAAI,GAAO,SAAUZ,GAAgBC,CAAO,CAAE,EAGzD,IAAMc,EAAUlB,EAACmB,IAAqC,CACpD,GAAI,GACJ,SAAUhB,GAAgBC,EAAS,CAAE,MAAO,gBAAiB,YAAAe,CAAY,CAAC,CAC5E,GAHgB,WAKhB,GAAIJ,IAAU,QAAa,CAACK,EAAcL,CAAK,EAC7C,OAAOG,EAAQ,uDAAuD,EAGxE,IAAMG,EAAM,MAAMhB,EAAQ,MAAM,eAAeiB,EAAUP,CAAK,CAAC,EAE/D,GAAIM,IAAQ,QAAaE,EAAWF,EAAI,UAAWhB,EAAQ,IAAI,CAAC,EAC9D,OAAOa,EAAQ,8CAA8C,EAG/D,GAAIG,EAAI,WAAahB,EAAQ,SAC3B,OAAOa,EAAQ,8CAA8C,EAG/D,IAAMM,EAAQ,MAAMC,GAAUrB,EAASiB,EAAI,QAASA,EAAI,QAAQ,EAEhE,GAAIG,IAAU,OACZ,OAAON,EAAQ,6CAA6C,EAG9D,IAAIQ,EAEJ,GAAI,CACFA,EAAe,MAAMC,GAAgBvB,EAASoB,CAAK,CACrD,MAAQ,CAGN,aAAMpB,EAAQ,KAAK,CAAE,KAAM,uBAAwB,QAASoB,EAAM,OAAQ,CAAC,EAEpE,CAAE,GAAI,GAAO,SAAUf,GAAc,CAAE,CAChD,CAEA,MAAO,CACL,GAAI,GACJ,QAASe,EAAM,QACf,QAASA,EAAM,QACf,SAAUA,EAAM,SAChB,OAAQH,EAAI,OACZ,QAASK,EACT,uBAAwB1B,EAACK,GACvBY,EAASO,EAAM,QAASnB,GAAS,eAAiB,EAAI,EADhC,yBAE1B,CACF,CA3DsBL,EAAAgB,GAAA,uBCpFf,IAAMY,GAAgE,CAC3E,aAAc,CACZ,OAAQ,IACR,MAAO,YACP,QACE,8HACJ,EACA,mBAAoB,CAClB,OAAQ,IACR,MAAO,iBACP,QACE,gGACJ,EACA,eAAgB,CACd,OAAQ,IACR,MAAO,6BACP,QACE,+GACJ,EACA,4BAA6B,CAC3B,OAAQ,IACR,MAAO,4BACP,QACE,0HACJ,EACA,gBAAiB,CACf,OAAQ,IACR,MAAO,qBACP,QACE,6GACJ,EACA,0BAA2B,CACzB,OAAQ,IACR,MAAO,sBACP,QACE,yEACJ,EACA,eAAgB,CACd,OAAQ,IACR,MAAO,gBACP,QACE,6EACJ,EACA,cAAe,CACb,OAAQ,IACR,MAAO,0BACP,QACE,qEACJ,EACA,iBAAkB,CAChB,OAAQ,IACR,MAAO,kBACP,QACE,kGACJ,EACA,cAAe,CACb,OAAQ,IACR,MAAO,gBACP,QAAS,mDACX,EACA,WAAY,CACV,OAAQ,IACR,MAAO,yBACP,QACE,mKACJ,EACA,mBAAoB,CAClB,OAAQ,IACR,MAAO,qBACP,QACE,0EACJ,EACA,iBAAkB,CAChB,OAAQ,IACR,MAAO,oBACP,QACE,mHACJ,EACA,sBAAuB,CACrB,OAAQ,IACR,MAAO,wBACP,QACE,iGACJ,EACA,sBAAuB,CACrB,OAAQ,IACR,MAAO,uBACP,QAAS,kEACX,EACA,oBAAqB,CACnB,OAAQ,IACR,MAAO,qBACP,QACE,6KACJ,EACA,uBAAwB,CACtB,OAAQ,IACR,MAAO,wBACP,QAAS,8DACX,EACA,aAAc,CACZ,OAAQ,IACR,MAAO,qBACP,QACE,wGACJ,CACF,ECpJO,SAASC,GACdC,EACAC,EACQ,CACR,IAAMC,EAASF,EAAQ,cAAcC,CAAO,EAE5C,GAAIC,IAAW,QAAaA,IAAW,GAAI,OAAOA,EAElD,GAAI,CAACF,EAAQ,WAAY,MAAO,UAGhC,IAAMG,GADYF,EAAQ,QAAQ,IAAI,iBAAiB,GAC5B,IACxB,MAAM,GAAG,EACT,IAAKG,GAAQA,EAAI,KAAK,CAAC,EACvB,OAAQA,GAAQA,IAAQ,EAAE,EAE7B,OAAOD,EAAKA,EAAK,OAAS,CAAC,GAAK,SAClC,CAjBgBE,EAAAN,GAAA,YCThB,IAAMO,GAAc,wCAUb,SAASC,GAAaC,EAAiC,CAC5D,GAAIA,IAAW,KAAM,MAAO,CAAC,EAE7B,IAAMC,EAAsD,CAAC,EAE7D,OAAW,CAACC,EAAOC,CAAI,IAAKH,EAAO,MAAM,GAAG,EAAE,QAAQ,EAAG,CACvD,GAAM,CAACI,EAAS,GAAI,GAAGC,CAAM,EAAIF,EAAK,KAAK,EAAE,MAAM,GAAG,EAChDG,EAAMF,EAAO,KAAK,EAClBG,EAASF,EAAO,IAAKG,GAAMA,EAAE,KAAK,CAAC,EAAE,KAAMA,GAAMA,EAAE,WAAW,IAAI,CAAC,EACnEC,EAAIF,IAAW,OAAY,EAAI,OAAOA,EAAO,MAAM,CAAC,CAAC,EAEvDT,GAAY,KAAKQ,CAAG,GAAK,OAAO,SAASG,CAAC,GAAKA,EAAI,GAAKA,GAAK,GAC/DR,EAAO,KAAK,CAAE,IAAAK,EAAK,EAAAG,EAAG,MAAAP,CAAM,CAAC,CAEjC,CAEA,OAAAD,EAAO,KAAK,CAACS,EAAGC,IAAMA,EAAE,EAAID,EAAE,GAAKA,EAAE,MAAQC,EAAE,KAAK,EAE7C,CAAC,GAAG,IAAI,IAAIV,EAAO,IAAKW,GAAUA,EAAM,GAAG,CAAC,CAAC,EAAE,MAAM,EAAG,EAAY,CAC7E,CAnBgBC,EAAAd,GAAA,gBCVhB,IAAMe,GAAiB,0BAuBvB,eAAsBC,GACpBC,EACwB,CACxB,IAAIC,EAEJ,GAAI,CAEFA,EAAU,MAAMD,EAAM,CACxB,MAAQ,CACN,MAAO,CAAE,QAAS,EAAM,CAC1B,CAEA,OAAOE,GAAMD,CAAO,CACtB,CAbsBE,EAAAJ,GAAA,UAsBf,SAASG,GAAMD,EAAiC,CACrD,GAAIA,IAAY,GAAM,MAAO,CAAE,QAAS,EAAK,EAE7C,GAAI,CAACG,EAASH,CAAO,EAAG,MAAO,CAAE,QAAS,EAAM,EAEhD,GAAIA,EAAQ,QAAU,GAAM,MAAO,CAAE,QAAS,EAAK,EAEnD,IAAMI,EAASJ,EAAQ,OAEvB,OAAO,OAAOI,GAAW,UAAYP,GAAe,KAAKO,CAAM,EAC3D,CAAE,QAAS,GAAO,OAAAA,CAAO,EACzB,CAAE,QAAS,EAAM,CACvB,CAZgBF,EAAAD,GAAA,SAqBhB,eAAsBI,GACpBN,EACkB,CAClB,OAAQ,MAAMD,GAAOC,CAAK,GAAG,OAC/B,CAJsBG,EAAAG,GAAA,WCtDf,SAASC,GAAcC,EAA0C,CACtE,IAAMC,EAA+BC,EAAA,MAAOC,GAAU,CACpD,GAAKH,EAAQ,QAEb,GAAI,CACF,MAAMA,EAAQ,QAAQG,CAAK,CAC7B,MAAQ,CAER,CACF,EARqC,QAUrC,MAAO,CACL,QAAAH,EACA,KAAM,CAAE,SAAU,IAAI,IAAO,SAAU,IAAI,GAAM,EACjD,KAAAC,EACA,MAAM,QAAQG,EAASC,EAAO,CAC5B,GAAI,CAACL,EAAQ,UAAW,MAAO,GAE/B,IAAMM,EAAUN,EAAQ,UAGlBO,EAAK,MAAMC,GAAQ,IAAMF,EAAQG,GAAST,EAASI,CAAO,EAAGC,CAAK,CAAC,EAEzE,OAAKE,GAAI,MAAMN,EAAK,CAAE,KAAM,oBAAqB,MAAAI,CAAM,CAAC,EAEjDE,CACT,EACA,UAAUH,EAASM,EAAQC,EAAQ,CAAC,EAAG,CACrC,IAAMC,EAAOC,GAAYH,CAAM,EACzBI,EAAOd,EAAQ,gBACnBe,EAAW,CACT,OAAQH,EAAK,OACb,OAAAF,EACA,MAAOE,EAAK,MACZ,QAASA,EAAK,QACd,QAASI,GAAaZ,EAAQ,QAAQ,IAAI,iBAAiB,CAAC,EAC5D,GAAIO,EAAM,SAAW,OAAY,CAAE,OAAQA,EAAM,MAAO,EAAI,CAAC,EAC7D,GAAIA,EAAM,eAAiB,OACvB,CAAE,aAAcA,EAAM,YAAa,EACnC,CAAC,CACP,CAAC,CACH,EAEA,OAAOM,GAAKL,EAAK,OAAQE,EAAM,CAAE,WAAYH,EAAM,YAAc,CAAC,CAAE,CAAC,CACvE,CACF,CACF,CA9CgBT,EAAAH,GAAA,iBCRhB,IAAMmB,GAAa,gDAWZ,SAASC,GAAoBC,EAAwB,CAC1D,OAAOF,GAAW,KAAKE,CAAK,CAC9B,CAFgBC,EAAAF,GAAA,uBAIhB,IAAMG,GAAiD,CACrD,IAAK,QACL,IAAK,OACL,IAAK,OACL,IAAK,SACL,IAAK,OACP,EAuBO,SAASC,EAAWH,EAAuB,CAChD,OAAOA,EAAM,QAAQ,WAAaI,GAASF,GAAaE,CAAI,GAAKA,CAAI,CACvE,CAFgBH,EAAAE,EAAA,cAYT,SAASE,GAAaL,EAAwB,CACnD,GAAIA,EAAM,SAAW,EAAG,MAAO,GAE/B,QAASM,EAAI,EAAGA,EAAIN,EAAM,OAAQM,GAAK,EAAG,CACxC,IAAMC,EAAOP,EAAM,WAAWM,CAAC,EAE/B,GACEC,IAAS,IACT,EAAEA,GAAQ,IAAQA,GAAQ,KAC1B,EAAEA,GAAQ,IAAQA,GAAQ,KAE1B,MAAO,EAEX,CAEA,MAAO,EACT,CAhBgBN,EAAAI,GAAA,gBA2BT,SAASG,GAAaC,EAAcL,EAAsB,CAC/D,IAAIM,EAAMD,EAAK,OAEf,KAAOC,EAAM,GAAKD,EAAKC,EAAM,CAAC,IAAMN,GAAMM,GAAO,EAEjD,OAAOD,EAAK,MAAM,EAAGC,CAAG,CAC1B,CANgBT,EAAAO,GAAA,gBChFT,SAASG,GAASC,EAAeC,EAAsB,CAC5D,MAAO;AAAA;AAAA;AAAA;AAAA;AAAA,SAKAC,EAAWF,CAAK,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAexBC,CAAI;AAAA;AAAA,QAGN,CAxBgBE,EAAAJ,GAAA,YCEhB,SAASK,GAAWC,EAAqC,CACvD,IAAMC,EAAY,iKAElB,OAAQD,EAAQ,WAAY,CAC1B,IAAK,SACH,MAAO,8DACT,IAAK,oBACH,OAAOA,EAAQ,aAAe,QAAaA,EAAQ,aAAe,GAC9DC,EACA,kEAAkEC,EAAWF,EAAQ,UAAU,CAAC,gEACtG,IAAK,UACH,OAAOC,CACX,CACF,CAbSE,EAAAJ,GAAA,cAsBT,SAASK,GAAiBJ,EAAqC,CAC7D,IAAMK,EAASL,EAAQ,aAAe,CAAC,EACjCM,EAAYN,EAAQ,WAAa,CAAC,EAClCO,EAAQ,CACZ,GAAG,IAAI,IAAI,CAAC,GAAG,OAAO,KAAKF,CAAM,EAAG,GAAG,OAAO,KAAKC,CAAS,CAAC,CAAC,CAChE,EAEA,OAAIC,EAAM,SAAW,EAAU,GAoBxB,uCAlBOA,EAAM,IAAI,CAACC,EAAMC,IAAU,CACvC,IAAMC,EAAK,SAASD,CAAK,GACnBE,EAAU,OAAO,OAAON,EAAQG,CAAI,EAAIH,EAAOG,CAAI,EAAI,OACvDI,EAAQ,OAAO,OAAON,EAAWE,CAAI,EAAIF,EAAUE,CAAI,EAAI,OAC3DK,EACJF,IAAY,OACR,GACA,0BAA0BD,CAAE,WAAWR,EAAWS,CAAO,CAAC,UAC1DG,EACJH,IAAY,OACR,GACA,0CAA0CD,CAAE,UAElD,MAAO,kBAAkBA,CAAE,KAAKR,EAAWM,CAAI,CAAC;AAAA,yBAC3BE,CAAE,WAAWR,EAAWM,CAAI,CAAC,YAAYN,EAAWU,GAAS,EAAE,CAAC,IAAIE,CAAS;AAAA,EACpGD,CAAI,MACJ,CAAC,EAEmD,KAAK;AAAA,CAAI,CAAC,YAChE,CA5BSV,EAAAC,GAAA,oBAqCF,SAASW,GAAmBf,EAAqC,CACtE,IAAMgB,EAAQhB,EAAQ,YAAc,gCAC9BiB,EAAWjB,EAAQ,mBACrB,qCAAqCE,EAAWF,EAAQ,YAAY,CAAC,IACrEE,EAAWF,EAAQ,YAAY,EAC7BkB,EACJlB,EAAQ,qBAAuB,OAC3B,GACA,mCAAmCE,EAAWF,EAAQ,kBAAkB,CAAC,iBACzEmB,EAAanB,EAAQ,OACxB,IAAKoB,GAAU,CAGd,IAAMC,EAAc,OAAO,OAAOrB,EAAQ,kBAAmBoB,CAAK,EAC9DpB,EAAQ,kBAAkBoB,CAAK,EAC/B,OACEE,EACJD,IAAgB,OAAY,GAAK,WAAMnB,EAAWmB,CAAW,CAAC,GAEhE,MAAO,aAAanB,EAAWkB,CAAK,CAAC,UAAUE,CAAM,OACvD,CAAC,EACA,KAAK,EAAE,EACJC,EAAS,OAAO,QAAQvB,EAAQ,YAAY,EAC/C,IACC,CAAC,CAACQ,EAAMI,CAAK,IACX,8BAA8BV,EAAWM,CAAI,CAAC,YAAYN,EAAWU,CAAK,CAAC,IAC/E,EACC,KAAK;AAAA,CAAI,EACNY,EAAWtB,EAAWF,EAAQ,aAAa,EAC3CyB,EAAcrB,GAAiBJ,CAAO,EAE5C,OAAO0B,GACL,sBACA,uBAAuBxB,EAAWc,CAAK,CAAC;AAAA,EAC1CE,CAAO;AAAA,EACPnB,GAAWC,CAAO,CAAC;AAAA;AAAA,qCAEgBmB,CAAU;AAAA,0BACrBjB,EAAWF,EAAQ,QAAQ,CAAC;AAAA,iCACrBiB,CAAQ;AAAA,uCACFf,EAAWF,EAAQ,QAAQ,CAAC;AAAA;AAAA;AAAA,8BAGrCE,EAAWF,EAAQ,UAAU,CAAC;AAAA,EAC1DuB,CAAM;AAAA,EACNE,CAAW;AAAA;AAAA,8BAEiBD,CAAQ;AAAA,8BACRA,CAAQ;AAAA;AAAA,QAGpC,CACF,CApDgBrB,EAAAY,GAAA,sBA6DT,SAASY,GAAiB3B,EAAmC,CAClE,OAAO0B,GACL1B,EAAQ,MACR,OAAOE,EAAWF,EAAQ,KAAK,CAAC;AAAA,KAC/BE,EAAWF,EAAQ,OAAO,CAAC,OAC1BA,EAAQ,SAAW,OACf,GACA;AAAA,kBAAqBE,EAAWF,EAAQ,MAAM,CAAC,MACrD,EACF,CACF,CAVgBG,EAAAwB,GAAA,oBCrHT,SAASC,GAA2BC,EAA2B,CACpE,GACEA,EAAS,OAASC,IAClB,CAACD,EAAS,WAAW,UAAU,GAC/BA,EAAS,SAAS,GAAG,EAErB,MAAO,GAGT,IAAIE,EAEJ,GAAI,CACFA,EAAS,IAAI,IAAIF,CAAQ,CAC3B,MAAQ,CACN,MAAO,EACT,CAEA,OACEE,EAAO,WAAa,IACpBA,EAAO,WAAa,IACpBA,EAAO,WAAa,IACpBA,EAAO,SAAS,OAAS,GACzBA,EAAO,OAASF,CAEpB,CAxBgBG,EAAAJ,GAAA,8BAmCT,SAASK,EACdC,EACAC,EACoB,CACpB,GAAI,OAAOD,GAAU,SAAU,OAE/B,IAAME,EAAOF,EAAM,KAAK,EAExB,GAAI,EAAAE,EAAK,SAAW,GAAKA,EAAK,OAASD,GAEvC,OAAOE,GAAoBD,CAAI,EAAI,OAAYA,CACjD,CAXgBJ,EAAAC,EAAA,mBAmBhB,SAASK,GAAqBJ,EAA4C,CACxE,OAAOA,IAAU,sBAAwBA,IAAU,eACrD,CAFSF,EAAAM,GAAA,wBAeF,SAASC,GACdL,EACAM,EACkC,CAClC,GAAIN,IAAU,OAAW,MAAO,CAAC,oBAAoB,EAErD,GAAI,CAACO,EAAcP,CAAK,GAAK,CAACA,EAAM,SAAS,oBAAoB,EAC/D,OAGF,IAAMQ,EAAQR,EAAM,OAAOI,EAAoB,EAE/C,GAAI,EAAAE,GAAUE,EAAM,SAAWR,EAAM,QAErC,MAAO,CAAC,GAAG,IAAI,IAAIQ,CAAK,CAAC,CAC3B,CAfgBV,EAAAO,GAAA,mBA6BT,SAASI,GACdT,EACAU,EACAJ,EACAL,EAC6B,CAC7B,GAAID,IAAU,OAAW,OAAO,KAEhC,GAAI,OAAOA,GAAU,UAAYA,EAAM,OAASC,EAAW,OAE3D,IAAMU,EAAY,CAAC,GAAG,IAAI,IAAIX,EAAM,MAAM,GAAG,EAAE,OAAQY,GAAMA,EAAE,OAAS,CAAC,CAAC,CAAC,EACrEC,EAASF,EAAU,OAAQC,GAAMF,EAAU,SAASE,CAAC,CAAC,EAE5D,GAAIC,EAAO,SAAW,GAElB,EAAAP,GAAUO,EAAO,OAASF,EAAU,QAExC,OAAOE,CACT,CAlBgBf,EAAAW,GAAA,qBC/FT,IAAMK,GAA8B,KAoBrCC,GAAkD,CACtD,OACA,QACA,MACA,QACA,oBACA,WACF,EAEA,SAASC,GAASC,EAA+B,CAC/C,GAAI,CACF,OAAO,IAAI,IAAIA,CAAI,CACrB,MAAQ,CACN,MACF,CACF,CANSC,EAAAF,GAAA,YAQT,SAASG,GAAeC,EAAmB,CACzC,OAAOA,EAAI,WAAa,SAAWC,GAAe,IAAID,EAAI,QAAQ,CACpE,CAFSF,EAAAC,GAAA,kBAWF,SAASG,GAAsBC,EAAsB,CAC1D,IAAMH,EAAMJ,GAASO,CAAG,EAExB,OAAOH,IAAQ,QAAaD,GAAeC,CAAG,CAChD,CAJgBF,EAAAI,GAAA,yBAeT,SAASE,GACdD,EACAE,EAAiD,CAC/C,kBAAmBX,EACrB,EACgC,CAChC,GAAIS,EAAI,OAASE,EAAO,kBAAmB,MAAO,cAElD,IAAML,EAAMJ,GAASO,CAAG,EAExB,OAAIH,IAAQ,OAAkB,cAG1BG,EAAI,SAAS,GAAG,EAAU,eAE1BH,EAAI,SAAS,OAAS,GAAKA,EAAI,SAAS,OAAS,EAC5C,kBAKL,EAFWA,EAAI,WAAa,UAAYA,EAAI,SAAS,OAAS,IAEnD,CAACD,GAAeC,CAAG,EAAU,oBAErCL,GAA6B,KAAMW,GAASN,EAAI,aAAa,IAAIM,CAAI,CAAC,EACzE,2BACA,MACN,CA1BgBR,EAAAM,GAAA,sBAqCT,SAASG,GACdJ,EACAE,EACS,CACT,OAAOD,GAAmBD,EAAKE,CAAM,IAAM,MAC7C,CALgBP,EAAAS,GAAA,2BAOhB,SAASC,GAAQC,EAAuB,CACtC,OAAOA,GAAQ,IAAMA,GAAQ,EAC/B,CAFSX,EAAAU,GAAA,WAWT,SAASE,GAASP,EAAaH,EAAkB,CAC/C,IAAMW,EAAO,GAAGX,EAAI,QAAQ,KAAKA,EAAI,QAAQ,GAE7C,GAAIG,EAAI,MAAM,EAAGQ,EAAK,MAAM,EAAE,YAAY,IAAMA,EAAK,YAAY,EAC/D,OAAOR,EAGT,IAAIS,EAAOT,EAAI,MAAMQ,EAAK,MAAM,EAEhC,GAAIC,EAAK,WAAW,GAAG,EAAG,CACxB,IAAIC,EAAI,EAER,KAAOA,EAAID,EAAK,QAAUJ,GAAQI,EAAK,WAAWC,CAAC,CAAC,GAAGA,GAAK,EAC5DD,EAAOA,EAAK,MAAMC,CAAC,CACrB,CAEA,OAAOF,EAAK,YAAY,EAAIC,CAC9B,CAjBSd,EAAAY,GAAA,YA6BF,SAASI,GACdC,EACAC,EACAX,EACS,CACT,GAAI,CAACE,GAAwBS,EAAWX,CAAM,EAAG,MAAO,GAExD,GAAIW,IAAcD,EAAY,MAAO,GAErC,IAAME,EAAIrB,GAASmB,CAAU,EACvBG,EAAItB,GAASoB,CAAS,EAI5B,OAFIC,IAAM,QAAaC,IAAM,QAEzB,CAACnB,GAAekB,CAAC,GAAK,CAAClB,GAAemB,CAAC,EAAU,GAI9CR,GAASK,EAAYE,CAAC,IAAMP,GAASM,EAAWE,CAAC,CAC1D,CAnBgBpB,EAAAgB,GAAA,sBA+BT,SAASK,GACdJ,EACAC,EACAX,EACS,CACT,OAAOU,EAAW,KAAMZ,GAAQW,GAAmBX,EAAKa,EAAWX,CAAM,CAAC,CAC5E,CANgBP,EAAAqB,GAAA,yBAeT,SAASC,GAAejB,EAAqB,CAClD,IAAMH,EAAMJ,GAASO,CAAG,EAExB,OAAIH,IAAQ,OAAkB,GAEvBD,GAAeC,CAAG,EAAIA,EAAI,SAAWA,EAAI,IAClD,CANgBF,EAAAsB,GAAA,kBAeT,SAASC,GAASlB,EAAiC,CACxD,OAAOP,GAASO,CAAG,GAAG,MACxB,CAFgBL,EAAAuB,GAAA,YAaT,SAASC,GACdC,EACAC,EACQ,CACR,IAAMC,EAAS,IAAI,IAAIF,CAAW,EAElC,OAAW,CAACjB,EAAMoB,CAAK,IAAK,OAAO,QAAQF,CAAM,EAC3CE,IAAU,QAAWD,EAAO,aAAa,OAAOnB,EAAMoB,CAAK,EAGjE,OAAOD,EAAO,SAAS,CACzB,CAXgB3B,EAAAwB,GAAA,iBCzNT,SAASK,GACdC,EACAC,EAAiD,CAAC,EAChB,CAClC,OAAW,CAACC,EAAOC,CAAI,IAAK,OAAO,QAAQF,CAAY,EAAG,CACxD,GAAI,CAACD,EAAO,SAASE,CAAK,EACxB,MAAM,IAAIE,EACR,2BAA2BF,CAAK,0BAClC,EAGF,GAAIC,EAAK,KAAK,IAAM,IAAMA,EAAK,OAAS,KAAOE,GAAoBF,CAAI,EACrE,MAAM,IAAIC,EACR,qBAAqBF,CAAK,sCAC5B,CAEJ,CAGA,IAAMI,EAA8B,OAAO,YACzC,OAAO,QAAQL,CAAY,CAC7B,EAEA,cAAO,eAAeK,EAAK,IAAI,EAExB,OAAO,OAAOA,CAAG,CAC1B,CA1BgBC,EAAAR,GAAA,4BAuCT,SAASS,GACdC,EAAmC,CAAC,EACpCT,EACAU,EACAC,EACmC,CACnC,IAAMC,EAAO,IAAI,IAEjB,QAAWC,KAAUJ,EAAS,CAC5B,IAAMK,EAAKD,EAAO,SACZE,EAAQ,WAAW,KAAK,UAAUD,CAAE,CAAC,IAE3C,GAAIA,EAAG,KAAK,IAAM,IAAMA,EAAG,OAAS,KAAOT,GAAoBS,CAAE,EAC/D,MAAM,IAAIV,EACR,GAAGW,CAAK,gDACV,EAGF,GAAIC,GAA2BF,CAAE,GAAK,WAAW,KAAKA,CAAE,EACtD,MAAM,IAAIV,EACR,GAAGW,CAAK,qCACV,EAGF,GAAIH,EAAK,IAAIE,CAAE,EAAG,MAAM,IAAIV,EAAmB,GAAGW,CAAK,gBAAgB,EAEvE,GACEF,EAAO,aAAa,SAAW,GAC/BA,EAAO,aAAa,OAASH,EAAO,aAEpC,MAAM,IAAIN,EACR,GAAGW,CAAK,eAAeL,EAAO,YAAY,eAC5C,EAGF,QAAWO,KAAOJ,EAAO,aAAc,CACrC,IAAMK,EAAUC,GAAmBF,EAAKP,CAAM,EAE9C,GAAIQ,IAAY,OACd,MAAM,IAAId,EACR,GAAGW,CAAK,2BAA2BG,CAAO,GAC5C,CAEJ,CAEA,IAAME,EACJP,EAAO,aAAe,OAClB,OACAQ,EAAgBR,EAAO,WAAYH,EAAO,gBAAgB,EAEhE,GAAIG,EAAO,aAAe,QAAaO,IAAe,OACpD,MAAM,IAAIhB,EACR,GAAGW,CAAK,0CACV,EAGF,IAAMO,EAAaC,GACjBV,EAAO,YAAc,CAAC,qBAAsB,eAAe,EAC3D,EACF,EAEA,GAAIS,IAAe,OACjB,MAAM,IAAIlB,EACR,GAAGW,CAAK,8CACV,EAGF,GAAIF,EAAO,QAAQ,KAAMX,GAAU,CAACF,EAAO,SAASE,CAAK,CAAC,IAAM,GAC9D,MAAM,IAAIE,EACR,GAAGW,CAAK,qCACV,EAGF,GAAIF,EAAO,QAAQ,SAAW,EAC5B,MAAM,IAAIT,EAAmB,GAAGW,CAAK,4BAA4B,EAInEH,EAAK,IACHE,EACAU,GAAW,CACT,SAAUV,EACV,aAAc,CAAC,GAAG,IAAI,IAAID,EAAO,YAAY,CAAC,EAC9C,KAAM,SACN,UAAWF,EAAI,EACf,WAAAW,EACA,GAAIT,EAAO,SAAW,OAAY,CAAE,OAAQ,CAAC,GAAGA,EAAO,MAAM,CAAE,EAAI,CAAC,EACpE,GAAIO,IAAe,OAAY,CAAE,WAAAA,CAAW,EAAI,CAAC,CACnD,CAAC,CACH,CACF,CAEA,OAAOR,CACT,CA7FgBL,EAAAC,GAAA,wBC7CT,SAASiB,GACdC,EACAC,EACAC,EACK,CACL,IAAIC,EAEJ,GAAI,CACFA,EAAM,IAAI,IAAIF,CAAK,CACrB,MAAQ,CACN,MAAM,IAAIG,EAAmB,GAAGJ,CAAI,0BAA0B,CAChE,CAEA,GAAIC,EAAM,SAAS,GAAG,GAAKE,EAAI,SAAW,IAAMF,EAAM,SAAS,GAAG,EAChE,MAAM,IAAIG,EAAmB,GAAGJ,CAAI,iCAAiC,EAGvE,GAAIG,EAAI,WAAa,IAAMA,EAAI,WAAa,GAC1C,MAAM,IAAIC,EAAmB,GAAGJ,CAAI,wBAAwB,EAG9D,IAAMK,EAAQF,EAAI,WAAa,SAAWG,GAAe,IAAIH,EAAI,QAAQ,EAEzE,GAAIA,EAAI,WAAa,UAAY,EAAEE,GAASH,GAC1C,MAAM,IAAIE,EACR,GAAGJ,CAAI,kFACT,EAGF,OAAOG,CACT,CA9BgBI,EAAAR,GAAA,kBAuCT,SAASS,GAASC,EAA0B,CACjD,OAAOC,GAAaD,EAAU,GAAG,CACnC,CAFgBF,EAAAC,GAAA,YAgBT,SAASG,GACdX,EACAC,EACAW,EACAC,EACAC,EACQ,CACR,IAAMC,EAASd,GAASW,EAExB,GAAI,CAAC,OAAO,UAAUG,CAAM,GAAKA,EAASF,GAAOE,EAASD,EACxD,MAAM,IAAIV,EACR,aAAaJ,CAAI,4BAA4Ba,CAAG,OAAOC,CAAG,EAC5D,EAGF,OAAOC,EAAS,GAClB,CAhBgBR,EAAAI,GAAA,WA4BT,SAASK,GACdhB,EACAC,EACAW,EACQ,CACR,IAAMG,EAASd,GAASW,EAExB,GAAI,CAAC,OAAO,UAAUG,CAAM,GAAKA,EAAS,EACxC,MAAM,IAAIX,EAAmB,UAAUJ,CAAI,6BAA6B,EAG1E,OAAOe,CACT,CAZgBR,EAAAS,GAAA,YAsBT,SAASC,GAAcC,EAAiB,CAAC,EAAqB,CACnE,IAAMC,EAAOZ,EAACa,GACZJ,GAASI,EAAKF,EAAOE,CAAG,EAAGC,GAAeD,CAAG,CAAC,EADnC,QAGb,MAAO,CACL,iBAAkBD,EAAK,kBAAkB,EACzC,aAAcA,EAAK,cAAc,EACjC,kBAAmBA,EAAK,mBAAmB,EAC3C,YAAaA,EAAK,aAAa,EAC/B,YAAaA,EAAK,aAAa,EAC/B,kBAAmBA,EAAK,mBAAmB,EAC3C,UAAWA,EAAK,WAAW,EAC3B,oBAAqBA,EAAK,qBAAqB,EAC/C,wBAAyBA,EAAK,yBAAyB,EACvD,uBAAwBA,EAAK,wBAAwB,EACrD,kBAAmBA,EAAK,mBAAmB,EAC3C,oBAAqBA,EAAK,qBAAqB,EAC/C,eAAgBA,EAAK,gBAAgB,CACvC,CACF,CAnBgBZ,EAAAU,GAAA,iBA+BT,SAASK,GAAUtB,EAAcuB,EAAsB,CAG5D,IAAMC,EAAWD,EAAK,MAAM,GAAG,EAAE,MAAM,CAAC,EAExC,GACE,CAACA,EAAK,WAAW,GAAG,GACpBA,EAAK,SAAS,IAAI,GAClB,WAAW,KAAKA,CAAI,GACpBC,EAAS,KAAMC,GAAYA,IAAY,KAAOA,IAAY,IAAI,EAE9D,MAAM,IAAIrB,EACR,SAASJ,CAAI,6EACf,EAGF,OAAOuB,CACT,CAjBgBhB,EAAAe,GAAA,aA4BT,SAASI,GACdC,EACAC,EAAoB,CAAC,EACC,CACtB,IAAMT,EAAOZ,EAACa,GACZE,GAAUF,EAAKQ,EAAMR,CAAG,GAAK,GAAGO,CAAU,IAAIP,CAAG,EAAE,EADxC,QAEPS,EAAiC,CACrC,UAAWV,EAAK,WAAW,EAC3B,SAAUA,EAAK,UAAU,EACzB,QAASA,EAAK,SAAS,EACvB,MAAOA,EAAK,OAAO,EACnB,SAAUA,EAAK,UAAU,EACzB,OAAQA,EAAK,QAAQ,CACvB,EACMW,EAAM,OAAO,OAAOD,CAAQ,EAElC,GAAI,IAAI,IAAIC,CAAG,EAAE,OAASA,EAAI,OAC5B,MAAM,IAAI1B,EAAmB,wBAAwB,EAGvD,OAAOyB,CACT,CArBgBtB,EAAAmB,GAAA,gBClJhB,IAAMK,GAAc,8BAGdC,GAAgB,gCAaf,SAASC,GACdC,EACAC,EAIiB,CACjB,IAAMC,EAAWF,EAAQ,yBAA2B,GAC9CG,EAAYC,GAAe,SAAUJ,EAAQ,OAAQE,CAAQ,EAC7DG,EAAaC,GAASH,EAAU,QAAQ,EACxCI,EAAS,GAAGJ,EAAU,MAAM,GAAGE,CAAU,GAE/C,GACEL,EAAQ,SAAWO,GACnB,EAAEF,IAAe,IAAML,EAAQ,SAAW,GAAGO,CAAM,KAEnD,MAAM,IAAIC,EACR,kDAAkDD,CAAM,EAC1D,EAGF,IAAME,EAAcL,GAAe,WAAYJ,EAAQ,SAAUE,CAAQ,EACnEQ,EAAeJ,GAASG,EAAY,QAAQ,EAC5CE,EAASX,EAAQ,QAAUY,GAEjC,GAAID,EAAO,SAAW,GAAK,CAACA,EAAO,MAAME,EAAY,EACnD,MAAM,IAAIL,EAAmB,uCAAuC,EAGtE,IAAMM,EAAgBd,EAAQ,eAAiBW,EAE/C,GACEG,EAAc,SAAW,GACzB,CAACA,EAAc,MAAOC,GAAUJ,EAAO,SAASI,CAAK,CAAC,EAEtD,MAAM,IAAIP,EAAmB,0CAA0C,EAGzE,IAAMQ,EACJhB,EAAQ,0BAA4BiB,GAEtC,GACE,CAAC,OAAO,UAAUD,CAAY,GAC9BA,EAAe,GACfA,EAAeE,GAEf,MAAM,IAAIV,EACR,yDAAyDU,EAA+B,EAC1F,EAGF,IAAMC,EAAYnB,EAAQ,WAAa,CAAC,EAClCoB,EAAWC,EAACC,GAChBC,GACED,EACAH,EAAUG,CAAG,EACbE,GAAkBF,CAAG,EACrBG,GAAgBH,CAAG,EAAE,IACrBG,GAAgBH,CAAG,EAAE,GACvB,EAPe,YAQXI,EAAQC,GAAatB,EAAYL,EAAQ,KAAK,EAC9C4B,EAAe5B,EAAQ,cAAgB6B,GAE7C,GAAI,CAAC/B,GAAc,KAAK8B,CAAY,EAClC,MAAM,IAAIpB,EACR,qFACF,EAGF,IAAMsB,EAASC,GAAc/B,EAAQ,MAAM,EACrCgC,EAAMC,GAAajC,EAAQ,KAAO,KAAK,GAAG,EAC1CkC,EAAe,CAAC,GAAIlC,EAAQ,cAAgB,CAAC,CAAE,EAErD,QAAWmC,KAAQD,EACjB,GAAI,CAACrC,GAAY,KAAKsC,CAAI,EACxB,MAAM,IAAI3B,EACR,iEACF,EAIJ,IAAM4B,EACJpC,EAAQ,sBAAwBqC,GAElC,GACE,CAAC,OAAO,UAAUD,CAAa,GAC/BA,EAAgB,GAChBA,EAAgBE,GAEhB,MAAM,IAAI9B,EACR,qDAAqD8B,EAA0B,EACjF,EAGF,IAAMC,EAAMC,GACNC,EAAMC,GACNC,EACJjC,IAAiB,GAAK,CAAC6B,CAAG,EAAI,CAAC,GAAGA,CAAG,GAAG7B,CAAY,GAAI6B,CAAG,EACvDK,EACJvC,IAAe,GAAK,CAACoC,CAAG,EAAI,CAAC,GAAGA,CAAG,GAAGpC,CAAU,GAAIoC,CAAG,EAGnDI,EAAW,IAAI,IAAI,CACvB,GAAGF,EACH,GAAGC,EACHnC,EAAY,SACZC,IAAiB,GAAK,IAAMA,CAC9B,CAAC,EAED,OAAW,CAACoC,EAAMC,CAAI,IAAK,OAAO,QAAQrB,CAAK,EAC7C,GAAImB,EAAS,IAAIE,CAAI,EACnB,MAAM,IAAIvC,EACR,SAASsC,CAAI,qDACf,EAIJ,MAAO,CACL,OAAAvC,EACA,aAAcJ,EAAU,OACxB,SAAUH,EAAQ,SAClB,oBAAqB,GAAGS,EAAY,MAAM,GAAG8B,CAAG,GAAG7B,CAAY,GAC/D,sBAAAiC,EACA,iCAAAC,EACA,OAAQ,CAAC,GAAGjC,CAAM,EAClB,cAAe,CAAC,GAAGG,CAAa,EAChC,iBAAkBd,EAAQ,iBAC1B,MAAOA,EAAQ,MACf,OAAQA,EAAQ,OAChB,kBAAmBA,EAAQ,mBAAqBC,EAAU,QAC1D,gBAAiBD,EAAQ,iBAAmBC,EAAU,MACtD,iBAAkBmB,EAAS,aAAa,EACxC,kBAAmBA,EAAS,cAAc,EAC1C,WAAYA,EAAS,OAAO,EAC5B,UAAWA,EAAS,mBAAmB,EACvC,aAAcA,EAAS,sBAAsB,EAC7C,sBAAuBA,EAAS,qBAAqB,EACrD,wBAAyBA,EAAS,uBAAuB,EACzD,OAAAU,EACA,aAAAF,EACA,aAAAM,EACA,gBAAiBE,EAAgB,IACjC,oBAAqBpB,EAAe,IACpC,0BAA2BhB,EAAQ,2BAA6B,GAChE,0BAA2BA,EAAQ,2BAA6B,GAChE,MAAA0B,EACA,YAAa,GAAGvB,EAAU,MAAM,GAAGuB,EAAM,QAAQ,GACjD,WAAY1B,EAAQ,YAAc,GAClC,IAAAgC,EACA,kBAAmBgB,GACjBrC,EACAX,EAAQ,iBACV,EACA,cAAeiD,GAAqBjD,EAAQ,QAASW,EAAQmB,EAAQE,CAAG,EACxE,GAAIhC,EAAQ,eAAiB,OACzB,CAAE,aAAcA,EAAQ,YAAa,EACrC,CAAC,EACL,GAAIA,EAAQ,UAAY,CAAE,UAAWA,EAAQ,SAAU,EAAI,CAAC,EAC5D,GAAIA,EAAQ,YAAc,CAAE,YAAaA,EAAQ,WAAY,EAAI,CAAC,EAClE,GAAIA,EAAQ,QAAU,CAAE,QAASA,EAAQ,OAAQ,EAAI,CAAC,EACtD,GAAIA,EAAQ,oBACR,CAAE,oBAAqBA,EAAQ,mBAAoB,EACnD,CAAC,EACL,GAAIA,EAAQ,gBACR,CAAE,gBAAiBA,EAAQ,eAAgB,EAC3C,CAAC,EACL,GAAIA,EAAQ,kBACR,CAAE,kBAAmBA,EAAQ,iBAAkB,EAC/C,CAAC,EACL,GAAIA,EAAQ,WAAa,CAAE,WAAYA,EAAQ,UAAW,EAAI,CAAC,EAC/D,GAAIA,EAAQ,aAAe,CAAE,aAAcA,EAAQ,YAAa,EAAI,CAAC,EACrE,GAAIA,EAAQ,uBACR,CAAE,uBAAwBA,EAAQ,sBAAuB,EACzD,CAAC,EACL,GAAIA,EAAQ,gBACR,CAAE,gBAAiBA,EAAQ,eAAgB,EAC3C,CAAC,EACL,GAAIA,EAAQ,kBACR,CAAE,kBAAmBA,EAAQ,iBAAkB,EAC/C,CAAC,EACL,GAAIA,EAAQ,kBACR,CAAE,kBAAmBA,EAAQ,iBAAkB,EAC/C,CAAC,EACL,GAAIA,EAAQ,YAAc,CAAE,YAAaA,EAAQ,WAAY,EAAI,CAAC,EAClE,GAAIA,EAAQ,WAAa,CAAE,WAAYA,EAAQ,UAAW,EAAI,CAAC,EAC/D,GAAIA,EAAQ,iBACR,CAAE,iBAAkBA,EAAQ,gBAAiB,EAC7C,CAAC,CACP,CACF,CA7LgBqB,EAAAtB,GAAA,kBCnDhB,OAAS,aAAAmD,OAAiB,sBCMnB,IAAMC,EAAN,cAAkC,KAAM,CAN/C,MAM+C,CAAAC,EAAA,4BAEpC,OAKT,YAAYC,EAAgB,CAC1B,MAAM,6CAA6CA,CAAM,GAAG,EAC5D,KAAK,KAAO,sBACZ,KAAK,OAASA,CAChB,CACF,EDaO,SAASC,GACdC,EACAC,EACAC,EACAC,EACc,CACd,GAAI,CAACC,EAASH,CAAQ,EAAG,MAAM,IAAII,EAAoB,eAAe,EAEtE,GAAIJ,EAAS,YAAcD,EACzB,MAAM,IAAIK,EAAoB,4BAA4B,EAG5D,IAAMC,EAAUL,EAAS,sCAIzB,GACGA,EAAS,6BAA+B,QACvCA,EAAS,6BAA+B,QACzCK,IAAY,SACV,CAACC,EAAcD,CAAO,GAAK,CAACA,EAAQ,SAAS,MAAM,IACtDL,EAAS,gBAAkB,OAE3B,MAAM,IAAII,EAAoB,qBAAqB,EAGrD,IAAMG,EAAaC,GAAgBR,EAAS,YAAa,EAAK,EAE9D,GAAIO,IAAe,OACjB,MAAM,IAAIH,EAAoB,sBAAsB,EAGtD,GACEJ,EAAS,iBAAmB,SAC3B,CAACM,EAAcN,EAAS,cAAc,GACrC,CAACA,EAAS,eAAe,SAAS,MAAM,GAE1C,MAAM,IAAII,EAAoB,yBAAyB,EAGzD,IAAMK,EAASC,GACbV,EAAS,MACTE,EAAQ,OACR,GACAA,EAAQ,OAAO,WACjB,EAEA,GAAIO,IAAW,OAAW,MAAM,IAAIL,EAAoB,gBAAgB,EAExE,GAAI,CAACE,EAAcN,EAAS,aAAa,EACvC,MAAM,IAAII,EAAoB,sBAAsB,EAGtD,IAAMO,EAAeX,EAAS,cAC3B,OAAQY,GAAQC,GAAwBD,EAAKV,EAAQ,MAAM,CAAC,EAC5D,MAAM,EAAGA,EAAQ,OAAO,YAAY,EAEvC,GAAIS,EAAa,SAAW,EAC1B,MAAM,IAAIP,EAAoB,wBAAwB,EAGxD,IAAMU,EAAaC,EACjBf,EAAS,YACTE,EAAQ,OAAO,gBACjB,EAEA,MAAO,CACL,SAAUH,EACV,aAAAY,EACA,KAAM,oBACN,UAAWV,EACX,WAAAM,EACA,GAAIE,IAAW,KAAO,CAAE,OAAAA,CAAO,EAAI,CAAC,EACpC,GAAIK,IAAe,OAAY,CAAE,WAAAA,CAAW,EAAI,CAAC,CACnD,CACF,CA3EgBE,EAAAlB,GAAA,0BAmFhB,SAASmB,GACPf,EACuB,CACvB,MAAO,CAACH,EAAKmB,IACXC,GACEpB,EACA,CACE,OAAQ,MACR,QAAS,CAAE,OAAQ,kBAAmB,EACtC,SAAU,QACV,OAAQmB,EAAK,MACf,EACA,CACE,UAAWhB,EAAQ,OAAO,wBAC1B,iBAAkBA,EAAQ,OAAO,oBACjC,MAAO,iBACT,CACF,CACJ,CAlBSc,EAAAC,GAAA,kBA2BT,SAASG,GAAeC,EAAkBC,EAAiC,CACzE,OAAIA,EAAO,QAAgB,QAAQ,OAAO,IAAIlB,EAAoB,SAAS,CAAC,EAErE,IAAI,QAAW,CAACmB,EAASC,IAAW,CACzC,IAAMC,EAAUT,EAAA,IAAM,CACpBQ,EAAO,IAAIpB,EAAoB,SAAS,CAAC,CAC3C,EAFgB,WAIhBkB,EAAO,iBAAiB,QAASG,EAAS,CAAE,KAAM,EAAK,CAAC,EACxDJ,EAAK,KACFK,GAAU,CACTJ,EAAO,oBAAoB,QAASG,CAAO,EAC3CF,EAAQG,CAAK,CACf,EACCC,GAAmB,CAClBL,EAAO,oBAAoB,QAASG,CAAO,EAC3CD,EACEG,aAAiB,MACbA,EACA,IAAIvB,EAAoBwB,GAAcD,CAAK,CAAC,CAClD,CACF,CACF,CACF,CAAC,CACH,CAxBSX,EAAAI,GAAA,eAmCT,eAAeS,GACbC,EACAC,EACAT,EACiB,CACjB,IAAMU,EAAW,OAAOF,EAAS,QAAQ,IAAI,gBAAgB,CAAC,EAE9D,GAAI,OAAO,SAASE,CAAQ,GAAKA,EAAWD,EAC1C,YAAMD,EAAS,MAAM,OAAO,EAAE,MAAM,IAAM,CAAC,CAAC,EACtC,IAAI1B,EAAoB,WAAW,EAG3C,GAAI0B,EAAS,OAAS,KAAM,MAAO,GAEnC,IAAMG,EAASH,EAAS,KAAK,UAAU,EACjCI,EAAsB,CAAC,EACzBC,EAAO,EAEX,GAAI,CACF,OAAS,CACP,IAAMC,EAAQ,MAAMhB,GAAYa,EAAO,KAAK,EAAGX,CAAM,EAErD,GAAIc,EAAM,KAAM,MAEhB,IAAMC,EAAiBD,EAAM,MAE7B,GAAI,EAAEC,aAAiB,YACrB,MAAM,IAAIjC,EAAoB,WAAW,EAK3C,GAFA+B,GAAQE,EAAM,WAEVF,EAAOJ,EAAO,MAAM,IAAI3B,EAAoB,WAAW,EAE3D8B,EAAM,KAAKG,CAAK,CAClB,CACF,OAASV,EAAO,CACd,YAAMM,EAAO,OAAO,EAAE,MAAM,IAAM,CAAC,CAAC,EAC9BN,CACR,CAEA,OAAO,OAAO,OAAOO,CAAK,EAAE,SAAS,MAAM,CAC7C,CA1CelB,EAAAa,GAAA,eAmDR,SAASD,GAAcD,EAAwB,CACpD,OAAIA,aAAiBvB,EAA4BuB,EAAM,OAElDxB,EAASwB,CAAK,EAEf,OAAOA,EAAM,MAAS,SAAiBA,EAAM,KAE1C,OAAOA,EAAM,QAAW,SAAWA,EAAM,OAAS,cAJ5B,aAK/B,CARgBX,EAAAY,GAAA,iBA6BT,SAASU,GAAoBC,EAA2C,CAC7E,GAAIA,IAAW,KAAM,OAErB,IAAIC,EAEJ,QAAWC,KAAQF,EAAO,MAAM,GAAG,EAAG,CACpC,GAAM,CAACG,EAAU,GAAIC,EAAW,EAAE,EAAIF,EAAK,MAAM,IAAK,CAAC,EAGvD,OAFaC,EAAQ,KAAK,EAAE,YAAY,EAE1B,CACZ,IAAK,WACL,IAAK,WACH,MAAO,GACT,IAAK,UAAW,CACd,IAAMhB,EAAQiB,EAAS,KAAK,EAAE,QAAQ,WAAY,IAAI,EAEtDH,EAAU,aAAa,KAAKd,CAAK,EAAI,OAAOA,CAAK,EAAI,EACrD,KACF,CACA,QACE,KACJ,CACF,CAEA,OAAOc,CACT,CAzBgBxB,EAAAsB,GAAA,uBAuChB,eAAsBM,GACpB7C,EACAE,EACAC,EACwB,CACxB,IAAM2C,EAAU3C,EAAQ,qBAAuBe,GAAef,CAAO,EAC/DoB,EAAS,YAAY,QAAQpB,EAAQ,OAAO,uBAAuB,EACrE4C,EACAC,EAEJ,GAAI,CACF,IAAMjB,EAAW,MAAMV,GAAYyB,EAAQ9C,EAAK,CAAE,OAAAuB,CAAO,CAAC,EAAGA,CAAM,EAEnE,GAAIQ,EAAS,SAAW,IACtB,YAAMA,EAAS,MAAM,OAAO,EAAE,MAAM,IAAM,CAAC,CAAC,EACtC,IAAI1B,EAAoB,UAAU0B,EAAS,MAAM,EAAE,EAG3DiB,EAAgBT,GAAoBR,EAAS,QAAQ,IAAI,eAAe,CAAC,EACzEgB,EAAO,MAAMjB,GACXC,EACA5B,EAAQ,OAAO,oBACfoB,CACF,CACF,OAASK,EAAO,CACd,MAAMA,aAAiBvB,EACnBuB,EACA,IAAIvB,EAAoBwB,GAAcD,CAAK,CAAC,CAClD,CAEA,IAAI3B,EAEJ,GAAI,CACFA,EAAW,KAAK,MAAM8C,CAAI,CAC5B,MAAQ,CACN,MAAM,IAAI1C,EAAoB,cAAc,CAC9C,CAEA,MAAO,CACL,OAAQN,GAAuBC,EAAKC,EAAUC,EAAKC,CAAO,EAC1D,cAAA6C,CACF,CACF,CA1CsB/B,EAAA4B,GAAA,uBErRtB,SAASI,GAAOC,EAAqB,CACnC,GAAI,CACF,OAAO,IAAI,IAAIA,CAAG,EAAE,IACtB,MAAQ,CACN,MAAO,EACT,CACF,CANSC,EAAAF,GAAA,UAgBT,SAASG,GAAeC,EAAmC,CACzD,IAAIH,EAEJ,GAAI,CACFA,EAAM,IAAI,IAAIG,CAAK,CACrB,MAAQ,CACN,MACF,CAEA,IAAMC,EAAOC,GAAaL,EAAI,SAAU,GAAG,EACrCM,EAAOD,GAAaL,EAAI,SAAU,GAAG,EAE3C,MAAO,GAAGA,EAAI,QAAQ,KAAKI,CAAI,IAAIJ,EAAI,IAAI,GAAGM,CAAI,EACpD,CAbSL,EAAAC,GAAA,kBAuBF,SAASK,GAAYP,EAAaQ,EAAyB,CAChE,IAAMC,EAAQP,GAAeF,CAAG,EAEhC,OAAOS,IAAU,QAAaA,IAAUP,GAAeM,CAAM,CAC/D,CAJgBP,EAAAM,GAAA,eAiBhB,eAAsBG,GACpBC,EACAC,EACmC,CACnC,GAAM,CAAE,QAAAC,CAAQ,EAAIF,EAEpB,GAAIC,EAAS,OAASE,GAAwB,OAE9C,GAAI,CAACC,GAA2BH,CAAQ,EAAG,CACzC,IAAMI,EAASH,EAAQ,cAAc,IAAID,CAAQ,EAEjD,GAAII,IAAW,OAAW,OAAOA,EAEjC,IAAMC,EAAS,MAAMJ,EAAQ,MAAM,UAAUD,CAAQ,EAGrD,OAAOK,GAAQ,OAAS,oBAAsB,OAAYA,CAC5D,CAEA,GAAI,CAACJ,EAAQ,0BAA2B,OAIxC,GAAIN,GAAYK,EAAUC,EAAQ,MAAM,EAAG,CACzC,MAAMF,EAAQ,KAAK,CACjB,KAAM,0BACN,KAAMZ,GAAOa,CAAQ,EACrB,OAAQ,qBACV,CAAC,EAED,MACF,CAEA,IAAMM,EAAQL,EAAQ,uBAEtB,GAAIK,IAAU,QAAa,CAAE,MAAMC,GAAQ,IAAMD,EAAMN,CAAQ,CAAC,EAAI,CAClE,MAAMD,EAAQ,KAAK,CACjB,KAAM,0BACN,KAAMZ,GAAOa,CAAQ,EACrB,OAAQ,uBACV,CAAC,EAED,MACF,CAEA,IAAMQ,EAAS,MAAMP,EAAQ,MAAM,wBAAwBD,CAAQ,EAEnE,GAAIQ,GAAQ,WAAaR,EAAU,OAAOQ,EAE1C,GAAM,CAAE,SAAAC,EAAU,SAAAC,CAAS,EAAIX,EAAQ,KACjCY,EAAaF,EAAS,IAAIT,CAAQ,EAIxC,GAAIW,IAAe,OAAW,CAC5B,GAAIA,EAAaV,EAAQ,IAAI,EAAG,OAEhCQ,EAAS,OAAOT,CAAQ,CAC1B,CAGA,IAAMY,EAAUF,EAAS,IAAIV,CAAQ,EAErC,GAAIY,IAAY,OAAW,OAAOA,EAElC,IAAMC,EAAWC,GAAoBf,EAASC,CAAQ,EAAE,QAAQ,IAAM,CACpEU,EAAS,OAAOV,CAAQ,CAC1B,CAAC,EAED,OAAAU,EAAS,IAAIV,EAAUa,CAAQ,EAExBA,CACT,CAxEsBxB,EAAAS,GAAA,iBAiFtB,eAAegB,GACbf,EACAC,EACmC,CACnC,GAAM,CAAE,QAAAC,CAAQ,EAAIF,EACdgB,EAAYd,EAAQ,IAAI,EAC1Be,EAEJ,GAAI,CACFA,EAAU,MAAMC,GAAoBjB,EAAUe,EAAWd,CAAO,CAClE,OAASiB,EAAO,CACdC,GAAgBpB,EAASC,CAAQ,EACjC,MAAMD,EAAQ,KAAK,CACjB,KAAM,0BACN,KAAMZ,GAAOa,CAAQ,EACrB,OAAQoB,GAAcF,CAAK,CAC7B,CAAC,EAED,MACF,CAIA,IAAMG,EAASL,EAAQ,cACjBM,EACJD,IAAW,OACPpB,EAAQ,sBACR,KAAK,IAAIoB,EAAS,IAAMpB,EAAQ,qBAAqB,EAE3D,OAAIqB,EAAU,GACZ,MAAMrB,EAAQ,MAAM,wBAClBD,EACAgB,EAAQ,OACRD,EAAYO,CACd,EAGKN,EAAQ,MACjB,CAtCe3B,EAAAyB,GAAA,uBA8Cf,SAASK,GAAgBpB,EAAyBC,EAAwB,CACxE,GAAM,CAAE,QAAAC,CAAQ,EAAIF,EACd,CAAE,SAAAU,CAAS,EAAIV,EAAQ,KAE7B,GAAIE,EAAQ,0BAA4B,EAIxC,KAFAQ,EAAS,OAAOT,CAAQ,EAEjBS,EAAS,MAAQR,EAAQ,OAAO,wBAAwB,CAC7D,IAAMsB,EAASd,EAAS,KAAK,EAAE,KAAK,EAEpC,GAAIc,EAAO,OAAS,GAAM,MAE1Bd,EAAS,OAAOc,EAAO,KAAK,CAC9B,CAEAd,EAAS,IAAIT,EAAUC,EAAQ,IAAI,EAAIA,EAAQ,uBAAuB,EACxE,CAjBSZ,EAAA8B,GAAA,mBC/LF,SAASK,GAAWC,EAAkBC,EAAkC,CAC7E,IAAMC,EAASF,EAAQ,QAAQ,IAAI,QAAQ,EAE3C,GAAIE,IAAW,KAEf,QAAWC,KAAQD,EAAO,MAAM,GAAG,EAAG,CACpC,IAAME,EAAQD,EAAK,QAAQ,GAAG,EAE9B,GAAIC,IAAU,IAAMD,EAAK,MAAM,EAAGC,CAAK,EAAE,KAAK,IAAMH,EAClD,OAAOE,EAAK,MAAMC,EAAQ,CAAC,EAAE,KAAK,CAEtC,CAGF,CAdgBC,EAAAN,GAAA,cA0BT,SAASO,GACdL,EACAM,EACAC,EACQ,CACR,MAAO,GAAGP,CAAI,IAAIM,CAAK,qBAAqBC,CAAa,kCAC3D,CANgBH,EAAAC,GAAA,cAeT,SAASG,GAAgBR,EAAsB,CACpD,MAAO,GAAGA,CAAI,sDAChB,CAFgBI,EAAAI,GAAA,mBAcT,SAASC,EAAkBC,EAAgBC,EAAwB,CACxE,OAAOD,EAASE,EAAU,WAAWD,CAAM,EAAE,EAAE,MAAM,EAAG,EAAE,CAC5D,CAFgBP,EAAAK,EAAA,qBCnDT,SAASI,EAAYC,EAAiC,CAC3D,IAAMC,EAAS,IAAI,IAEnB,OAAW,CAACC,EAAKC,CAAK,IAAKH,EAAQ,CACjC,IAAMI,EAASH,EAAO,IAAIC,CAAG,EAEzBE,EAAQA,EAAO,KAAKD,CAAK,EACxBF,EAAO,IAAIC,EAAK,CAACC,CAAK,CAAC,CAC9B,CAEA,OAAOF,CACT,CAXgBI,EAAAN,EAAA,eAgCT,SAASO,EAAOC,EAAgBC,EAAkC,CACvE,IAAMC,EAAQF,EAAO,IAAIC,CAAI,IAAI,CAAC,EAElC,OAAOC,IAAU,QAAaA,IAAU,GAAK,OAAYA,CAC3D,CAJgBC,EAAAJ,EAAA,UAgBT,SAASK,EACdJ,EACAK,EACU,CACV,OAAOA,EAAQ,OAAQJ,IAAUD,EAAO,IAAIC,CAAI,GAAG,QAAU,GAAK,CAAC,CACrE,CALgBE,EAAAC,EAAA,mBCvDhB,SAASE,GACPC,EACiD,CACjD,IAAMC,EAAQD,EAAM,QAAQ,KAAK,EAEjC,GAAIC,IAAU,GAAI,OAElB,IAAMC,EAAYF,EAAM,QAAQ,IAAKC,EAAQ,CAAC,EACxCE,EAAMD,IAAc,GAAKF,EAAM,OAASE,EAE9C,MAAO,CACL,UAAWF,EAAM,MAAM,EAAGG,CAAG,EAAE,YAAY,EAC3C,KAAMH,EAAM,MAAMG,CAAG,CACvB,CACF,CAdSC,EAAAL,GAAA,kBA0BF,SAASM,GAAaC,EAAoBC,EAAwB,CACvE,GAAIA,IAAUD,EAAY,MAAO,GAEjC,IAAME,EAAIT,GAAeO,CAAU,EAC7BG,EAAIV,GAAeQ,CAAK,EAE9B,OACEC,IAAM,QACNC,IAAM,QACND,EAAE,YAAcC,EAAE,WAClBD,EAAE,OAASC,EAAE,IAEjB,CAZgBL,EAAAC,GAAA,gBClBT,SAASK,GACdC,EACAC,EACAC,EACAC,EAC+B,CAC/B,GAAIH,IAAc,OAAW,OAAOE,EAEpC,GAAIF,EAAU,OAASG,EAAW,OAElC,IAAMC,EAAQ,CAAC,GAAG,IAAI,IAAIJ,EAAU,MAAM,GAAG,EAAE,OAAQK,GAAMA,EAAE,OAAS,CAAC,CAAC,CAAC,EAE3E,OAAID,EAAM,SAAW,EAAG,OAEPA,EAAM,MAAOC,GAAMC,GAAaD,CAAC,GAAKJ,EAAU,SAASI,CAAC,CAAC,EAE1DD,EAAQ,MAC5B,CAjBgBG,EAAAR,GAAA,iBCKhB,IAAMS,GAAuB,CAC3B,YACA,eACA,gBACA,iBACA,wBACA,WACA,QACA,QACA,QACF,EAsBA,eAAsBC,GACpBC,EACAC,EACmB,CACnB,GAAM,CAAE,QAAAC,CAAQ,EAAIF,EACd,CAAE,OAAAG,CAAO,EAAID,EAEnB,GAAI,CAAE,MAAMF,EAAQ,QAAQC,EAAS,WAAW,EAC9C,OAAOD,EAAQ,UAAUC,EAAS,cAAc,EAGlD,IAAMG,EAASC,EAAY,IAAI,IAAIJ,EAAQ,GAAG,EAAE,YAAY,EACtDK,EAASC,EAAA,MACbC,EACAC,EACAC,EAAoD,CAAC,KAErD,MAAMV,EAAQ,KAAK,CACjB,KAAM,wBACN,OAAAQ,EACA,GAAIC,IAAa,OAAY,CAAE,SAAAA,CAAS,EAAI,CAAC,CAC/C,CAAC,EAEMT,EAAQ,UAAUC,EAASO,EAAQE,CAAK,GAXlC,UAiBf,GAAIC,EAAgBP,EAAQN,EAAoB,EAAE,OAAS,EACzD,OAAOQ,EAAO,oBAAoB,EAGpC,IAAMG,EAAWG,EAAOR,EAAQ,WAAW,EACrCS,EACJJ,IAAa,OAAY,OAAY,MAAMK,GAAcd,EAASS,CAAQ,EAE5E,GAAIA,IAAa,QAAaI,IAAW,OACvC,OAAOP,EAAO,gBAAgB,EAKhC,IAAMS,EAAOH,EAAOR,EAAQ,cAAc,EACpC,CAACY,CAAc,EACnBH,EAAO,aAAa,SAAW,EAAIA,EAAO,aAAe,CAAC,EACtDI,EAAcF,GAAQC,EAE5B,GACEC,IAAgB,QAChB,CAACC,GAAsBL,EAAO,aAAcI,EAAad,CAAM,EAE/D,OAAOG,EAAO,8BAA+BG,CAAQ,EAGvD,IAAMU,EAAQP,EAAOR,EAAQ,OAAO,EAEpC,GAAIe,IAAU,QAAaA,EAAM,OAAShB,EAAO,YAC/C,OAAOG,EAAO,kBAAmBG,EAAU,CACzC,OAAQ,2BAA2BN,EAAO,WAAW,aACvD,CAAC,EAGH,GAAIS,EAAOR,EAAQ,eAAe,IAAM,OACtC,OAAOE,EAAO,4BAA6BG,EAAU,CACnD,OAAQ,6CACV,CAAC,EAGH,IAAMW,EAAgBR,EAAOR,EAAQ,gBAAgB,EAErD,GAAIgB,IAAkB,QAAa,CAACC,GAAsBD,CAAa,EACrE,OAAOd,EAAO,kBAAmBG,EAAU,CACzC,OAAQ,+DACV,CAAC,EAGH,GAAIG,EAAOR,EAAQ,uBAAuB,IAAM,OAC9C,OAAOE,EAAO,kBAAmBG,EAAU,CACzC,OAAQ,iDACV,CAAC,EAGH,IAAMa,EAAWV,EAAOR,EAAQ,UAAU,EAE1C,GAAIkB,IAAa,QAAa,CAACC,GAAarB,EAAQ,SAAUoB,CAAQ,EACpE,OAAOhB,EAAO,iBAAkBG,EAAU,CACxC,OAAQ,wCAAwCP,EAAQ,QAAQ,EAClE,CAAC,EAIH,IAAMsB,EAAYX,EAAO,QAAUX,EAAQ,OACrCuB,EAAWvB,EAAQ,cAAc,OAAQwB,GAAMF,EAAU,SAASE,CAAC,CAAC,EACpEC,EAASC,GACbxB,EAAO,IAAI,OAAO,IAAI,CAAC,EACvBoB,EACAC,EAAS,OAAS,EAAIA,EAAWD,EACjCrB,EAAO,WACT,EAEA,GAAIwB,IAAW,OACb,OAAOrB,EAAO,gBAAiBG,CAAQ,EAKzC,GAAIG,EAAOR,EAAQ,QAAQ,GAAG,MAAM,GAAG,EAAE,SAAS,MAAM,EACtD,OAAOE,EAAO,mBAAoBG,CAAQ,EAI5C,IAAMoB,EAAS3B,EAAQ,gBAEvB,GAAI2B,IAAW,OAAW,CACxB,IAAMC,EAAU,MAAMC,GAAO,IAC3BF,EAAO,CACL,OAAQG,EAAWnB,CAAM,EACzB,YAAAI,EACA,OAAQe,EAAWL,CAAM,EACzB,QAAA1B,CACF,CAAC,CACH,EAEA,GAAI,CAAC6B,EAAQ,QACX,OAAOxB,EACL,gBACAG,EACAqB,EAAQ,SAAW,OAAY,CAAC,EAAI,CAAE,aAAcA,EAAQ,MAAO,CACrE,CAEJ,CAEA,IAAMG,EAASC,EAAY,EACrBC,EAAUD,EAAY,EACtBE,EAAmBF,EAAY,EAC/BG,EAAQH,EAAY,EACpBI,EAAgC,CACpC,GAAIL,EACJ,SAAUpB,EAAO,SACjB,WAAYA,EAAO,KACnB,YAAAI,EACA,cAAAG,EAEA,SAAUlB,EAAQ,SAClB,OAAAyB,EACA,qBAAsBS,EACtB,cAAeC,EACf,YAAaE,EAAUJ,CAAO,EAC9B,aAActB,EAAO,YAAY,SAAS,eAAe,GAAK,GAC9D,UAAWX,EAAQ,IAAI,EAAIA,EAAQ,aACnC,GAAIW,EAAO,aAAe,OACtB,CAAE,WAAYA,EAAO,UAAW,EAChC,CAAC,EACL,GAAIM,IAAU,OAAY,CAAE,MAAAA,CAAM,EAAI,CAAC,CACzC,EAEA,MAAMjB,EAAQ,MAAM,wBAAwBoC,CAAO,EAEnD,IAAME,EAAW,MAAMtC,EAAQ,iBAAiB,iBAAiB,CAC/D,MAAO+B,EACP,cAAeQ,GAAcL,CAAgB,EAC7C,YAAalC,EAAQ,YACrB,MAAAmC,CACF,CAAC,EAED,aAAMrC,EAAQ,KAAK,CACjB,KAAM,wBACN,SAAUa,EAAO,SACjB,OAAAc,CACF,CAAC,EAEMe,GAASF,EAAU,CACxBG,GACEC,EAAkB1C,EAAQ,aAAc+B,CAAM,EAC9CE,EACA,KAAK,KAAKjC,EAAQ,aAAe,GAAI,CACvC,CACF,CAAC,CACH,CAnLsBK,EAAAR,GAAA,mBCpDtB,OAAS,cAAA8C,OAAkB,cCmB3B,SAASC,GAAaC,EAAqB,CACzC,GAAI,CACF,OAAO,IAAI,IAAIA,CAAG,EAAE,IACtB,MAAQ,CACN,MAAO,EACT,CACF,CANSC,EAAAF,GAAA,gBAuCF,SAASG,GACdC,EACAC,EACAC,EACAC,EACAC,EAA4B,CAAC,EACnB,CACV,GAAM,CAAE,QAAAC,CAAQ,EAAIL,EACdM,EAAOD,EAAQ,kBACnBE,EAAW,CACT,SAAUL,EAAQ,SAClB,WAAYA,EAAQ,WACpB,YAAaA,EAAQ,YACrB,aAAcM,GAAeN,EAAQ,WAAW,EAChD,mBAAoBO,GAAsBP,EAAQ,WAAW,EAC7D,OAAQA,EAAQ,OAChB,kBAAmB,CAAE,GAAGG,EAAQ,iBAAkB,EAClD,SAAUH,EAAQ,SAClB,WAAYG,EAAQ,MAAM,QAC1B,aAAc,CACZ,CAACK,EAAe,EAAE,EAAGR,EAAQ,GAC7B,CAACQ,EAAe,IAAI,EAAGP,CACzB,EACA,cAAeO,EAAe,SAC9B,QAASC,GAAaV,EAAQ,QAAQ,IAAI,iBAAiB,CAAC,EAC5D,GAAIC,EAAQ,aAAe,OACvB,CAAE,WAAYA,EAAQ,UAAW,EACjC,CAAC,EACL,GAAIA,EAAQ,aAAe,oBACvB,CAAE,WAAYN,GAAaM,EAAQ,QAAQ,CAAE,EAC7C,CAAC,EACL,GAAIA,EAAQ,cAAgB,OACxB,CAAE,mBAAoBA,EAAQ,WAAY,EAC1C,CAAC,EACL,GAAIA,EAAQ,UAAY,OAAY,CAAE,QAASA,EAAQ,OAAQ,EAAI,CAAC,EACpE,GAAIE,EAAO,cAAgB,OACvB,CAAE,YAAaA,EAAO,WAAY,EAClC,CAAC,EACL,GAAIA,EAAO,YAAc,OACrB,CAAE,UAAWA,EAAO,SAAU,EAC9B,CAAC,CACP,CAAC,CACH,EACMQ,EAASC,GAASX,EAAQ,WAAW,EACrCY,EAAU,CAAC,GAAIV,EAAO,YAAc,CAAC,CAAE,EAE7C,OAAIA,EAAO,UAAY,QACrBU,EAAQ,KACNC,GACEC,EAAkBX,EAAQ,aAAcH,EAAQ,EAAE,EAClDE,EAAO,QACP,KAAK,KAAKC,EAAQ,aAAe,GAAI,CACvC,CACF,EAGKY,GAAKb,EAAO,QAAU,IAAKE,EAAM,CACtC,kBAAmBM,IAAW,OAAY,CAAC,EAAI,CAACA,CAAM,EACtD,WAAYE,EACZ,aAAcT,EAAQ,YACxB,CAAC,CACH,CA7DgBP,EAAAC,GAAA,uBChDhB,IAAMmB,GAAuB,IAW7B,SAASC,GAAgBC,EAAqC,CAC5D,OACE,OAAOA,GAAY,UACnBA,IAAY,IACZA,EAAQ,QAAUF,IAClBE,EAAQ,KAAK,IAAMA,GACnBA,EAAQ,UAAU,KAAK,IAAMA,GAC7B,CAACC,GAAoBD,CAAO,CAEhC,CATSE,EAAAH,GAAA,mBAoBT,SAASI,GACPC,EACwD,CACxD,GAAI,OAAOA,GAAU,UAAYA,IAAU,KAAM,OAEjD,IAAMJ,EAAmB,QAAQ,IAAII,EAAO,SAAS,EAC/CC,EAAuB,QAAQ,IAAID,EAAO,aAAa,EAE7D,MAAO,CAAE,QAAAJ,EAAS,YAAAK,CAAY,CAChC,CATSH,EAAAC,GAAA,cA4BT,eAAsBG,GACpBC,EACAC,EACAC,EAIA,CACA,GAAM,CAAE,WAAAC,EAAY,aAAAC,EAAc,OAAAC,CAAO,EAAIL,EAAQ,QAC/CM,EAASL,EAAM,QAAU,CAAC,EAC5BR,EAAmBQ,EAAM,QACzBM,EAAmBN,EAAM,YAE7B,GAAIE,IAAe,OAAW,CAC5B,IAAIK,EAEJ,GAAI,CACFA,EAASZ,GACP,MAAMO,EACJM,EAAW,CACT,QAASR,EAAM,QACf,OAAAK,EACA,GAAIL,EAAM,cAAgB,OACtB,CAAE,YAAaA,EAAM,WAAY,EACjC,CAAC,CACP,CAAC,CACH,CACF,CACF,MAAQ,CACN,MAAO,CAAE,GAAI,EAAM,CACrB,CAEA,GAAIO,IAAW,OAAW,MAAO,CAAE,GAAI,EAAM,EAE7Cf,EAAUe,EAAO,QACjBD,EAAUC,EAAO,WACnB,CAEA,GAAI,CAAChB,GAAgBC,CAAO,EAAG,MAAO,CAAE,GAAI,EAAM,EAGlD,IAAMK,EACJS,IAAY,OACR,OACAG,EAAgBH,EAASF,EAAO,gBAAgB,EAChDM,EACJb,IAAgB,OAAY,CAAE,QAAAL,CAAQ,EAAI,CAAE,QAAAA,EAAS,YAAAK,CAAY,EAEnE,GAAIM,IAAiB,OAAW,CAC9B,IAAMQ,EAAU,MAAMC,GAAO,IAC3BT,EAAaK,EAAW,CAAE,GAAGE,EAAQ,OAAAL,EAAQ,OAAAJ,CAAO,CAAC,CAAC,CACxD,EAEA,GAAI,CAACU,EAAQ,QACX,OAAOA,EAAQ,SAAW,OACtB,CAAE,GAAI,EAAM,EACZ,CAAE,GAAI,GAAO,aAAcA,EAAQ,MAAO,CAElD,CAEA,MAAO,CAAE,GAAI,GAAM,OAAAD,CAAO,CAC5B,CA7DsBhB,EAAAI,GAAA,iBF9CtB,IAAMe,GAAsB,CAC1B,QACA,OACA,QACA,oBACA,YACA,KACF,EAmBA,eAAsBC,GACpBC,EACAC,EACmB,CACnB,GAAM,CAAE,QAAAC,CAAQ,EAAIF,EAEpB,GAAI,CAAE,MAAMA,EAAQ,QAAQC,EAAS,UAAU,EAC7C,OAAOD,EAAQ,UAAUC,EAAS,cAAc,EAGlD,IAAME,EAASC,EAAY,IAAI,IAAIH,EAAQ,GAAG,EAAE,YAAY,EAE5D,GAAII,EAAgBF,EAAQL,EAAmB,EAAE,OAAS,EACxD,OAAOE,EAAQ,UAAUC,EAAS,oBAAoB,EAGxD,IAAMK,EAASC,EAAOJ,EAAQ,OAAO,EAErC,GAAIG,IAAW,OAAW,OAAON,EAAQ,UAAUC,EAAS,YAAY,EAExE,IAAMO,EAAaC,EAAkBP,EAAQ,aAAcI,CAAM,EAC3DI,EAAaC,GAAgBH,CAAU,EACvCI,EAAW,MAAMV,EAAQ,MAAM,wBAAwBI,CAAM,EAEnE,GAAIM,IAAa,QAAaC,EAAWD,EAAS,UAAWV,EAAQ,IAAI,CAAC,EACxE,OAAOF,EAAQ,UAAUC,EAAS,YAAY,EAGhD,IAAMa,EAAUC,GAAWd,EAASO,CAAU,EAE9C,GACEM,IAAY,QACZ,CAACE,GAAkBC,EAAUH,CAAO,EAAGF,EAAS,WAAW,EAE3D,aAAMZ,EAAQ,KAAK,CAAE,KAAM,wBAAyB,KAAM,UAAW,CAAC,EAE/DA,EAAQ,UAAUC,EAAS,mBAAoB,CACpD,WAAY,CAACS,CAAU,CACzB,CAAC,EAGH,IAAMQ,EAAU,MAAMhB,EAAQ,MAAM,4BAA4BI,CAAM,EAEtE,GACEY,IAAY,QACZL,EAAWK,EAAQ,UAAWhB,EAAQ,IAAI,CAAC,GAC3CgB,EAAQ,cAAgBN,EAAS,YAEjC,OAAOZ,EAAQ,UAAUC,EAAS,YAAY,EAGhD,IAAMkB,EAAgBZ,EAAOJ,EAAQ,OAAO,EAK5C,GAAIgB,IAAkB,OAAW,CAC/B,IAAMC,EAAYD,IAAkB,gBAEpC,aAAMnB,EAAQ,KAAK,CACjB,KAAM,0BACN,SAAUkB,EAAQ,SAClB,OAAQE,EAAY,mBAAqB,yBAC3C,CAAC,EAEMC,GACLC,GAAcJ,EAAQ,YAAa,CACjC,MAAOE,EAAY,gBAAkB,eACrC,kBAAmBA,EACf,yCACA,uDACJ,MAAOF,EAAQ,MACf,IAAKhB,EAAQ,MACf,CAAC,EACD,CAACQ,CAAU,CACb,CACF,CAEA,IAAMa,EAAOhB,EAAOJ,EAAQ,MAAM,EAElC,GAAIoB,IAAS,OACX,OAAOvB,EAAQ,UAAUC,EAAS,qBAAsB,CACtD,WAAY,CAACS,CAAU,CACzB,CAAC,EAGH,IAAMc,EAAMjB,EAAOJ,EAAQ,KAAK,EAC5BsB,EAEJ,GAAI,CACFA,EAAQ,MAAMvB,EAAQ,iBAAiB,aAAa,CAClD,KAAAqB,EACA,aAAcL,EAAQ,qBACtB,YAAahB,EAAQ,YACrB,MAAOgB,EAAQ,cACf,GAAIM,IAAQ,OAAY,CAAE,OAAQA,CAAI,EAAI,CAAC,CAC7C,CAAC,CACH,OAASE,EAAO,CACd,aAAM1B,EAAQ,KAAK,CACjB,KAAM,0BACN,SAAUkB,EAAQ,SAClB,OAAQQ,aAAiB,MAAQA,EAAM,KAAO,iBAChD,CAAC,EAEM1B,EAAQ,UAAUC,EAAS,wBAAyB,CACzD,WAAY,CAACS,CAAU,CACzB,CAAC,CACH,CAEA,IAAMiB,EAAkBC,EAAA,SAAY,CAC9BH,EAAM,eAAiB,QACzB,MAAMI,GAAoB7B,EAASyB,EAAM,YAAY,CAEzD,EAJwB,mBAMxB,GAAI,OAAOA,EAAM,SAAY,UAAYA,EAAM,QAAQ,SAAW,EAChE,aAAME,EAAgB,EACtB,MAAM3B,EAAQ,KAAK,CACjB,KAAM,0BACN,SAAUkB,EAAQ,SAClB,OAAQ,iBACV,CAAC,EAEMlB,EAAQ,UAAUC,EAAS,wBAAyB,CACzD,WAAY,CAACS,CAAU,CACzB,CAAC,EAGH,IAAMoB,EAAS,CACb,SAAUZ,EAAQ,SAClB,KAAMA,EAAQ,WACd,GAAIA,EAAQ,aAAe,OACvB,CAAE,WAAYA,EAAQ,UAAW,EACjC,CAAC,CACP,EACMa,EAAW,MAAMC,GAAchC,EAASyB,EAAOK,CAAM,EAE3D,GAAI,CAACC,EAAS,GACZ,aAAMJ,EAAgB,EACtB,MAAM3B,EAAQ,KAAK,CACjB,KAAM,0BACN,SAAUkB,EAAQ,SAClB,OAAQ,uBACV,CAAC,EAEMlB,EAAQ,UAAUC,EAAS,wBAAyB,CACzD,WAAY,CAACS,CAAU,EACvB,GAAIqB,EAAS,eAAiB,OAC1B,CAAE,aAAcA,EAAS,YAAa,EACtC,CAAC,CACP,CAAC,EAGH,GAAM,CAAE,OAAAE,CAAO,EAAIF,EACfG,EAEJ,GAAIhC,EAAQ,oBAAsB,OAChC,GAAI,CACFgC,EAAUC,EACR,MAAMjC,EAAQ,kBACZ,OAAO,OAAO,CACZ,GAAGkC,EAAW,CACZ,QAASH,EAAO,QAChB,OAAAH,EACA,OAAQ,CAAC,GAAGZ,EAAQ,MAAM,CAC5B,CAAC,EACD,QAAAjB,CACF,CAAC,CACH,EACAC,EAAQ,OAAO,oBACf,mBACF,CACF,MAAQ,CACN,aAAMyB,EAAgB,EAEf3B,EAAQ,UAAUC,EAAS,eAAgB,CAChD,WAAY,CAACS,CAAU,CACzB,CAAC,CACH,CAGF,IAAM2B,EAAMnC,EAAQ,IAAI,EAClBoC,EAAUC,GAAW,EACrBC,EAAYC,EAAY,EACxB,CAAE,OAAAC,CAAO,EAAIxC,EACbyC,EACJlB,EAAM,YAAc,QAAa,OAAO,SAASA,EAAM,SAAS,EAC5D,KAAK,IACH,KAAK,IAAIA,EAAM,UAAW,CAAC,EAC3BmB,EACF,EACA,OACAC,EAA0B,CAC9B,GAAIJ,EAAY,EAChB,QAAAH,EACA,SAAUpB,EAAQ,SAClB,WAAYA,EAAQ,WACpB,YAAaA,EAAQ,YACrB,cAAeA,EAAQ,cACvB,SAAUA,EAAQ,SAClB,OAAQA,EAAQ,OAChB,QAASe,EAAO,QAChB,SAAUhB,EAAUuB,CAAS,EAC7B,YAAatB,EAAQ,YACrB,UAAWmB,EAAMnC,EAAQ,aACzB,GAAIgB,EAAQ,eAAiB,OACzB,CAAE,aAAcA,EAAQ,YAAa,EACrC,CAAC,EACL,GAAIA,EAAQ,aAAe,OACvB,CAAE,WAAYA,EAAQ,UAAW,EACjC,CAAC,EACL,GAAIA,EAAQ,QAAU,OAAY,CAAE,MAAOA,EAAQ,KAAM,EAAI,CAAC,EAC9D,GAAIe,EAAO,cAAgB,OACvB,CAAE,YAAaA,EAAO,WAAY,EAClC,CAAC,EACL,GAAIC,IAAY,OAAY,CAAE,QAAAA,CAAQ,EAAI,CAAC,EAC3C,GAAIT,EAAM,eAAiB,OACvB,CACE,2BAA4B,MAAMiB,EAAO,KACvCjB,EAAM,aACNa,CACF,CACF,EACA,CAAC,EACL,GAAIb,EAAM,cAAgB,QAAakB,IAAa,OAChD,CACE,0BAA2B,MAAMD,EAAO,KACtCjB,EAAM,YACN,GAAGa,CAAO,SACZ,EACA,6BAA8BD,EAAMM,EAAW,GACjD,EACA,CAAC,CACP,EACIG,EAIJ,GAAI,CACFA,EAAWC,GAAoB/C,EAASC,EAAS4C,EAASL,EAAW,CACnE,QAAA1B,EACA,WAAY,CAACJ,CAAU,CACzB,CAAC,CACH,OAASgB,EAAO,CACd,YAAMC,EAAgB,EAChBD,CACR,CAEA,aAAMxB,EAAQ,MAAM,kBAAkB2C,CAAO,EAEtCC,CACT,CA3PsBlB,EAAA7B,GAAA,kBG5Cf,IAAMiD,EAAN,cAAgC,KAAM,CAL7C,MAK6C,CAAAC,EAAA,0BAE3C,aAAc,CACZ,MAAM,wCAAwC,EAC9C,KAAK,KAAO,mBACd,CACF,ECAO,SAASC,GAAeC,EAAkBC,EAA2B,CAC1E,IAAMC,EAAQF,EAAQ,QAAQ,IAAI,cAAc,EAEhD,GAAIE,IAAU,KAAM,MAAO,GAE3B,GAAM,CAACC,CAAI,EAAID,EAAM,MAAM,GAAG,EAE9B,OAAQC,GAAQ,IAAI,KAAK,EAAE,YAAY,IAAMF,CAC/C,CARgBG,EAAAL,GAAA,kBAqBhB,eAAsBM,GACpBL,EACAM,EACiB,CACjB,IAAMC,EAAW,OAAOP,EAAQ,QAAQ,IAAI,gBAAgB,CAAC,EAE7D,GAAI,OAAO,SAASO,CAAQ,GAAKA,EAAWD,EAC1C,MAAM,IAAIE,EAGZ,GAAIR,EAAQ,OAAS,KAAM,MAAO,GAElC,IAAMS,EAAST,EAAQ,KAAK,UAAU,EAChCU,EAAuB,CAAC,EAC1BC,EAAQ,EAEZ,OAAS,CACP,IAAMC,EAAQ,MAAMH,EAAO,KAAK,EAEhC,GAAIG,EAAM,KAAM,MAEhB,IAAMC,EAAiBD,EAAM,MAG7B,GAAI,EAAEC,aAAiB,YAAa,MAAM,IAAI,UAAU,mBAAmB,EAI3E,GAFAF,GAASE,EAAM,WAEXF,EAAQL,EACV,YAAMG,EAAO,OAAO,EACd,IAAID,EAGZE,EAAO,KAAKG,CAAK,CACnB,CAEA,OAAO,OAAO,OAAOH,CAAM,EAAE,SAAS,MAAM,CAC9C,CArCsBN,EAAAC,GAAA,YCEtB,IAAMS,GAAqC,OAAO,OAAOC,CAAc,EAGjEC,GAAoB,GAGpBC,GAAmB,IASzB,SAASC,GAAYC,EAAsC,CACzD,IAAMC,EAA6B,CAAC,EAEpC,OAAW,CAACC,EAAMC,CAAM,IAAKH,EAAM,CACjC,GAAM,CAACI,CAAK,EAAID,EAEZ,CAACR,GAAgB,SAASO,CAAI,GAAKE,IAAU,QAC/CH,EAAO,KAAK,CAACC,EAAME,CAAK,CAAC,CAE7B,CAGA,OAAO,OAAO,YAAYH,CAAM,CAClC,CAbSI,EAAAN,GAAA,eAsBT,SAASO,GACPC,EAC8C,CAC9C,GAAI,CAACC,EAASD,CAAK,EAAG,OAEtB,IAAME,EAAU,OAAO,QAAQF,CAAK,EAEpC,GAAIE,EAAQ,SAAW,GAAKA,EAAQ,OAASZ,GAC3C,OAGF,IAAMa,EAA6B,CAAC,EAEpC,OAAW,CAACR,EAAMS,CAAO,IAAKF,EAAS,CACrC,GACE,OAAOE,GAAY,UACnBA,IAAY,IACZA,EAAQ,OAASb,IACjBI,EAAK,OAASJ,GAEd,OAGFY,EAAO,KAAK,CAACR,EAAMS,CAAO,CAAC,CAC7B,CAEA,OAAO,OAAO,YAAYD,CAAM,CAClC,CA3BSL,EAAAC,GAAA,mBA4CT,SAASM,GAAWC,EAAqC,CACvD,MAAO,CACL,SAAUA,EAAQ,SAClB,KAAMA,EAAQ,WACd,GAAIA,EAAQ,aAAe,OACvB,CAAE,WAAYA,EAAQ,UAAW,EACjC,CAAC,CACP,CACF,CARSR,EAAAO,GAAA,cAmBT,eAAeE,GACbC,EACAF,EACAZ,EACwB,CACxB,IAAMe,EAAOD,EAAQ,QAAQ,kBAE7B,GAAIC,IAAS,OAAW,MAAO,CAAE,KAAM,WAAY,UAAW,MAAU,EAExE,IAAIC,EAEJ,GAAI,CACFA,EAAS,MAAMD,EACbE,EAAW,CACT,KAAM,CAAE,GAAGjB,CAAO,EAClB,QAASY,EAAQ,QACjB,QAASA,EAAQ,QACjB,OAAQD,GAAWC,CAAO,EAC1B,OAAQ,CAAC,GAAGA,EAAQ,MAAM,CAC5B,CAAC,CACH,CACF,MAAQ,CACN,MAAO,CAAE,KAAM,QAAS,CAC1B,CAEA,GAAI,CAACL,EAASS,CAAM,EAAG,MAAO,CAAE,KAAM,QAAS,EAE/C,OAAQA,EAAO,GAAI,CACjB,IAAK,GACH,GAAI,CACF,MAAO,CACL,KAAM,WACN,UAAWE,EACTF,EAAO,UACPF,EAAQ,QAAQ,OAAO,eACvB,mBACF,CACF,CACF,MAAQ,CACN,MAAO,CAAE,KAAM,QAAS,CAC1B,CACF,IAAK,GAAO,CACV,IAAMK,EAAcd,GAAgBW,EAAO,WAAW,EAEtD,OAAOG,IAAgB,OACnB,CAAE,KAAM,QAAS,EACjB,CAAE,KAAM,UAAW,YAAAA,CAAY,CACrC,CACA,QACE,MAAO,CAAE,KAAM,QAAS,CAC5B,CACF,CAnDef,EAAAS,GAAA,cA8Ef,eAAsBO,GACpBN,EACAO,EACmB,CACnB,GAAM,CAAE,QAAAC,CAAQ,EAAIR,EAEpB,GAAI,CAAE,MAAMA,EAAQ,QAAQO,EAAS,SAAS,EAC5C,OAAOP,EAAQ,UAAUO,EAAS,cAAc,EAOlD,IAAME,EAASF,EAAQ,QAAQ,IAAI,QAAQ,EAE3C,GAAIE,IAAW,MAAQA,IAAW,QAAUA,IAAWD,EAAQ,aAC7D,OAAOR,EAAQ,UAAUO,EAAS,qBAAqB,EAGzD,GAAI,CAACG,GAAeH,EAASI,EAAe,EAC1C,OAAOX,EAAQ,UAAUO,EAAS,wBAAwB,EAG5D,IAAItB,EAEJ,GAAI,CACFA,EAAO2B,EACL,IAAI,gBAAgB,MAAMC,GAASN,EAASC,EAAQ,OAAO,SAAS,CAAC,CACvE,CACF,OAASM,EAAO,CACd,GAAIA,aAAiBC,EACnB,OAAOf,EAAQ,UAAUO,EAAS,qBAAqB,EAGzD,MAAMO,CACR,CAEA,GAAIE,EAAgB/B,EAAML,EAAe,EAAE,OAAS,EAClD,OAAOoB,EAAQ,UAAUO,EAAS,qBAAqB,EAGzD,IAAMU,EAAgBC,EAAOjC,EAAMJ,EAAe,EAAE,EAC9CsC,EAAYD,EAAOjC,EAAMJ,EAAe,IAAI,EAElD,GAAIoC,IAAkB,QAAaE,IAAc,OAC/C,OAAOnB,EAAQ,UAAUO,EAAS,qBAAqB,EAGzD,GAAM,CAAE,MAAAa,EAAO,OAAAC,CAAO,EAAIb,EACpBc,EAAaC,EAAkBf,EAAQ,aAAcS,CAAa,EAClEO,EAAaC,GAAgBH,CAAU,EACvCI,EAAUC,GAAWpB,EAASe,CAAU,EACxCM,EAAQtC,EAAA,IACZU,EAAQ,UAAUO,EAAS,sBAAuB,CAChD,WAAY,CAACiB,CAAU,CACzB,CAAC,EAHW,SAMRK,EACJH,IAAY,OACR,OACAI,EAAU,GAAGA,EAAUJ,CAAO,CAAC,IAAII,EAAUX,CAAS,CAAC,EAAE,EACzDY,EAASzC,EAAA,SAA+B,CAC5C,GAAIuC,IAAc,QAAaT,EAAM,oBAAsB,OACzD,OAAOQ,EAAM,EAGf,IAAMI,EAAS,MAAMZ,EAAM,kBAAkBS,CAAS,EAEtD,GAAIG,IAAW,OAAW,OAAOJ,EAAM,EAEvC,IAAIK,EAEJ,GAAI,CACFA,EAAU,KAAK,MAAM,MAAMZ,EAAO,KAAKW,EAAQH,CAAS,CAAC,CAC3D,MAAQ,CACN,OAAOD,EAAM,CACf,CAGA,MACE,CAACnC,EAASwC,CAAO,GACjB,OAAOA,EAAQ,UAAa,UAC5B,OAAOA,EAAQ,WAAc,UAC7BC,EAAWD,EAAQ,UAAWzB,EAAQ,IAAI,CAAC,EAEpCoB,EAAM,EAGRO,GAASF,EAAQ,SAAU,CAACT,CAAU,EAAG,GAAG,CACrD,EA5Be,UA6BT1B,EAAU,MAAMsB,EAAM,kBAAkBH,CAAa,EAE3D,GAAInB,IAAY,OAAW,OAAOiC,EAAO,EAEzC,IAAMK,EAAkB9C,EAAA,IACtB+C,GACErC,EACAF,EAAQ,2BACRA,EAAQ,OACV,EALsB,mBAMlBwC,EAAUhD,EAAA,SAA2B,CACzC,MAAM8B,EAAM,sBAAsBH,CAAa,EAC/C,MAAMmB,EAAgB,CACxB,EAHgB,WAKhB,GAAIF,EAAWpC,EAAQ,UAAWU,EAAQ,IAAI,CAAC,EAC7C,aAAM8B,EAAQ,EAEPV,EAAM,EAGf,IAAMW,EAAUC,GAAkBV,EAAUX,CAAS,EAAGrB,EAAQ,QAAQ,EAClE2C,EACJf,IAAY,QACZc,GAAkBV,EAAUJ,CAAO,EAAG5B,EAAQ,WAAW,EAE3D,GAAI,CAACyC,GAAW,CAACE,EACf,aAAMzC,EAAQ,KAAK,CAAE,KAAM,wBAAyB,KAAM,SAAU,CAAC,EACrE,MAAMsC,EAAQ,EAEPV,EAAM,EAGf,IAAMc,EAAUxB,EAAOjC,EAAMJ,EAAe,QAAQ,IAAM8D,GACtDC,EAEJ,GAAIF,EAAS,CACX,IAAMxD,EAASF,GAAYC,CAAI,EACzB4D,EAAU,MAAM9C,GAAWC,EAASF,EAASZ,CAAM,EAEzD,OAAQ2D,EAAQ,KAAM,CACpB,IAAK,UAEH,OAAOC,GAAoB9C,EAASO,EAAST,EAASqB,EAAW,CAC/D,OAAQ,IACR,YAAa0B,EAAQ,YACrB,UAAW,CAAE,GAAG3D,CAAO,CACzB,CAAC,EACH,IAAK,SACH,aAAMoD,EAAQ,EAEPtC,EAAQ,UAAUO,EAAS,eAAgB,CAChD,WAAY,CAACiB,CAAU,CACzB,CAAC,EACH,IAAK,WACHoB,EAAYC,EAAQ,UACpB,KACJ,CACF,CAMA,IAFc,MAAMzB,EAAM,sBAAsBH,CAAa,IAElD,WAAanB,EAAQ,SAAU,OAAOiC,EAAO,EAExD,IAAMgB,EAASzD,EAAA,MACb0D,GACsB,CACtB,IAAMC,EAAWC,GAAcpD,EAAQ,YAAa,CAClD,GAAGkD,EACH,MAAOlD,EAAQ,MACf,IAAKU,EAAQ,MACf,CAAC,EAED,GACEqB,IAAc,QACdrB,EAAQ,gBAAkB,GAC1BY,EAAM,oBAAsB,QAC5BA,EAAM,oBAAsB,OAC5B,CACA,IAAM+B,GAAY3C,EAAQ,IAAI,EAAIA,EAAQ,gBAI1C,MAAMY,EAAM,kBACVS,EACA,MAAMR,EAAO,KAAK,KAAK,UAAU,CAAE,SAAA4B,EAAU,UAAAE,EAAU,CAAC,EAAGtB,CAAS,EACpEsB,EACF,CACF,CAEA,OAAOhB,GAASc,EAAU,CAACzB,CAAU,EAAG,GAAG,CAC7C,EA3Be,UA6Bf,GAAI,CAACkB,EACH,aAAMN,EAAgB,EACtB,MAAMpC,EAAQ,KAAK,CACjB,KAAM,mBACN,SAAUF,EAAQ,SAClB,QAASA,EAAQ,OACnB,CAAC,EAEMiD,EAAO,CACZ,MAAO,gBACP,kBAAmB,qCACrB,CAAC,EAGH,IAAMK,EAAS5C,EAAQ,YAEvB,GAAI4C,IAAW,OAAW,CACxB,IAAMnB,EAAU,MAAMoB,GAAO,IAC3BD,EACE,OAAO,OAAO,CACZ,GAAGjD,EAAW,CACZ,QAASL,EAAQ,QACjB,OAAQD,GAAWC,CAAO,EAC1B,OAAQ,CAAC,GAAGA,EAAQ,MAAM,EAC1B,UAAA8C,CACF,CAAC,EACD,QAAArC,CACF,CAAC,CACH,CACF,EAEA,GAAI,CAAC0B,EAAQ,QACX,aAAMG,EAAgB,EACtB,MAAMpC,EAAQ,KAAK,CACjB,KAAM,wBACN,SAAUF,EAAQ,SAClB,OAAQmC,EAAQ,QAAU,cAC5B,CAAC,EAEMc,EAAO,CACZ,MAAO,gBACP,kBAAmB,wBACrB,CAAC,CAEL,CAEA,IAAMO,EAAM9C,EAAQ,IAAI,EAClB+C,EAAqB,CACzB,QAASzD,EAAQ,QACjB,SAAUA,EAAQ,SAClB,QAASA,EAAQ,QACjB,OAAQA,EAAQ,OAChB,SAAUA,EAAQ,SAClB,UAAWwD,EACX,UAAWA,EAAM9C,EAAQ,WACzB,GAAIoC,IAAc,OAAY,CAAE,KAAMA,CAAU,EAAI,CAAC,EACrD,GAAI9C,EAAQ,eAAiB,OACzB,CAAE,aAAcA,EAAQ,YAAa,EACrC,CAAC,EACL,GAAIA,EAAQ,aAAe,OACvB,CAAE,WAAYA,EAAQ,UAAW,EACjC,CAAC,EACL,GAAIA,EAAQ,cAAgB,OACxB,CAAE,YAAaA,EAAQ,WAAY,EACnC,CAAC,EACL,GAAIA,EAAQ,6BAA+B,OACvC,CAAE,2BAA4BA,EAAQ,0BAA2B,EACjE,CAAC,EACL,GAAIA,EAAQ,4BAA8B,QAC1CA,EAAQ,+BAAiC,OACrC,CACE,0BAA2BA,EAAQ,0BACnC,6BAA8BA,EAAQ,4BACxC,EACA,CAAC,CACP,EAEA,MAAMsB,EAAM,SAASmC,CAAK,EAE1B,IAAMC,GAAOC,EAAY,EAEzB,MAAMrC,EAAM,qBAAqB,CAC/B,SAAUU,EAAU0B,EAAI,EACxB,QAASD,EAAM,QACf,SAAUzD,EAAQ,SAClB,YAAaA,EAAQ,YACrB,cAAeA,EAAQ,cACvB,SAAUA,EAAQ,SAClB,OAAQA,EAAQ,OAChB,UAAWwD,EAAM9C,EAAQ,SAC3B,CAAC,EAED,IAAMkD,GAAQlD,EAAQ,WAEtB,GAAIkD,KAAU,OACZ,GAAI,CACF,MAAMA,GAAM,OAAO,OAAO,CAAE,MAAOC,GAAaJ,CAAK,CAAE,CAAC,CAAC,CAC3D,OAASzC,EAAO,CAGd,aAAMM,EAAM,YAAYmC,EAAM,OAAO,EACrC,MAAMnB,EAAgB,EACtB,MAAMpC,EAAQ,KAAK,CACjB,KAAM,qBACN,MAAO,aACP,UAAWc,aAAiB,MAAQA,EAAM,KAAO,OAAOA,CAC1D,CAAC,EAEMiC,EAAO,CACZ,MAAO,eACP,kBAAmB,uCACrB,CAAC,CACH,CAGF,aAAM/C,EAAQ,KAAK,CACjB,KAAM,mBACN,QAASuD,EAAM,QACf,SAAUA,EAAM,SAChB,QAASA,EAAM,QACf,OAAQA,EAAM,MAChB,CAAC,EAEMR,EAAO,CAAE,KAAAS,EAAK,CAAC,CACxB,CAvTsBlE,EAAAgB,GAAA,iBCpMtB,SAASsD,GAAYC,EAA+B,CAClD,OAAOC,EAAW,IAAK,0BAA2BD,CAAW,CAC/D,CAFSE,EAAAH,GAAA,eAIT,SAASI,GAAYH,EAA+B,CAClD,OAAOC,EAAW,IAAK,uBAAwBD,CAAW,CAC5D,CAFSE,EAAAC,GAAA,eAcT,eAAsBC,GACpBC,EACAC,EACmB,CACnB,GAAM,CAAE,QAAAC,CAAQ,EAAIF,EACd,CAAE,OAAAG,CAAO,EAAID,EAEnB,GAAI,CAAE,MAAMF,EAAQ,QAAQC,EAAS,UAAU,EAC7C,OAAOL,EACL,IACA,0BACA,yCACF,EAGF,GAAI,CAACQ,GAAeH,EAAS,kBAAkB,EAC7C,OAAOP,GAAY,8CAA8C,EAGnE,IAAIW,EAEJ,GAAI,CACFA,EAAO,KAAK,MAAM,MAAMC,GAASL,EAASE,EAAO,iBAAiB,CAAC,CACrE,OAASI,EAAO,CACd,OAAOb,GACLa,aAAiBC,EACb,wBAAwBL,EAAO,iBAAiB,SAChD,+BACN,CACF,CAEA,GAAI,CAACM,EAASJ,CAAI,EAAG,OAAOX,GAAY,+BAA+B,EAEvE,IAAMgB,EAAOL,EAAK,cAElB,GAAI,CAACM,EAAcD,CAAI,GAAKA,EAAK,SAAW,EAC1C,OAAOZ,GAAY,iDAAiD,EAGtE,GAAIY,EAAK,OAASP,EAAO,aACvB,OAAOL,GAAY,oBAAoBK,EAAO,YAAY,gBAAgB,EAG5E,QAAWS,KAAOF,EAAM,CACtB,IAAMG,EAAUC,GAAmBF,EAAKT,CAAM,EAE9C,GAAIU,IAAY,OACd,OAAOf,GACL,yBAAyBe,CAAO,kEAClC,CAEJ,CAEA,IAAME,EAAaV,EAAK,2BAExB,GAAIU,IAAe,QAAaA,IAAe,OAC7C,OAAOrB,GACL,sFACF,EAKF,IAAMsB,EAAaC,GAAgBZ,EAAK,YAAa,EAAI,EAEzD,GAAIW,IAAe,OACjB,OAAOtB,GACL,oGACF,EAGF,IAAMwB,EAAgBb,EAAK,gBAAkB,CAAC,MAAM,EAEpD,GACE,CAACM,EAAcO,CAAa,GAC5BA,EAAc,SAAW,GACzBA,EAAc,CAAC,IAAM,OAErB,OAAOxB,GAAY,0CAA0C,EAG/D,IAAMyB,EAASC,GACbf,EAAK,MACLH,EAAQ,OACR,GACAC,EAAO,WACT,EAEA,GAAIgB,IAAW,OACb,OAAOzB,GAAY,qBAAqBQ,EAAQ,OAAO,KAAK,GAAG,CAAC,EAAE,EAGpE,IAAImB,EAEJ,GAAIhB,EAAK,cAAgB,SACvBgB,EAAaC,EAAgBjB,EAAK,YAAaF,EAAO,gBAAgB,EAElEkB,IAAe,QACjB,OAAO3B,GACL,0BAA0BS,EAAO,gBAAgB,qBACnD,EAIJ,IAAMoB,EAASrB,EAAQ,kBAEvB,GACEqB,IAAW,QACX,CAAE,MAAMC,GAAQ,IAAMD,EAAO,CAAE,SAAUE,EAAWpB,CAAI,EAAG,QAAAJ,CAAQ,CAAC,CAAC,EAErE,OAAOL,EACL,IACA,0BACA,6DACF,EAGF,IAAM8B,EAAWxB,EAAQ,IAAI,EACvByB,EAAe,CAAC,GAAG,IAAI,IAAIjB,CAAI,CAAC,EAChCkB,EAAuB,CAC3B,SAAU,UAAUC,EAAY,CAAC,GACjC,aAAAF,EACA,KAAM,UACN,UAAWD,EACX,WAAAV,EACA,GAAIG,IAAW,KAAO,CAAE,OAAAA,CAAO,EAAI,CAAC,EACpC,GAAIE,IAAe,OAAY,CAAE,WAAAA,CAAW,EAAI,CAAC,CACnD,EAEA,aAAMnB,EAAQ,MAAM,UAAU0B,CAAM,EACpC,MAAM5B,EAAQ,KAAK,CACjB,KAAM,oBACN,SAAU4B,EAAO,SACjB,cAAe,CAAC,GAAG,IAAI,IAAID,EAAa,IAAIG,EAAc,CAAC,CAAC,EAC5D,GAAIT,IAAe,OAAY,CAAE,WAAAA,CAAW,EAAI,CAAC,CACnD,CAAC,EAEMU,GACL,IACA,CACE,UAAWH,EAAO,SAClB,oBAAqB,KAAK,MAAMF,EAAW,GAAI,EAC/C,cAAeC,EACf,YAAaX,EACb,eAAgB,CAAC,MAAM,EACvB,2BAA4B,OAC5B,GAAIG,IAAW,KAAO,CAAE,MAAOA,EAAO,KAAK,GAAG,CAAE,EAAI,CAAC,EACrD,GAAIE,IAAe,OAAY,CAAE,YAAaA,CAAW,EAAI,CAAC,CAChE,EACA,CAAE,KAAM,EAAK,CACf,CACF,CAvJsBxB,EAAAE,GAAA,kBC5Bf,IAAMiC,EAAN,cAA8B,KAAM,CAQzC,YACWC,EACAC,EACAC,EAAiB,IACjBC,EACAC,EACT,CACA,MAAMH,CAAW,EANR,WAAAD,EACA,iBAAAC,EACA,YAAAC,EACA,eAAAC,EACA,YAAAC,EAGT,KAAK,KAAO,iBACd,CARW,MACA,YACA,OACA,UACA,OAnBb,MAM2C,CAAAC,EAAA,wBAkB3C,ECVA,IAAMC,GAAc,qCAGdC,GAAuC,CAC3C,YACA,gBACA,mBACA,uBACF,EAQA,SAASC,GAASC,EAAwB,CACxC,IAAMC,EAASD,EAAO,KAAK,EAAE,MAAM,GAAG,EAAE,CAAC,GAAK,GAE9C,OAAOH,GAAY,KAAKI,CAAM,EAAIA,EAAS,OAC7C,CAJSC,EAAAH,GAAA,YAiBT,eAAsBI,GACpBC,EACAC,EACAC,EACyD,CACzD,GAAI,CAACC,GAAeH,EAASI,EAAe,EAC1C,MAAM,IAAIC,EACR,kBACA,kEACF,EAGF,IAAIC,EAEJ,GAAI,CACFA,EAAOC,EAAY,IAAI,gBAAgB,MAAMC,GAASR,EAASC,CAAQ,CAAC,CAAC,CAC3E,OAASQ,EAAO,CACd,MAAIA,aAAiBC,EACb,IAAIL,EACR,kBACA,oBAAoBJ,CAAQ,QAC9B,EAGIQ,CACR,CAIA,IAAME,EAAWC,EAAgBN,EAAM,CAAC,GAAGZ,GAAmB,GAAGQ,CAAO,CAAC,EAEzE,GAAIS,EAAS,OAAS,EACpB,MAAM,IAAIN,EACR,kBACA,kCAAkCM,EAAS,KAAK,IAAI,CAAC,EACvD,EAOF,IAAME,EAAgBb,EAAQ,QAAQ,IAAI,eAAe,EACnDc,EACJ,2EAEF,GAAID,IAAkB,KACpB,MAAM,IAAIR,EACR,iBACAS,EACA,IACAnB,GAASkB,CAAa,CACxB,EAGF,GAAIP,EAAK,IAAI,eAAe,GAAKA,EAAK,IAAI,kBAAkB,EAC1D,MAAM,IAAID,EAAgB,iBAAkBS,CAAO,EAGrD,MAAO,CAAE,KAAAR,EAAM,SAAUS,EAAOT,EAAM,WAAW,CAAE,CACrD,CA5DsBR,EAAAC,GAAA,wBAsEf,SAASiB,GACdC,EACAX,EACM,CACN,IAAMY,EAAWH,EAAOT,EAAM,UAAU,EAExC,GACEY,IAAa,QACb,CAACC,GAAaF,EAAQ,QAAQ,SAAUC,CAAQ,EAEhD,MAAM,IAAIb,EACR,iBACA,wCAAwCY,EAAQ,QAAQ,QAAQ,EAClE,CAEJ,CAfgBnB,EAAAkB,GAAA,sBC7GhB,IAAMI,GAAiD,CACrD,gBAAiB,WACjB,OAAQ,WACR,8BAA+B,GACjC,EAGMC,GAAuC,CAAC,QAAS,iBAAiB,EAgBxE,eAAsBC,GACpBC,EACAC,EACAC,EACmB,CACnB,GAAM,CAAE,MAAAC,EAAO,OAAAC,CAAO,EAAIJ,EAAQ,QAC5BK,EAAOC,EAAA,IAAM,IAAI,SAAS,KAAM,CAAE,OAAQ,IAAK,QAAST,EAAa,CAAC,EAA/D,QAEb,GAAI,CACF,GAAI,CAAE,MAAMG,EAAQ,QAAQC,EAAS,QAAQ,EAC3C,OAAOM,EACL,IACA,0BACA,oCACF,EAGF,GAAM,CAAE,KAAAC,EAAM,SAAAC,CAAS,EAAI,MAAMC,GAC/BT,EACAG,EAAO,UACPN,EACF,EACMa,EAAQC,EAAOJ,EAAM,OAAO,EAElC,GAAIG,IAAU,OACZ,MAAM,IAAIE,EAAgB,kBAAmB,0BAA0B,EAGzE,GAAI,CAACC,EAAcH,CAAK,EAAG,OAAON,EAAK,EAEvC,IAAMU,EAAOC,EAAUL,CAAK,EACtBM,EAAeX,EAACY,GACpBT,IAAa,QAAaA,IAAaS,EADpB,gBAEfC,EAAiBb,EAAA,SAAY,CACjC,IAAMc,EAAQ,MAAMjB,EAAM,gBAAgBY,CAAI,EAE9C,OAAIK,IAAU,QAAa,CAACH,EAAaG,EAAM,QAAQ,EAAU,IAEjE,MAAMlB,EAAOkB,EAAM,QAAS,oBAAoB,EAEzC,GACT,EARuB,kBASjBC,EAAgBf,EAAA,SAAY,CAChC,IAAMc,EAAQ,MAAMjB,EAAM,eAAeY,CAAI,EAE7C,OAAIK,IAAU,QAAa,CAACH,EAAaG,EAAM,QAAQ,EAAU,IAEjE,MAAMjB,EAAM,kBAAkBY,CAAI,EAE3B,GACT,EARsB,iBAShBO,EACJV,EAAOJ,EAAM,iBAAiB,IAAM,eAChC,CAACa,EAAeF,CAAc,EAC9B,CAACA,EAAgBE,CAAa,EAEpC,QAAWE,KAAWD,EACpB,GAAI,MAAMC,EAAQ,EAAG,MAGvB,OAAOlB,EAAK,CACd,OAASmB,EAAO,CACd,GAAI,EAAEA,aAAiBX,GAAkB,MAAMW,EAE/C,OAAOjB,EACLiB,EAAM,OACNA,EAAM,MACNA,EAAM,YACNA,EAAM,SACR,CACF,CACF,CAvEsBlB,EAAAP,GAAA,gBCdtB,eAAsB0B,GACpBC,EACAC,EACAC,EACe,CACf,IAAMC,EAAOH,EAAQ,QAAQ,iBAE7B,GAAIG,IAAS,OAAW,OAExB,IAAIC,EAEJ,GAAI,CACFA,EAAU,MAAMD,EACd,OAAO,OAAO,CAAE,MAAOE,GAAaJ,CAAK,EAAG,UAAAC,CAAU,CAAC,CACzD,CACF,MAAQ,CAGN,MAAM,IAAII,EACR,0BACA,iDACA,GACF,CACF,CAEA,GAAI,CAACC,GAAMH,CAAO,EAAE,QAClB,MAAM,IAAIE,EACR,gBACA,sCACF,CAEJ,CA/BsBE,EAAAT,GAAA,qBCgBtB,eAAsBU,GACpBC,EACAC,EACAC,EACAC,EACwB,CACxB,GAAM,CAAE,QAAAC,CAAQ,EAAIJ,EACdK,EAAOC,EAAOL,EAAM,MAAM,EAC1BM,EAAWD,EAAOL,EAAM,eAAe,EAEvCO,EAAcF,EAAOL,EAAM,cAAc,EACzCQ,EAAU,CACdJ,IAAS,OAAY,OAAS,OAC9BE,IAAa,OAAY,gBAAkB,MAC7C,EAAE,OAAQG,GAASA,IAAS,MAAS,EAErC,GAAIL,IAAS,QAAaE,IAAa,OACrC,MAAM,IAAII,EACR,kBACA,sBAAsBF,EAAQ,KAAK,IAAI,CAAC,EAC1C,EAGFG,GAAmBZ,EAASC,CAAI,EAIhC,IAAMY,EAAWC,EAACC,GAChB,IAAIJ,EACF,gBACA,8CACA,IACA,OACAI,CACF,EAPe,YASjB,GAAI,CAACC,EAAcX,CAAI,EAAG,MAAMQ,EAAS,gBAAgB,EAEzD,IAAMI,EAAWC,EAAUb,CAAI,EACzBc,EAAS,MAAMf,EAAQ,MAAM,qBAAqBa,CAAQ,EAEhE,GAAIE,IAAW,OAAW,MAAMN,EAAS,cAAc,EAEvD,IAAMO,EAASD,EAAO,OAChBE,EAAWf,EAAOL,EAAM,UAAU,EAclCqB,EAZuD,CAC3D,CAAC,YAAaF,EAAO,WAAalB,CAAQ,EAC1C,CACE,eACAM,IAAgB,QAAaY,EAAO,cAAgBZ,CACtD,EACA,CACE,WACAa,IAAa,QAAa,CAACE,GAAaH,EAAO,SAAUC,CAAQ,CACnE,EACA,CAAC,gBAAiB,CAACG,GAAYjB,EAAUa,EAAO,aAAa,CAAC,CAChE,EAC4B,KAAK,CAAC,CAAC,CAAEK,CAAQ,IAAMA,CAAQ,IAAI,CAAC,EAEhE,GAAIH,IAAY,OACd,MAAMT,EAAS,GAAGS,CAAO,WAAW,EAGtC,IAAMI,EAAWZ,EAAA,UAGf,MAAMd,EAAQ,KAAK,CACjB,KAAM,8BACN,QAASoB,EAAO,QAChB,SAAUA,EAAO,QACnB,CAAC,EACD,MAAMjB,EAAOiB,EAAO,QAAS,2BAA2B,EAEjDP,EAAS,eAAe,GAVhB,YAajB,GAAIM,EAAO,SAAU,MAAM,MAAMO,EAAS,EAE1C,GAAIC,EAAWP,EAAO,UAAWhB,EAAQ,IAAI,CAAC,EAC5C,YAAMD,EAAOiB,EAAO,QAAS,4BAA4B,EAEnDP,EAAS,cAAc,EAG/B,IAAMe,EAAQ,MAAMC,GAAU7B,EAASoB,EAAO,QAASlB,CAAQ,EAE/D,GAAI0B,IAAU,OACZ,MAAM,IAAIjB,EAAgB,gBAAiB,2BAA2B,EAGxE,MAAMmB,GAAkB9B,EAAS4B,EAAO,oBAAoB,EAE5D,IAAMG,EAAW,MAAM3B,EAAQ,MAAM,yBAAyBa,CAAQ,EAEtE,GAAIc,IAAa,OAAW,MAAMlB,EAAS,cAAc,EAEzD,GAAI,CAACkB,EAAS,SAAU,MAAM,MAAML,EAAS,EAE7C,IAAMM,EAAS,MAAMC,GAAejC,EAAS4B,EAAOR,EAAO,MAAM,EAEjE,aAAMpB,EAAQ,KAAK,CACjB,KAAM,eACN,QAAS4B,EAAM,QACf,SAAA1B,EACA,QAAS0B,EAAM,QACf,UAAW,oBACb,CAAC,EAEMI,CACT,CA9GsBlB,EAAAf,GAAA,gBCVtB,eAAsBmC,GACpBC,EACAC,EACAC,EACAC,EACwB,CACxB,GAAM,CAAE,QAAAC,CAAQ,EAAIJ,EACdK,EAAYC,EAAOL,EAAM,eAAe,EAE9C,GAAII,IAAc,OAChB,MAAM,IAAIE,EACR,kBACA,kCACF,EAGFC,GAAmBR,EAASC,CAAI,EAEhC,IAAMQ,EAAWC,EAAA,IACf,IAAIH,EAAgB,gBAAiB,mCAAmC,EADzD,YAGjB,GAAI,CAACI,EAAcN,CAAS,EAAG,MAAMI,EAAS,EAE9C,IAAMG,EAAOC,EAAUR,CAAS,EAC1BS,EAAMV,EAAQ,IAAI,EAGlBW,EAAQ,MAAMX,EAAQ,MAAM,gBAAgBQ,CAAI,EAEtD,GACEG,IAAU,QACVA,EAAM,WAAab,GACnBc,EAAWD,EAAM,UAAWD,CAAG,EAE/B,MAAML,EAAS,EAGjB,IAAIQ,EAASF,EAAM,OACbG,EAAWZ,EAAOL,EAAM,OAAO,EAErC,GAAIiB,IAAa,OAAW,CAC1B,IAAMC,EAAQ,CAAC,GAAG,IAAI,IAAID,EAAS,MAAM,GAAG,EAAE,OAAQE,GAAMA,EAAE,OAAS,CAAC,CAAC,CAAC,EAE1E,GAAID,EAAM,SAAW,GAAKA,EAAM,KAAMC,GAAM,CAACL,EAAM,OAAO,SAASK,CAAC,CAAC,EACnE,MAAM,IAAIb,EACR,gBACA,yDACF,EAGFU,EAASE,CACX,CAIA,IAAME,EAAU,MAAMC,GAAUtB,EAASe,EAAM,QAASb,CAAQ,EAEhE,GAAImB,IAAY,QAAaA,EAAQ,eAAiB,GACpD,MAAM,IAAId,EAAgB,gBAAiB,2BAA2B,EAGxE,MAAMgB,GAAkBvB,EAASqB,EAAS,eAAe,EAEzD,IAAMG,EAAS,MAAMpB,EAAQ,MAAM,gBAAgBQ,EAAME,CAAG,EAE5D,GAAIU,IAAW,OAAW,MAAMf,EAAS,EAEzC,IAAMgB,EAAUD,EAAO,eACjBE,EACJD,IAAY,QACZrB,EAAQ,oBAAsB,GAC9BU,EAAMW,GAAWrB,EAAQ,oBAE3B,GAAIqB,IAAY,QAAa,CAACC,EAI5B,YAAM1B,EAAQ,KAAK,CACjB,KAAM,yBACN,QAASwB,EAAO,OAAO,QACvB,SAAAtB,CACF,CAAC,EACD,MAAMC,EAAOqB,EAAO,OAAO,QAAS,sBAAsB,EAEpDf,EAAS,EAGjB,IAAMkB,EAAQ,MAAML,GAAUtB,EAASwB,EAAO,OAAO,QAAStB,CAAQ,EAEtE,GAAIyB,IAAU,QAAaA,EAAM,eAAiB,GAChD,MAAM,IAAIpB,EAAgB,gBAAiB,2BAA2B,EAKxE,IAAMqB,EAAS,MAAMC,GAAe7B,EAAS2B,EAAOV,EAAQF,EAAM,MAAM,EAExE,aAAMf,EAAQ,KAAK,CACjB,KAAM,eACN,QAAS2B,EAAM,QACf,SAAAzB,EACA,QAASyB,EAAM,QACf,UAAW,eACb,CAAC,EAEMC,CACT,CA1GsBlB,EAAAX,GAAA,wBCVtB,IAAM+B,GAAsC,CAC1C,aACA,OACA,eACA,gBACA,gBACA,QACA,UACF,EAeA,eAAsBC,GACpBC,EACAC,EACAC,EACmB,CACnB,IAAIC,EAEJ,GAAI,CACF,GAAI,CAAE,MAAMH,EAAQ,QAAQC,EAAS,OAAO,EAC1C,OAAOG,EACL,IACA,0BACA,oCACF,EAGF,IAAMC,EAAS,MAAMC,GACnBL,EACAD,EAAQ,QAAQ,OAAO,UACvBF,EACF,EAIA,GAFAK,EAAWE,EAAO,SAEdF,IAAa,OACf,MAAM,IAAII,EACR,iBACA,8BACF,EAKF,IAAMC,EAAQC,GAA2BN,CAAQ,EAC3CO,EAASF,EAAQ,OAAY,MAAMG,GAAcX,EAASG,CAAQ,EAExE,GAAI,CAACK,GAASE,IAAW,OACvB,MAAM,IAAIH,EACR,iBACA,8CACF,EAGF,IAAMK,EAAYC,EAAOR,EAAO,KAAM,YAAY,EAElD,GACEO,IAAc,iBACdF,GAAQ,aAAe,QACvB,CAACA,EAAO,WAAW,SAAS,eAAe,EAE3C,MAAM,IAAIH,EACR,sBACA,iEACF,EAGF,IAAIO,EAEJ,OAAQF,EAAW,CACjB,IAAK,qBACHE,EAAS,MAAMC,GAAaf,EAASK,EAAO,KAAMF,EAAUD,CAAM,EAClE,MACF,IAAK,gBACHY,EAAS,MAAME,GACbhB,EACAK,EAAO,KACPF,EACAD,CACF,EACA,MACF,KAAK,OACH,MAAM,IAAIK,EACR,kBACA,+BACF,EACF,QACE,MAAM,IAAIA,EACR,yBACA,mEACF,CACJ,CAEA,OAAOU,GAAK,IAAKH,EAAQ,CAAE,KAAM,EAAK,CAAC,CACzC,OAASI,EAAO,CACd,GAAI,EAAEA,aAAiBX,GAAkB,MAAMW,EAE/C,aAAMlB,EAAQ,KAAK,CACjB,KAAM,wBACN,MAAOkB,EAAM,MACb,OAAQA,EAAM,QAAUA,EAAM,YAC9B,GAAIf,IAAa,OAAY,CAAE,SAAAA,CAAS,EAAI,CAAC,CAC/C,CAAC,EAEMC,EACLc,EAAM,OACNA,EAAM,MACNA,EAAM,YACNA,EAAM,SACR,CACF,CACF,CApGsBC,EAAApB,GAAA,eC3Bf,SAASqB,GACdC,EACyB,CACzB,IAAMC,EAAKC,EAACC,GAAiB,GAAGH,EAAQ,YAAY,GAAGG,CAAI,GAAhD,MAEX,MAAO,CACL,OAAQH,EAAQ,OAChB,uBAAwBC,EAAGD,EAAQ,MAAM,SAAS,EAClD,eAAgBC,EAAGD,EAAQ,MAAM,KAAK,EACtC,GAAIA,EAAQ,0BACR,CAAE,sBAAuBC,EAAGD,EAAQ,MAAM,QAAQ,CAAE,EACpD,CAAC,EACL,oBAAqBC,EAAGD,EAAQ,MAAM,MAAM,EAC5C,iBAAkBA,EAAQ,OAC1B,yBAA0B,CAAC,MAAM,EACjC,yBAA0B,CAAC,OAAO,EAClC,sBAAuB,CAAC,qBAAsB,eAAe,EAC7D,iCAAkC,CAAC,MAAM,EACzC,sCAAuC,CAAC,MAAM,EAC9C,2CAA4C,CAAC,MAAM,EACnD,sCAAuCA,EAAQ,0BAC/C,+CAAgD,EAClD,CACF,CAvBgBE,EAAAH,GAAA,+BAgCT,SAASK,GACdJ,EACyB,CACzB,MAAO,CACL,SAAUA,EAAQ,SAClB,sBAAuB,CAACA,EAAQ,MAAM,EACtC,iBAAkBA,EAAQ,OAC1B,yBAA0B,CAAC,QAAQ,EACnC,GAAIA,EAAQ,eAAiB,OACzB,CAAE,cAAeA,EAAQ,YAAa,EACtC,CAAC,CACP,CACF,CAZgBE,EAAAE,GAAA,6BCjBhB,eAAeC,GACbC,EACAC,EACAC,EACAC,EACmB,CACnB,GAAI,CACF,OAAO,MAAMD,EAAI,CACnB,OAASE,EAAO,CACd,aAAMJ,EAAQ,KAAK,CACjB,KAAM,qBACN,MAAAC,EACA,UAAWG,aAAiB,MAAQA,EAAM,KAAO,SACnD,CAAC,EAEMD,EAAK,CACd,CACF,CAjBeE,EAAAN,GAAA,WA6BR,SAASO,GACdN,EACAO,EACiC,CACjC,GAAM,CAAE,QAAAC,CAAQ,EAAIR,EACdS,EAAcJ,EAAA,IAClBK,EACE,IACA,eACA,6CACF,EALkB,eAMdC,EAAYN,EAACO,GACjBZ,EAAQ,UAAUY,EAAS,cAAc,EADzB,aAEZC,EAAS,IAAI,IACbC,EAAMT,EAAA,CAACU,EAAcC,IAAsB,CAC1CH,EAAO,IAAIE,CAAI,GAAGF,EAAO,IAAIE,EAAMC,CAAK,CAC/C,EAFY,OAGNC,EAAWZ,EAACa,IAA+C,CAC/D,QAAS,CAAC,KAAK,EACf,KAAM,GACN,IAAKb,EAAA,IACH,QAAQ,QAAQc,GAAK,IAAKD,EAAM,CAAE,KAAM,GAAM,UAAW,EAAK,CAAC,CAAC,EAD7D,MAEP,GALiB,YAOjB,QAAWH,KAAQP,EAAQ,sBACzBM,EAAIC,EAAME,EAASG,GAA0BZ,CAAO,CAAC,CAAC,EAGxD,QAAWO,KAAQP,EAAQ,iCACzBM,EAAIC,EAAME,EAASI,GAA4Bb,CAAO,CAAC,CAAC,EAG1D,OAAIA,EAAQ,2BACVM,EAAIN,EAAQ,MAAM,SAAU,CAC1B,QAAS,CAAC,MAAM,EAChB,KAAM,GACN,IAAKH,EAACO,GACJb,GACEC,EACA,WACA,IAAMsB,GAAetB,EAASY,CAAO,EACrCH,CACF,EANG,MAOP,CAAC,EAGHK,EAAIN,EAAQ,MAAM,UAAW,CAC3B,QAAS,CAAC,KAAK,EACf,KAAM,GACN,IAAKH,EAACO,GACJb,GACEC,EACA,YACA,IAAMuB,GAAgBvB,EAASY,CAAO,EACtC,IAAMD,EAAUC,CAAO,CACzB,EANG,MAOP,CAAC,EACDE,EAAIN,EAAQ,MAAM,SAAU,CAC1B,QAAS,CAAC,KAAK,EACf,KAAM,GACN,IAAKH,EAACO,GACJb,GACEC,EACA,WACA,IAAMwB,GAAexB,EAASY,CAAO,EACrC,IAAMD,EAAUC,CAAO,CACzB,EANG,MAOP,CAAC,EACDE,EAAIN,EAAQ,MAAM,QAAS,CACzB,QAAS,CAAC,MAAM,EAChB,KAAM,GACN,IAAKH,EAACO,GACJb,GACEC,EACA,UACA,IAAMyB,GAAczB,EAASY,CAAO,EACpC,IAAMD,EAAUC,CAAO,CACzB,EANG,MAOP,CAAC,EACDE,EAAIN,EAAQ,MAAM,MAAO,CACvB,QAAS,CAAC,MAAM,EAChB,KAAM,GACN,IAAKH,EAACO,GACJb,GACEC,EACA,QACA,IAAM0B,GAAY1B,EAASY,EAASL,CAAM,EAC1CE,CACF,EANG,MAOP,CAAC,EACDK,EAAIN,EAAQ,MAAM,OAAQ,CACxB,QAAS,CAAC,MAAM,EAChB,KAAM,GACN,IAAKH,EAACO,GACJb,GACEC,EACA,SACA,IAAM2B,GAAa3B,EAASY,EAASL,CAAM,EAC3CE,CACF,EANG,MAOP,CAAC,EAEMI,CACT,CAvGgBR,EAAAC,GAAA,eC/BT,IAAMsB,GAAN,cAAiC,KAAM,CAtB9C,MAsB8C,CAAAC,EAAA,2BAE5C,aAAc,CACZ,MAAM,mDAAmD,EACzD,KAAK,KAAO,oBACd,CACF,ECVO,IAAMC,GAAN,cAAiC,KAAM,CAlB9C,MAkB8C,CAAAC,EAAA,2BAM5C,YAAYC,EAAiBC,EAA+B,CAC1D,MAAMD,EAASC,CAAO,EACtB,KAAK,KAAO,oBACd,CACF,ECMO,IAAMC,GAAN,KAAyB,CAlChC,MAkCgC,CAAAC,EAAA,2BACrBC,GACAC,GAAS,IAAI,IACbC,GAAY,IAAI,IAEhBC,GAAY,IAAI,IAMzB,YAAYC,EAAyB,CACnC,KAAKJ,GAAWI,CAClB,CASA,OAAOC,EAAuB,CAG5B,GAFA,KAAKJ,GAAO,OAAOI,CAAO,EAEtB,KAAKF,GAAU,MAAQG,GAAuB,CAChD,IAAMC,EAAS,KAAKJ,GAAU,OAAO,EAAE,KAAK,EAExCI,EAAO,OAAS,IAAM,KAAKJ,GAAU,OAAOI,EAAO,KAAK,CAC9D,CAEA,KAAKJ,GAAU,IAAIE,CAAO,CAC5B,CASA,IAAIA,EAAiBG,EAAe,GAAwB,CAC1D,IAAMC,EAAU,KAAKP,GAAU,IAAIG,CAAO,EAE1C,GAAI,CAACG,EAAc,CACjB,IAAME,EAAM,KAAKV,GAAS,QAAQ,IAAI,EAChCW,EAAS,KAAKV,GAAO,IAAII,CAAO,EAEtC,OACEM,IAAW,QACX,CAACC,EAAWD,EAAO,UAAYE,GAAyBH,CAAG,EAEpD,QAAQ,QAAQC,EAAO,KAAK,EAGjCF,IAAY,OAAkBA,EAAQ,QAEnC,KAAKK,GAAOT,EAAS,GAAO,KAAKU,GAAMV,CAAO,CAAC,CACxD,CAEA,GAAII,GAAS,SAAW,GAAM,OAAOA,EAAQ,QAK7C,IAAMO,EACJP,IAAY,OACR,KAAKQ,GAAOZ,CAAO,EACnBI,EAAQ,QAAQ,KACd,IAAM,KAAKQ,GAAOZ,CAAO,EACzB,IAAM,KAAKY,GAAOZ,CAAO,CAC3B,EAEN,OAAO,KAAKS,GAAOT,EAAS,GAAMW,CAAK,CACzC,CAEAF,GACET,EACAa,EACAC,EACiB,CACjB,IAAMC,EAAUD,EAAK,QAAQ,IAAM,CAC7B,KAAKjB,GAAU,IAAIG,CAAO,GAAG,UAAYe,GAC3C,KAAKlB,GAAU,OAAOG,CAAO,CAEjC,CAAC,EAED,YAAKH,GAAU,IAAIG,EAAS,CAAE,QAAAe,EAAS,OAAAF,CAAO,CAAC,EAExCE,CACT,CASA,KAAMH,GAAOZ,EAAkC,CAC7C,GAAM,CAAE,QAAAgB,CAAQ,EAAI,KAAKrB,GAEzB,KAAKC,GAAO,OAAOI,CAAO,EAE1B,IAAMiB,EAAQ,MAAMD,EAAQ,MAAM,SAAShB,CAAO,EAElD,GAAIiB,IAAU,QAAaV,EAAWU,EAAM,UAAWD,EAAQ,IAAI,CAAC,EAClE,MAAM,IAAIE,GAKZ,OAAID,EAAM,4BAA8B,QACtC,MAAMD,EAAQ,MAAM,YAAYhB,EAAS,CACvC,6BAA8B,CAChC,CAAC,EAGI,KAAKmB,GAASF,CAAK,CAC5B,CAEAG,GAAUpB,EAAiBqB,EAA0B,CACnD,GACE,KAAKzB,GAAO,MAAQK,IACpB,CAAC,KAAKL,GAAO,IAAII,CAAO,EACxB,CACA,IAAME,EAAS,KAAKN,GAAO,KAAK,EAAE,KAAK,EAEnCM,EAAO,OAAS,IAAM,KAAKN,GAAO,OAAOM,EAAO,KAAK,CAC3D,CAEA,KAAKN,GAAO,OAAOI,CAAO,EAC1B,KAAKJ,GAAO,IAAII,EAASqB,CAAK,CAChC,CAEA,KAAMX,GAAMV,EAAkC,CAC5C,GAAM,CAAE,QAAAgB,CAAQ,EAAI,KAAKrB,GACnBsB,EAAQ,MAAMD,EAAQ,MAAM,SAAShB,CAAO,EAElD,GAAIiB,IAAU,QAAaV,EAAWU,EAAM,UAAWD,EAAQ,IAAI,CAAC,EAClE,MAAM,IAAIE,GAGZ,IAAMI,EAAS,MAAM,KAAKC,GAAWN,CAAK,EAE1C,OAAIK,IAAW,OAAkBA,EAE1B,KAAKH,GAASF,CAAK,CAC5B,CAEA,KAAMM,GAAWN,EAAiD,CAChE,GAAM,CAAE,QAAAD,CAAQ,EAAI,KAAKrB,GAEzB,GACE,EAAAsB,EAAM,4BAA8B,QACpCA,EAAM,+BAAiC,QACvCV,EACEU,EAAM,6BAA+BT,GACrCQ,EAAQ,IAAI,CACd,GAKF,GAAI,CACF,IAAMK,EAAQ,MAAML,EAAQ,OAAO,KACjCC,EAAM,0BACN,GAAGA,EAAM,OAAO,SAClB,EAEA,YAAKG,GAAUH,EAAM,QAAS,CAC5B,MAAAI,EACA,UAAWJ,EAAM,4BACnB,CAAC,EAEMI,CACT,MAAQ,CAEN,MACF,CACF,CAEA,KAAMF,GAASF,EAAqC,CAClD,GAAM,CAAE,QAAAD,CAAQ,EAAI,KAAKrB,GACnBK,EAAUiB,EAAM,QAEtB,GAAIA,EAAM,6BAA+B,OACvC,MAAM,IAAIO,GACR,wDACF,EAGF,IAAIC,EAEJ,GAAI,CACF,IAAMC,EAAe,MAAMV,EAAQ,OAAO,KACxCC,EAAM,2BACNjB,CACF,EAEAyB,EAAY,MAAMT,EAAQ,iBAAiB,QACzCU,EACA,OAAO,OAAO,CAAE,QAAA1B,EAAS,QAASiB,EAAM,OAAQ,CAAC,CACnD,CACF,OAASU,EAAO,CACd,YAAM,KAAKhC,GAAS,KAAK,CACvB,KAAM,gCACN,QAAAK,EACA,OAAQ2B,aAAiB,MAAQA,EAAM,KAAO,SAChD,CAAC,EAEK,IAAIH,GACR,2DACA,CACE,MAAOG,CACT,CACF,CACF,CAEA,IAAMtB,EAAMW,EAAQ,IAAI,EAClBY,EAAY,KAAK,IACrB,EACA,KAAK,IACH,OAAO,SAASH,EAAU,SAAS,EAAIA,EAAU,UAAY,EAC7DI,EACF,CACF,EACMC,EAAYzB,EAAMuB,EAAY,IAoBpC,GAAI,CAnBY,MAAMZ,EAAQ,MAAM,YAAYhB,EAAS,CACvD,0BAA2B,MAAMgB,EAAQ,OAAO,KAC9CS,EAAU,YACV,GAAGzB,CAAO,SACZ,EACA,6BAA8B8B,EAC9B,GAAIL,EAAU,eAAiB,OAC3B,CACE,2BAA4B,MAAMT,EAAQ,OAAO,KAC/CS,EAAU,aACVzB,CACF,CACF,EACA,CAAC,CACP,CAAC,GAKe,KAAKF,GAAU,IAAIE,CAAO,EAAG,CAG3C,GAFA,KAAKJ,GAAO,OAAOI,CAAO,EAGxByB,EAAU,eAAiB,QAC3BT,EAAQ,iBAAiB,SAAW,OAEpC,GAAI,CACF,MAAMA,EAAQ,iBAAiB,OAAOS,EAAU,YAAY,CAC9D,MAAQ,CAER,CAGF,MAAM,IAAIP,EACZ,CAEA,YAAKE,GAAUpB,EAAS,CAAE,MAAOyB,EAAU,YAAa,UAAAK,CAAU,CAAC,EAE5DL,EAAU,WACnB,CACF,ECjPO,SAASM,GAAcC,EAAkC,CAC9D,IAAMC,EAAWC,GAAeF,EAAS,CACvC,QAASG,GACT,MAAOC,EACT,CAAC,EACKC,EAAUC,GAAcL,CAAQ,EAChCM,EAAW,IAAIC,GAAmBH,CAAO,EACzCI,EAAsBC,EAAA,CAACC,EAASC,IACpCC,GAAgBR,EAASM,EAASC,EAASE,GAAO,CAChDP,EAAS,OAAOO,CAAE,CACpB,CAAC,EAHyB,UAItBC,EAASC,GAAYX,EAASI,CAAM,EAE1C,MAAO,CACL,MAAM,OAAOQ,EAAS,CACpB,IAAMC,EAAQH,EAAO,IAAI,IAAI,IAAIE,EAAQ,GAAG,EAAE,QAAQ,EAEtD,GAAIC,IAAU,OAEd,OAAID,EAAQ,SAAW,WAAaC,EAAM,KACjCC,GAAU,CAAC,GAAGD,EAAM,QAAS,SAAS,EAAE,KAAK,IAAI,CAAC,EAGtDA,EAAM,QAAQ,SAASD,EAAQ,MAAM,EAInCC,EAAM,IAAID,CAAO,EAHfG,GAAiBF,EAAM,QAAQ,KAAK,IAAI,CAAC,CAIpD,EACA,aAAaD,EAAS,CACpB,OAAOI,GAAoBhB,EAASY,EAAS,CAACN,EAASW,IACrDf,EAAS,IAAII,EAASW,CAAY,CACpC,CACF,EACA,aAAaC,EAAUC,EAAO,CAC5B,OAAOC,GAAgBpB,EAASmB,CAAK,CACvC,EACA,YAAYb,EAAS,CACnB,OAAOF,EAAOE,EAAS,mBAAmB,CAC5C,EACA,cAAcA,EAAS,CACrB,OAAOJ,EAAS,IAAII,EAAS,EAAI,CACnC,EACA,MAAM,gBAAgBA,EAASe,EAAM,CACnC,IAAMC,EAAOC,EACXF,EACAzB,EAAS,OAAO,eAChB,iBACF,EAEA,OAAOA,EAAS,MAAM,YAAYU,EAAS,CAAE,KAAMgB,CAAK,CAAC,CAC3D,EACA,MAAM,uBAAuBE,EAAS,CACpC,GAAM,CAAE,MAAAC,CAAM,EAAI7B,EAElB,GAAI6B,EAAM,sBAAwB,OAChC,MAAM,IAAIC,EACR,+DACF,EAGF,IAAMC,EAAS,MAAMF,EAAM,oBAAoBD,CAAO,EAClDI,EAAQ,EAEZ,QAAWC,KAASF,EAEdE,EAAM,UAAYL,IAEtB,MAAMpB,EAAOyB,EAAM,QAAS,qBAAqB,EACjDD,GAAS,GAGX,OAAOA,CACT,CACF,CACF,CA3EgBvB,EAAAX,GAAA,iBC7DhB,OAAS,kBAAAoC,GAAgB,oBAAAC,GAAkB,eAAAC,OAAmB,cCkBvD,IAAMC,EAAN,cAA0B,KAAM,CAlBvC,MAkBuC,CAAAC,EAAA,oBAIrC,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,aACd,CACF,EDpBA,IAAMC,GAAW,GAEXC,GAAY,GAEZC,GAAc,wBAEdC,GAAS,KAQf,SAASC,GAAWC,EAA2B,CAC7C,GAAI,CACF,OAAO,QAAQ,QAAQA,EAAK,CAAC,CAC/B,OAASC,EAAO,CACd,OAAO,QAAQ,OACbA,aAAiB,MAAQA,EAAQ,IAAIC,EAAY,gBAAgB,CACnE,CACF,CACF,CARSC,EAAAJ,GAAA,WAiBT,SAASK,GAAeC,EAAaC,EAAqB,CACxD,OAAO,OAAO,KAAK,GAAGR,EAAM,KAASO,CAAG,KAASC,CAAG,GAAI,MAAM,CAChE,CAFSH,EAAAC,GAAA,kBA6BF,SAASG,GAAmBC,EAA4C,CAC7E,IAAMC,EAAO,IAAI,IAEjB,OAAW,CAACJ,EAAKK,CAAG,IAAK,OAAO,QAAQF,EAAQ,IAAI,EAAG,CACrD,GAAI,CAACX,GAAY,KAAKQ,CAAG,EACvB,MAAM,IAAI,UACR,UAAU,KAAK,UAAUA,CAAG,CAAC,yDAC/B,EAGF,GAAI,EAAEK,aAAe,aAAeA,EAAI,aAAe,GACrD,MAAM,IAAI,UAAU,OAAOL,CAAG,uBAAuB,EAIvDI,EAAK,IAAIJ,EAAK,OAAO,KAAKK,CAAG,CAAC,CAChC,CAEA,IAAMC,EAAUF,EAAK,IAAID,EAAQ,UAAU,EAE3C,GAAIG,IAAY,OACd,MAAM,IAAI,UAAU,0CAA0C,EAGhE,IAAMC,EAAaJ,EAAQ,WAE3B,MAAO,CACL,KAAKK,EAAWP,EAAK,CACnB,OAAOP,GAAQ,IAAM,CACnB,IAAMe,EAAKC,GAAYpB,EAAQ,EACzBqB,EAASC,GAAe,cAAeN,EAASG,EAAI,CACxD,cAAelB,EACjB,CAAC,EAEDoB,EAAO,OAAOZ,GAAeQ,EAAYN,CAAG,CAAC,EAE7C,IAAMY,EAAO,OAAO,OAAO,CACzBF,EAAO,OAAOH,EAAW,MAAM,EAC/BG,EAAO,MAAM,CACf,CAAC,EAED,MAAO,CACLlB,GACAc,EACAE,EAAG,SAAS,WAAW,EACvBI,EAAK,SAAS,WAAW,EACzBF,EAAO,WAAW,EAAE,SAAS,WAAW,CAC1C,EAAE,KAAK,GAAG,CACZ,CAAC,CACH,EACA,KAAKG,EAAQb,EAAK,CAChB,OAAOP,GAAQ,IAAM,CACnB,IAAMqB,EAAQD,EAAO,MAAM,GAAG,EAE9B,GAAIC,EAAM,SAAW,GAAKA,EAAM,CAAC,IAAMtB,GACrC,MAAM,IAAII,EAAY,+BAA+B,EAGvD,GAAM,CAAC,CAAEG,EAAM,GAAIgB,EAAS,GAAIC,EAAW,GAAIC,EAAU,EAAE,EAAIH,EACzDV,EAAMD,EAAK,IAAIJ,CAAG,EAExB,GAAIK,IAAQ,OACV,MAAM,IAAIR,EACR,oDACF,EAEF,IAAMY,EAAK,OAAO,KAAKO,EAAQ,WAAW,EACpCG,EAAM,OAAO,KAAKD,EAAS,WAAW,EAE5C,GAAIT,EAAG,aAAenB,IAAY6B,EAAI,aAAe5B,GACnD,MAAM,IAAIM,EAAY,+BAA+B,EAGvD,GAAI,CACF,IAAMuB,EAAWC,GAAiB,cAAehB,EAAKI,EAAI,CACxD,cAAelB,EACjB,CAAC,EAED,OAAA6B,EAAS,OAAOrB,GAAeC,EAAKC,CAAG,CAAC,EACxCmB,EAAS,WAAWD,CAAG,EAEhB,OAAO,OAAO,CACnBC,EAAS,OAAO,OAAO,KAAKH,EAAU,WAAW,CAAC,EAClDG,EAAS,MAAM,CACjB,CAAC,EAAE,SAAS,MAAM,CACpB,MAAQ,CACN,MAAM,IAAIvB,EACR,mDACF,CACF,CACF,CAAC,CACH,CACF,CACF,CA7FgBC,EAAAI,GAAA,sBEjDT,IAAMoB,EAAN,cAAoC,KAAM,CAjBjD,MAiBiD,CAAAC,EAAA,8BAEtC,KAQT,YAAYC,EAAiBC,EAAeC,EAA+B,CACzE,MAAMF,EAASE,CAAO,EACtB,KAAK,KAAO,wBACZ,KAAK,KAAOD,CACd,CACF,EC/BA,OACE,sBAAAE,GACA,eAAAC,GAEA,aAAAC,OACK,OCDA,IAAMC,GAAqB,CAChC,QACA,QACA,QACA,QACA,QACA,QACA,QACA,QACA,QACA,OACF,EAOaC,GAAuC,IAAI,IAAI,CAC1D,QACA,QACA,iBACA,wBACA,YACA,eACA,gBACA,OACF,CAAC,EC3BD,SAASC,GAAeC,EAA6C,CACnE,OAAOA,IAAU,QAAa,OAAOA,GAAU,QACjD,CAFSC,EAAAF,GAAA,kBAUF,SAASG,GAAYF,EAAoC,CAC9D,OACEG,EAASH,CAAK,GACd,OAAOA,EAAM,QAAW,UACxB,OAAOA,EAAM,wBAA2B,UACxC,OAAOA,EAAM,gBAAmB,UAChC,OAAOA,EAAM,UAAa,UAC1BD,GAAeC,EAAM,mBAAmB,IACvCA,EAAM,wCAA0C,QAC/CI,EAAcJ,EAAM,qCAAqC,KAC1DA,EAAM,iDAAmD,QACxD,OAAOA,EAAM,gDACX,aACHA,EAAM,mCAAqC,QAC1CI,EAAcJ,EAAM,gCAAgC,EAE1D,CAhBgBC,EAAAC,GAAA,eAwBT,SAASG,GACdL,EACgC,CAChC,OACEG,EAASH,CAAK,GACd,OAAOA,EAAM,cAAiB,UAC9BA,EAAM,eAAiB,IACvBD,GAAeC,EAAM,QAAQ,GAC7BD,GAAeC,EAAM,aAAa,IACjCA,EAAM,aAAe,QACnB,OAAOA,EAAM,YAAe,UAC3B,OAAO,SAASA,EAAM,UAAU,EAExC,CAbgBC,EAAAI,GAAA,mBAwBT,SAASC,GACdC,EACAP,EACAQ,EACK,CACL,IAAIC,EAEJ,GAAI,CACFA,EAAM,IAAI,IAAIT,CAAK,CACrB,MAAQ,CACN,MAAM,IAAIU,EACR,qBAAqBH,CAAI,4BAC3B,CACF,CAEA,IAAMI,EAAQF,EAAI,WAAa,SAAWG,GAAe,IAAIH,EAAI,QAAQ,EAEzE,GAAIA,EAAI,WAAa,UAAY,EAAEE,GAASH,GAC1C,MAAM,IAAIE,EACR,qBAAqBH,CAAI,sBAC3B,EAGF,OAAOE,CACT,CAxBgBR,EAAAK,GAAA,iBFRT,SAASO,GACdC,EACkB,CAClB,IAAMC,EAAWD,EAAQ,yBAA2B,GAC9CE,EAAYF,EAAQ,WAAa,IACjCG,EAAUH,EAAQ,OAAS,MAC3BI,EAAa,CAAC,GAAIJ,EAAQ,YAAcK,EAAmB,EAEjE,GAAI,CAACL,EAAQ,OAAO,SAAS,QAAQ,EACnC,MAAM,IAAI,UAAU,oCAAoC,EAG1D,GACEI,EAAW,SAAW,GACtBA,EAAW,KACRE,GACCA,EAAI,YAAY,IAAM,QAAUA,EAAI,YAAY,EAAE,WAAW,IAAI,CACrE,EAEA,MAAM,IAAI,UACR,oEACF,EAGF,QAAWC,KAAO,OAAO,KAAKP,EAAQ,0BAA4B,CAAC,CAAC,EAClE,GAAIQ,GAAgB,IAAID,CAAG,EACzB,MAAM,IAAI,UAAU,6CAA6CA,CAAG,EAAE,EAI1E,GAAI,CACFE,GAAc,SAAUT,EAAQ,OAAQC,CAAQ,CAClD,OAASS,EAAO,CACd,MAAM,IAAI,UACRA,aAAiB,MACbA,EAAM,QACN,wCACN,CACF,CAEA,IAAMC,EAAaC,EAAA,CAACC,EAAmBC,EAAoB,CAAC,IAC1DX,EAAQU,EAAK,CACX,GAAGC,EACH,SAAU,QACV,OAAQ,YAAY,QAAQZ,CAAS,CACvC,CAAC,EALgB,cAOfa,EAGQ,KAENC,EAAWJ,EAAA,IAAM,CACrB,GAAIG,IAAc,KAAM,OAAOA,EAE/B,IAAME,GAAW,SAAY,CAC3B,IAAMC,EAAOC,GAAanB,EAAQ,OAAQ,GAAG,EACzCoB,EAEJ,GAAI,CACFA,EAAW,MAAMT,EACf,GAAGO,CAAI,oCACP,CACE,QAAS,CAAE,OAAQ,kBAAmB,CACxC,CACF,CACF,OAASR,EAAO,CACd,MAAM,IAAIW,EACR,6DACA,OACA,CACE,MAAOX,CACT,CACF,CACF,CAEA,GAAI,CAACU,EAAS,GACZ,MAAM,IAAIC,EACR,4CAA4CD,EAAS,MAAM,EAC7D,EAGF,IAAME,EAAoB,MAAMF,EAAS,KAAK,EAAE,MAAM,IAAG,EAAY,EAErE,GAAI,CAACG,GAAYD,CAAQ,EACvB,MAAM,IAAID,EACR,0CACF,EAIF,GAAIC,EAAS,SAAWtB,EAAQ,OAC9B,MAAM,IAAIqB,EACR,6DACF,EAGF,GACEC,EAAS,mCAAqC,QAC9C,CAACA,EAAS,iCAAiC,SAAS,MAAM,EAE1D,MAAM,IAAID,EACR,uDACF,EAGFZ,GACE,yBACAa,EAAS,uBACTrB,CACF,EACAQ,GAAc,iBAAkBa,EAAS,eAAgBrB,CAAQ,EAEjE,IAAMuB,EAAUf,GAAc,WAAYa,EAAS,SAAUrB,CAAQ,EAEjEqB,EAAS,sBAAwB,QACnCb,GACE,sBACAa,EAAS,oBACTrB,CACF,EAGF,IAAMwB,EAAOC,GAAmBF,EAAS,CACvC,gBAAiBtB,EACjB,CAACyB,EAAW,EAAG,CAACd,EAAaC,IAC3BX,EAAQU,EAAK,CAAE,GAAGC,EAAM,SAAU,OAAQ,CAAC,CAC/C,CAAC,EAED,MAAO,CAAE,SAAAQ,EAAU,KAAAG,CAAK,CAC1B,GAAG,EAEH,OAAAV,EAAYE,EAEZA,EAAQ,MAAM,IAAM,CACdF,IAAcE,IAASF,EAAY,KACzC,CAAC,EAEME,CACT,EAvFiB,YAgGXW,EAAahB,EAAA,CACjBU,EACAO,IAC2B,CAG3B,GAFAA,EAAK,IAAI,YAAa7B,EAAQ,QAAQ,EAElCA,EAAQ,eAAiB,OAAW,MAAO,CAAC,EAEhD,IAAM8B,EAAUR,EAAS,uCAAyC,CAChE,qBACF,EAEA,GAAIQ,EAAQ,SAAS,qBAAqB,EAAG,CAE3C,IAAMC,EAAO,mBAAmB/B,EAAQ,QAAQ,EAC1CgC,EAAO,mBAAmBhC,EAAQ,YAAY,EAEpD,MAAO,CACL,cAAe,SAAS,OAAO,KAAK,GAAG+B,CAAI,IAAIC,CAAI,EAAE,EAAE,SAAS,QAAQ,CAAC,EAC3E,CACF,CAEA,GAAIF,EAAQ,SAAS,oBAAoB,EACvC,OAAAD,EAAK,IAAI,gBAAiB7B,EAAQ,YAAY,EAEvC,CAAC,EAGV,MAAM,IAAIqB,EACR,2EACF,CACF,EA/BmB,cAiCbY,EAAYrB,EAAA,MAChBU,EACAO,IACmC,CACnC,IAAMK,EAAU,CACd,eAAgB,oCAChB,OAAQ,mBACR,GAAGN,EAAWN,EAAUO,CAAI,CAC9B,EACIT,EAEJ,GAAI,CACFA,EAAW,MAAMT,EAAWW,EAAS,eAAgB,CACnD,OAAQ,OACR,QAAAY,EACA,KAAML,EAAK,SAAS,CACtB,CAAC,CACH,OAASnB,EAAO,CACd,MAAM,IAAIW,EACR,yDACA,OACA,CACE,MAAOX,CACT,CACF,CACF,CAEA,IAAMyB,EAAkB,MAAMf,EAAS,KAAK,EAAE,MAAM,IAAG,EAAY,EAEnE,GAAI,CAACA,EAAS,GAAI,CAChB,IAAMgB,EACJC,EAASF,CAAM,GAAK,OAAOA,EAAO,OAAU,SACxCA,EAAO,MAAM,MAAM,EAAG,EAAE,EACxB,OAEN,MAAM,IAAId,EACR,gCAAgCD,EAAS,MAAM,GAC/CgB,CACF,CACF,CAEA,GAAI,CAACE,GAAgBH,CAAM,EACzB,MAAM,IAAId,EACR,4CACF,EAGF,OAAOc,CACT,EAhDkB,aAkDZI,EAAgB3B,EAAA,MACpBU,EACAG,EACAe,EACAC,IACwB,CACxB,IAAIC,EAEJ,GAAI,EACA,CAAE,QAAAA,CAAQ,EAAI,MAAMC,GAAUH,EAASf,EAAM,CAC7C,OAAQH,EAAS,OACjB,SAAUtB,EAAQ,SAClB,WAAAI,EACA,eAAgBJ,EAAQ,uBAAyB,GACjD,eAAgB,CAAC,MAAO,MAAO,KAAK,CACtC,CAAC,EACH,OAASU,EAAO,CACd,MAAM,IAAIW,EACR,8CACA,OACA,CAAE,MAAOX,CAAM,CACjB,CACF,CAEA,GAAIgC,EAAQ,QAAUD,EACpB,MAAM,IAAIpB,EACR,0CACF,EAKF,KAFkB,MAAM,QAAQqB,EAAQ,GAAG,EAAIA,EAAQ,IAAM,CAACA,EAAQ,GAAG,GAG5D,OAAS,GAAKA,EAAQ,MAAQ,SACzCA,EAAQ,MAAQ1C,EAAQ,SAExB,MAAM,IAAIqB,EAAsB,sCAAsC,EAGxE,OAAOqB,CACT,EAxCsB,iBA0CtB,MAAO,CACL,MAAM,iBAAiB,CAAE,MAAAE,EAAO,cAAAC,EAAe,YAAAC,EAAa,MAAAL,CAAM,EAAG,CACnE,GAAIK,IAAgB9C,EAAQ,YAC1B,MAAM,IAAIqB,EACR,6EACF,EAGF,GAAM,CAAE,SAAAC,CAAS,EAAI,MAAMN,EAAS,EAC9BH,EAAM,IAAI,IAAIS,EAAS,sBAAsB,EAEnD,OAAW,CAACf,EAAKwC,CAAK,IAAK,OAAO,QAChC/C,EAAQ,0BAA4B,CAAC,CACvC,EACEa,EAAI,aAAa,IAAIN,EAAKwC,CAAK,EAGjC,OAAAlC,EAAI,aAAa,IAAI,gBAAiB,MAAM,EAC5CA,EAAI,aAAa,IAAI,YAAab,EAAQ,QAAQ,EAClDa,EAAI,aAAa,IAAI,eAAgBiC,CAAW,EAChDjC,EAAI,aAAa,IAAI,QAASb,EAAQ,OAAO,KAAK,GAAG,CAAC,EACtDa,EAAI,aAAa,IAAI,QAAS+B,CAAK,EACnC/B,EAAI,aAAa,IAAI,QAAS4B,CAAK,EACnC5B,EAAI,aAAa,IAAI,iBAAkBgC,CAAa,EACpDhC,EAAI,aAAa,IAAI,wBAAyB,MAAM,EAE7CA,EAAI,SAAS,CACtB,EAEA,MAAM,aAAa,CAAE,KAAAuB,EAAM,aAAAY,EAAc,YAAAF,EAAa,MAAAL,EAAO,OAAAQ,CAAO,EAAG,CACrE,GAAM,CAAE,SAAA3B,EAAU,KAAAG,CAAK,EAAI,MAAMT,EAAS,EAI1C,GAAIiC,IAAW,QAAaA,IAAW3B,EAAS,OAC9C,MAAM,IAAID,EACR,iDACF,EAGF,GACE4B,IAAW,QACX3B,EAAS,iDAAmD,GAE5D,MAAM,IAAID,EACR,+CACF,EAGF,IAAM6B,EAAS,MAAMjB,EACnBX,EACA,IAAI,gBAAgB,CAClB,WAAY,qBACZ,KAAAc,EACA,aAAcU,EACd,cAAeE,CACjB,CAAC,CACH,EAEA,GAAIE,EAAO,WAAa,OACtB,MAAM,IAAI7B,EAAsB,sCAAsC,EAGxE,IAAM8B,EAAS,MAAMZ,EAAcjB,EAAUG,EAAMyB,EAAO,SAAUT,CAAK,EACnEW,EAAUD,EAAO,IAEvB,GAAI,OAAOC,GAAY,UAAYA,IAAY,GAC7C,MAAM,IAAI/B,EAAsB,+BAA+B,EAGjE,IAAMgC,EAAc,CAClBF,EAAO,KACPA,EAAO,MACPA,EAAO,kBACT,EAAE,KACCJ,GAA2B,OAAOA,GAAU,UAAYA,IAAU,EACrE,EAYA,MAX6B,CAC3B,QAAAK,EACA,OAAQ,CAAE,GAAGD,CAAO,EACpB,YAAaD,EAAO,aACpB,UAAWA,EAAO,YAAc,IAChC,GAAIG,IAAgB,OAAY,CAAE,YAAAA,CAAY,EAAI,CAAC,EACnD,GAAIH,EAAO,gBAAkB,OACzB,CAAE,aAAcA,EAAO,aAAc,EACrC,CAAC,CACP,CAGF,EAEA,MAAM,QAAQI,EAAuC,CACnD,GAAM,CAAE,SAAAhC,CAAS,EAAI,MAAMN,EAAS,EAC9BkC,EAAS,MAAMjB,EACnBX,EACA,IAAI,gBAAgB,CAClB,WAAY,gBACZ,cAAegC,CACjB,CAAC,CACH,EAEA,MAAO,CACL,YAAaJ,EAAO,aACpB,UAAWA,EAAO,YAAc,IAChC,GAAIA,EAAO,gBAAkB,OACzB,CAAE,aAAcA,EAAO,aAAc,EACrC,CAAC,CACP,CACF,EAEA,MAAM,OAAOI,EAAc,CACzB,GAAM,CAAE,SAAAhC,CAAS,EAAI,MAAMN,EAAS,EAEpC,GAAIM,EAAS,sBAAwB,OAAW,OAEhD,IAAMO,EAAO,IAAI,gBAAgB,CAC/B,MAAOyB,EACP,gBAAiB,eACnB,CAAC,EACKpB,EAAU,CACd,eAAgB,oCAChB,GAAGN,EAAWN,EAAUO,CAAI,CAC9B,EAEA,GAAI,CACF,MAAMlB,EAAWW,EAAS,oBAAqB,CAC7C,OAAQ,OACR,QAAAY,EACA,KAAML,EAAK,SAAS,CACtB,CAAC,CACH,OAASnB,EAAO,CACd,MAAM,IAAIW,EACR,8DACA,OACA,CACE,MAAOX,CACT,CACF,CACF,CACF,CACF,CACF,CA9ZgBE,EAAAb,GAAA,sBGxCT,IAAMwD,GAAN,KAAuB,CAf9B,MAe8B,CAAAC,EAAA,yBACnBC,GAAQ,IAAI,IACZC,GAAQ,IAAI,IACZC,GAAe,IAAI,IACnBC,GACAC,GACAC,GAQT,YAAYC,EAAaC,EAAgBC,EAAmB,CAC1D,KAAKL,GAAOG,EACZ,KAAKF,GAAUG,EACf,KAAKF,GAAOG,CACd,CAEAC,GAAQC,EAA4B,CAGlC,IAFA,KAAKT,GAAM,OAAOS,EAAO,QAAQ,EAE1B,KAAKT,GAAM,MAAQ,KAAKE,IAAM,CACnC,IAAMQ,EAAS,KAAKV,GAAM,KAAK,EAAE,KAAK,EAEtC,GAAIU,EAAO,OAAS,GAAM,MAE1B,KAAKV,GAAM,OAAOU,EAAO,KAAK,CAChC,CAEA,KAAKV,GAAM,IAAIS,EAAO,SAAU,CAAE,OAAAA,EAAQ,OAAQ,KAAKL,GAAK,CAAE,CAAC,CACjE,CAMA,IAAIK,EAA4B,CAC9B,KAAKV,GAAM,OAAOU,EAAO,QAAQ,EACjC,KAAKT,GAAM,OAAOS,EAAO,QAAQ,EAG/BA,EAAO,OAAS,WACf,KAAKR,GAAa,IAAIQ,EAAO,QAAQ,GAAK,GAAK,EAEhD,KAAKV,GAAM,IAAIU,EAAO,SAAUA,CAAM,EAEtC,KAAKD,GAAQC,CAAM,CAEvB,CAOA,IAAIE,EAA4C,CAC9C,IAAMC,EAAO,KAAKb,GAAM,IAAIY,CAAQ,EAEpC,GAAIC,IAAS,OAAW,OAAOA,EAE/B,IAAMC,EAAO,KAAKb,GAAM,IAAIW,CAAQ,EAEpC,GAAIE,IAAS,SAEb,KAAKb,GAAM,OAAOW,CAAQ,EAEtB,CAAAG,EAAWD,EAAK,OAAS,KAAKV,GAAS,KAAKC,GAAK,CAAC,GAGtD,YAAKJ,GAAM,IAAIW,EAAU,CAAE,OAAQE,EAAK,OAAQ,OAAQ,KAAKT,GAAK,CAAE,CAAC,EAE9DS,EAAK,MACd,CAMA,WAAWF,EAAwB,CACjC,KAAKV,GAAa,IAAIU,GAAW,KAAKV,GAAa,IAAIU,CAAQ,GAAK,GAAK,CAAC,EAE1E,IAAME,EAAO,KAAKb,GAAM,IAAIW,CAAQ,EAEhCE,IAAS,SACX,KAAKb,GAAM,OAAOW,CAAQ,EAC1B,KAAKZ,GAAM,IAAIY,EAAUE,EAAK,MAAM,EAExC,CAMA,aAAaF,EAAwB,CACnC,IAAMI,GAAS,KAAKd,GAAa,IAAIU,CAAQ,GAAK,GAAK,EAEvD,GAAII,EAAQ,EAAG,CACb,KAAKd,GAAa,IAAIU,EAAUI,CAAK,EAErC,MACF,CAEA,KAAKd,GAAa,OAAOU,CAAQ,EAEjC,IAAMC,EAAO,KAAKb,GAAM,IAAIY,CAAQ,EAEhCC,IAAS,QAAaA,EAAK,OAAS,WACtC,KAAKb,GAAM,OAAOY,CAAQ,EAC1B,KAAKH,GAAQI,CAAI,EAErB,CACF,ECxHO,IAAMI,EAAN,KAAoB,CAR3B,MAQ2B,CAAAC,EAAA,mBAChBC,GAAS,IAAI,IACbC,GACAC,GACAC,GACAC,GAST,YACEC,EACAC,EACAC,EACAC,EAA+B,IAAM,CAAC,EACtC,CACA,KAAKP,GAAOI,EACZ,KAAKF,GAAOG,EACZ,KAAKJ,GAAaK,EAClB,KAAKH,GAAYI,CACnB,CAEAC,GAAQC,EAAaC,EAAgB,CACnC,KAAKX,GAAO,OAAOU,CAAG,EACtB,KAAKN,GAAUO,CAAK,CACtB,CAOA,IAAID,EAA4B,CAC9B,IAAMC,EAAQ,KAAKX,GAAO,IAAIU,CAAG,EAEjC,GAAIC,IAAU,OAEd,IAAIC,EAAW,KAAKV,GAAWS,CAAK,EAAG,KAAKR,GAAK,CAAC,EAAG,CACnD,KAAKM,GAAQC,EAAKC,CAAK,EAEvB,MACF,CAEA,OAAOA,EACT,CAOA,IAAID,EAAaC,EAAgB,CAC/B,IAAME,EAAW,KAAKb,GAAO,IAAIU,CAAG,EAIpC,IAFIG,IAAa,QAAW,KAAKJ,GAAQC,EAAKG,CAAQ,EAE/C,KAAKb,GAAO,MAAQ,KAAKC,IAAM,CACpC,IAAMa,EAAS,KAAKd,GAAO,QAAQ,EAAE,KAAK,EAE1C,GAAIc,EAAO,OAAS,GAAM,MAE1B,KAAKL,GAAQK,EAAO,MAAM,CAAC,EAAGA,EAAO,MAAM,CAAC,CAAC,CAC/C,CAEA,KAAKd,GAAO,IAAIU,EAAKC,CAAK,CAC5B,CAQA,KAAKD,EAA4B,CAC/B,OAAO,KAAKV,GAAO,IAAIU,CAAG,CAC5B,CASA,KAAKA,EAAaK,EAAiB,GAAsB,CACvD,IAAMJ,EAAQI,EAAiB,KAAKf,GAAO,IAAIU,CAAG,EAAI,KAAK,IAAIA,CAAG,EAElE,OAAIC,IAAU,QAAW,KAAKF,GAAQC,EAAKC,CAAK,EAEzCA,CACT,CAMA,OAAOD,EAAmB,CACxB,IAAMC,EAAQ,KAAKX,GAAO,IAAIU,CAAG,EAE7BC,IAAU,QAAW,KAAKF,GAAQC,EAAKC,CAAK,CAClD,CAOA,QAAc,CACZ,IAAML,EAAM,KAAKH,GAAK,EAEtB,MAAO,CAAC,GAAG,KAAKH,GAAO,OAAO,CAAC,EAAE,OAC9BW,GAAU,CAACC,EAAW,KAAKV,GAAWS,CAAK,EAAGL,CAAG,CACpD,CACF,CAOA,YAAYU,EAAmC,CAC7C,OAAW,CAACN,EAAKC,CAAK,IAAK,KAAKX,GAC1BgB,EAAKL,CAAK,GAAG,KAAKF,GAAQC,EAAKC,CAAK,CAE5C,CACF,ECnHA,IAAMM,GAASC,EAACC,GAA0CA,EAAM,UAAjD,UAQf,SAASC,EAAUC,EAA2B,CAC5C,GAAI,CACF,OAAO,QAAQ,QAAQA,EAAK,CAAC,CAC/B,OAASC,EAAO,CACd,OAAO,QAAQ,OACbA,aAAiB,MAAQA,EAAQ,IAAI,MAAM,sBAAsB,CACnE,CACF,CACF,CARSJ,EAAAE,EAAA,UA6BF,SAASG,GACdC,EAA8B,CAAC,EACnB,CACZ,IAAMC,EAAMD,EAAQ,yBAA2BE,GACzCC,EAAMC,GAAaJ,EAAQ,KAAO,KAAK,GAAG,EAC1CK,EAAU,IAAIC,GAClBL,GACCD,EAAQ,0BAA4BO,IACnC,IACFJ,CACF,EACMK,EAAW,IAAIC,EAGlBR,EAAKE,EAAKV,EAAM,EACbiB,EAAU,IAAID,EAAiCR,EAAKE,EAAKV,EAAM,EAC/DkB,EAAW,IAAIF,EAA2BR,EAAKE,EAAKV,EAAM,EAC1DmB,EAAW,IAAIH,EAGlBR,EAAKE,EAAKV,EAAM,EACboB,EAAQ,IAAIJ,EAIfR,EAAKE,EAAKV,EAAM,EACbqB,EAAS,IAAIL,EAAwBR,EAAKE,EAAKV,GAASsB,GAAU,CACtEV,EAAQ,aAAaU,EAAM,QAAQ,CACrC,CAAC,EACKC,EAAS,IAAIP,EAA8BR,EAAKE,EAAKV,EAAM,EAC3DwB,EAAU,IAAIR,EAA+BR,EAAKE,EAAKV,EAAM,EAEnE,MAAO,CACL,UAAUyB,EAAU,CAClB,OAAOtB,EAAO,IACLS,EAAQ,IAAIa,CAAQ,CAC5B,CACH,EACA,UAAUC,EAAQ,CAChB,OAAOvB,EAAO,IAAM,CAClBS,EAAQ,IAAIc,CAAM,CACpB,CAAC,CACH,EACA,wBAAwBC,EAAK,CAC3B,OAAOxB,EAAO,IACLY,EAAS,IAAIY,CAAG,GAAG,MAC3B,CACH,EACA,wBAAwBA,EAAKD,EAAQE,EAAW,CAC9C,OAAOzB,EAAO,IAAM,CAClBY,EAAS,IAAIY,EAAK,CAAE,OAAAD,EAAQ,UAAAE,CAAU,CAAC,CACzC,CAAC,CACH,EACA,wBAAwBC,EAAQ,CAC9B,OAAO1B,EAAO,IAAM,CAClBc,EAAQ,IAAIY,EAAO,GAAIA,CAAM,CAC/B,CAAC,CACH,EACA,wBAAwBC,EAAI,CAC1B,OAAO3B,EAAO,IACLc,EAAQ,IAAIa,CAAE,CACtB,CACH,EACA,4BAA4BA,EAAI,CAC9B,OAAO3B,EAAO,IACLc,EAAQ,KAAKa,CAAE,CACvB,CACH,EACA,kBAAkBD,EAAQ,CACxB,OAAO1B,EAAO,IAAM,CAClBe,EAAS,IAAIW,EAAO,GAAIA,CAAM,CAChC,CAAC,CACH,EACA,kBAAkBC,EAAI,CACpB,OAAO3B,EAAO,IAAMe,EAAS,KAAKY,CAAE,CAAC,CACvC,EACA,kBAAkBC,EAAKC,EAAgBJ,EAAW,CAChD,OAAOzB,EAAO,IAAM,CAClBgB,EAAS,IAAIY,EAAK,CAAE,eAAAC,EAAgB,UAAAJ,CAAU,CAAC,CACjD,CAAC,CACH,EACA,kBAAkBG,EAAK,CACrB,OAAO5B,EAAO,IAAMgB,EAAS,IAAIY,CAAG,GAAG,cAAc,CACvD,EACA,oBAAoBE,EAAS,CAC3B,OAAO9B,EAAO,IAAMkB,EAAO,OAAO,EAAE,OAAQa,GAAMA,EAAE,UAAYD,CAAO,CAAC,CAC1E,EACA,sBAAsBH,EAAI,CACxB,OAAO3B,EAAO,IAELe,EAAS,KAAKY,EAAI,EAAI,CAC9B,CACH,EACA,qBAAqBD,EAAQ,CAC3B,OAAO1B,EAAO,IAAM,CAClBiB,EAAM,IAAIS,EAAO,SAAU,CACzB,OAAAA,EACA,SAAU,GACV,UAAWA,EAAO,SACpB,CAAC,CACH,CAAC,CACH,EACA,qBAAqBM,EAAU,CAC7B,OAAOhC,EAAO,IAAM,CAClB,IAAMiC,EAAQhB,EAAM,IAAIe,CAAQ,EAEhC,OAAOC,IAAU,OACb,OACA,CAAE,OAAQA,EAAM,OAAQ,SAAUA,EAAM,QAAS,CACvD,CAAC,CACH,EACA,yBAAyBD,EAAU,CACjC,OAAOhC,EAAO,IAAM,CAElB,IAAMiC,EAAQhB,EAAM,IAAIe,CAAQ,EAEhC,GAAIC,IAAU,OAAW,OAEzB,IAAMC,EAAoC,CACxC,OAAQD,EAAM,OACd,SAAU,CAACA,EAAM,QACnB,EAEA,OAAAhB,EAAM,IAAIe,EAAU,CAAE,GAAGC,EAAO,SAAU,EAAK,CAAC,EAEzCC,CACT,CAAC,CACH,EACA,SAASf,EAAO,CACd,OAAOnB,EAAO,IAAM,CAGlBS,EAAQ,WAAWU,EAAM,QAAQ,EACjCD,EAAO,IAAIC,EAAM,QAASA,CAAK,CACjC,CAAC,CACH,EACA,SAASgB,EAAS,CAChB,OAAOnC,EAAO,IACLkB,EAAO,IAAIiB,CAAO,CAC1B,CACH,EACA,YAAYA,EAASC,EAA6B,CAChD,OAAOpC,EAAO,IAAM,CAClB,IAAMmB,EAAQD,EAAO,IAAIiB,CAAO,EAEhC,OAAIhB,IAAU,OAAkB,IAEhCV,EAAQ,WAAWU,EAAM,QAAQ,EACjCD,EAAO,IAAIiB,EAAS,CAAE,GAAGhB,EAAO,GAAGiB,CAAO,CAAC,EAEpC,GACT,CAAC,CACH,EACA,YAAYD,EAAS,CACnB,OAAOnC,EAAO,IAAM,CAClB,IAAMqC,EAAUnB,EAAO,KAAKiB,EAAS,EAAI,EAEzC,OAAAf,EAAO,YAAakB,GAAUA,EAAM,UAAYH,CAAO,EACvDd,EAAQ,YAAaiB,GAAUA,EAAM,UAAYH,CAAO,EACxDlB,EAAM,YAAasB,GAASA,EAAK,OAAO,UAAYJ,CAAO,EAEpDE,CACT,CAAC,CACH,EACA,eAAeC,EAAO,CACpB,OAAOtC,EAAO,IAAM,CAClBoB,EAAO,IAAIkB,EAAM,UAAWA,CAAK,CACnC,CAAC,CACH,EACA,eAAeE,EAAW,CACxB,OAAOxC,EAAO,IACLoB,EAAO,IAAIoB,CAAS,CAC5B,CACH,EACA,kBAAkBA,EAAW,CAC3B,OAAOxC,EAAO,IAAM,CAClBoB,EAAO,OAAOoB,CAAS,CACzB,CAAC,CACH,EACA,gBAAgBF,EAAO,CACrB,OAAOtC,EAAO,IAAM,CAClBqB,EAAQ,IAAIiB,EAAM,UAAWA,CAAK,CACpC,CAAC,CACH,EACA,gBAAgBE,EAAW,CACzB,OAAOxC,EAAO,IACLqB,EAAQ,IAAImB,CAAS,CAC7B,CACH,EACA,gBAAgBA,EAAWC,EAA+C,CACxE,OAAOzC,EAAO,IAAM,CAClB,IAAMsC,EAAQjB,EAAQ,IAAImB,CAAS,EAEnC,GAAIF,IAAU,OAAW,OAEzB,GAAIA,EAAM,SAAW,OACnB,MAAO,CAAE,OAAQA,EAAO,eAAgBA,EAAM,MAAO,EAGvD,IAAMI,EAAS,CAAE,GAAGJ,EAAO,OAAAG,CAAO,EAElC,OAAApB,EAAQ,IAAImB,EAAWE,CAAM,EAEtB,CAAE,OAAQA,CAAO,CAC1B,CAAC,CACH,CACF,CACF,CA/MgB5C,EAAAK,GAAA",
6
- "names": ["McpAuthConfigError", "__name", "message", "jsonCopy", "value", "limit", "label", "text", "__name", "DEFAULT_LIFETIMES", "LIFETIME_BOUNDS", "DEFAULT_LIMITS", "DEFAULT_SCOPES", "DEFAULT_REFRESH_REUSE_GRACE_SECONDS", "MAX_REFRESH_REUSE_GRACE_SECONDS", "DEFAULT_COOKIE_PREFIX", "DEFAULT_MEMORY_STORE_ENTRIES", "DEFAULT_DYNAMIC_CLIENT_IDLE_SECONDS", "UPSTREAM_EXPIRY_SKEW_MS", "UPSTREAM_CACHE_GRANTS", "UPSTREAM_LIFETIME_CEILING_SECONDS", "CLIENT_ID_LENGTH_LIMIT", "METADATA_MAX_AGE_SECONDS", "DEFAULT_CONSENT_REPLAY_SECONDS", "MAX_CONSENT_REPLAY_SECONDS", "PROTECTED_RESOURCE_METADATA_PATH", "AUTHORIZATION_SERVER_METADATA_PATH", "LOOPBACK_HOSTS", "CONSENT_FIELDS", "CONSENT_APPROVE", "FORM_MEDIA_TYPE", "JSON_MEDIA_TYPE", "NO_STORE", "CORS", "json", "status", "body", "options", "headers", "JSON_MEDIA_TYPE", "METADATA_MAX_AGE_SECONDS", "__name", "oauthError", "error", "description", "challenge", "preflight", "methods", "methodNotAllowed", "allow", "pageHeaders", "formActionOrigins", "scriptHashes", "formAction", "h", "html", "cookie", "redirect", "location", "setCookies", "createHash", "randomBytes", "timingSafeEqual", "SECRET_BYTES", "ENCODED_32_BYTES", "randomToken", "randomBytes", "__name", "sha256Hex", "value", "createHash", "pkceChallenge", "verifier", "constantTimeEqual", "a", "b", "left", "right", "timingSafeEqual", "BASE64URL_EXTRA", "VERIFIER_EXTRA", "charsetCheck", "extra", "text", "i", "c", "onlyBase64Url", "onlyVerifierChars", "isWellFormedVerifier", "isWellFormedChallenge", "challenge", "isOpaqueToken", "pkceMatches", "deepFreeze", "value", "key", "__name", "frozenCopy", "freezeDeep", "grantContextFor", "context", "grant", "hook", "result", "frozenCopy", "jsonCopy", "__name", "isRecord", "value", "__name", "isStringArray", "item", "grantSummary", "grant", "frozenCopy", "__name", "hasExpired", "expiresAt", "now", "__name", "checkedClock", "clock", "value", "issueTokenPair", "context", "grant", "scopes", "refreshScopes", "options", "now", "accessToken", "randomToken", "accessExpiresAt", "sha256Hex", "response", "refreshToken", "__name", "liveGrant", "grantId", "clientId", "hasExpired", "revokeUpstreamToken", "provider", "revokeSealedUpstreamToken", "sealed", "plaintext", "revokeGrantById", "reason", "onRevoking", "grantSummary", "latest", "deleted", "isRecord", "quoted", "value", "__name", "challengeStatus", "error", "bearerChallenge", "context", "options", "params", "scope", "status", "serverFailure", "bearerToken", "request", "header", "trimmed", "space", "token", "authenticateRequest", "upstream", "invalid", "description", "isOpaqueToken", "row", "sha256Hex", "hasExpired", "grant", "liveGrant", "grantContext", "grantContextFor", "ERROR_PAGES", "clientIp", "options", "request", "custom", "hops", "hop", "__name", "TAG_PATTERN", "parseLocales", "header", "ranked", "order", "part", "rawTag", "params", "tag", "qParam", "p", "q", "a", "b", "entry", "__name", "REASON_PATTERN", "decide", "check", "verdict", "judge", "__name", "isRecord", "reason", "permits", "createContext", "options", "emit", "__name", "event", "request", "scope", "limiter", "ok", "permits", "clientIp", "reason", "extra", "text", "ERROR_PAGES", "page", "frozenCopy", "parseLocales", "html", "DISALLOWED", "hasHiddenCharacters", "value", "__name", "HTML_ESCAPES", "escapeHtml", "char", "isScopeToken", "i", "code", "trimTrailing", "text", "end", "document", "title", "body", "escapeHtml", "__name", "provenance", "context", "unchecked", "escapeHtml", "__name", "correctionFields", "errors", "submitted", "names", "name", "index", "id", "message", "value", "note", "described", "defaultConsentPage", "title", "delivery", "account", "scopeItems", "scope", "description", "detail", "fields", "decision", "corrections", "document", "defaultErrorPage", "isMetadataDocumentClientId", "clientId", "CLIENT_ID_LENGTH_LIMIT", "parsed", "__name", "cleanClientName", "value", "maxLength", "name", "hasHiddenCharacters", "isSupportedGrantType", "parseGrantTypes", "strict", "isStringArray", "known", "parseClientScopes", "supported", "requested", "s", "usable", "DEFAULT_REDIRECT_URI_LENGTH", "RESERVED_REDIRECT_PARAMETERS", "tryParse", "text", "__name", "isLoopbackHttp", "url", "LOOPBACK_HOSTS", "isLoopbackRedirectUri", "uri", "redirectUriProblem", "limits", "name", "isAcceptableRedirectUri", "isDigit", "code", "portless", "head", "tail", "i", "redirectUriMatches", "registered", "requested", "a", "b", "matchesAnyRedirectUri", "redirectHostOf", "originOf", "buildRedirect", "redirectUri", "params", "target", "value", "resolveScopeDescriptions", "scopes", "descriptions", "scope", "text", "McpAuthConfigError", "hasHiddenCharacters", "own", "__name", "resolveStaticClients", "clients", "limits", "now", "byId", "client", "id", "label", "isMetadataDocumentClientId", "uri", "problem", "redirectUriProblem", "clientName", "cleanClientName", "grantTypes", "parseGrantTypes", "freezeDeep", "parseServerUrl", "name", "value", "allowInsecureLocalhost", "url", "McpAuthConfigError", "local", "LOOPBACK_HOSTS", "__name", "trimPath", "pathname", "trimTrailing", "seconds", "fallback", "min", "max", "chosen", "positive", "resolveLimits", "limits", "pick", "key", "DEFAULT_LIMITS", "checkPath", "path", "segments", "segment", "resolvePaths", "issuerPath", "paths", "resolved", "all", "SCRIPT_HASH", "COOKIE_PREFIX", "resolveOptions", "options", "renderers", "insecure", "issuerUrl", "parseServerUrl", "issuerPath", "trimPath", "issuer", "McpAuthConfigError", "resourceUrl", "resourcePath", "scopes", "DEFAULT_SCOPES", "isScopeToken", "defaultScopes", "scope", "graceSeconds", "DEFAULT_REFRESH_REUSE_GRACE_SECONDS", "MAX_REFRESH_REUSE_GRACE_SECONDS", "lifetimes", "lifetime", "__name", "key", "seconds", "DEFAULT_LIFETIMES", "LIFETIME_BOUNDS", "paths", "resolvePaths", "cookiePrefix", "DEFAULT_COOKIE_PREFIX", "limits", "resolveLimits", "now", "checkedClock", "scriptHashes", "hash", "replaySeconds", "DEFAULT_CONSENT_REPLAY_SECONDS", "MAX_CONSENT_REPLAY_SECONDS", "prm", "PROTECTED_RESOURCE_METADATA_PATH", "asm", "AUTHORIZATION_SERVER_METADATA_PATH", "resourceMetadataPaths", "authorizationServerMetadataPaths", "reserved", "name", "path", "resolveScopeDescriptions", "resolveStaticClients", "safeFetch", "ClientMetadataError", "__name", "reason", "validateClientMetadata", "url", "document", "now", "options", "isRecord", "ClientMetadataError", "methods", "isStringArray", "grantTypes", "parseGrantTypes", "scopes", "parseClientScopes", "redirectUris", "uri", "isAcceptableRedirectUri", "clientName", "cleanClientName", "__name", "bundledFetcher", "init", "safeFetch", "beforeAbort", "work", "signal", "resolve", "reject", "onAbort", "value", "error", "failureReason", "readLimited", "response", "limit", "declared", "reader", "parts", "size", "chunk", "bytes", "cacheControlSeconds", "header", "seconds", "part", "rawName", "rawValue", "fetchClientMetadata", "fetcher", "text", "maxAgeSeconds", "hostOf", "url", "__name", "serverIdentity", "value", "host", "trimTrailing", "path", "isIssuerUrl", "issuer", "given", "resolveClient", "context", "clientId", "options", "CLIENT_ID_LENGTH_LIMIT", "isMetadataDocumentClientId", "listed", "stored", "allow", "permits", "cached", "failures", "inflight", "retryAfter", "pending", "download", "downloadAndRemember", "startedAt", "fetched", "fetchClientMetadata", "error", "rememberFailure", "failureReason", "stated", "cacheMs", "oldest", "readCookie", "request", "name", "header", "part", "index", "__name", "hostCookie", "value", "maxAgeSeconds", "clearHostCookie", "bindingCookieName", "prefix", "flowId", "sha256Hex", "groupParams", "search", "params", "key", "value", "values", "__name", "single", "params", "name", "value", "__name", "repeatedDefined", "defined", "splitAuthority", "value", "start", "pathStart", "end", "__name", "sameResource", "configured", "given", "a", "b", "resolveScopes", "requested", "permitted", "fallback", "maxLength", "asked", "s", "isScopeToken", "__name", "AUTHORIZE_PARAMETERS", "handleAuthorize", "context", "request", "options", "limits", "params", "groupParams", "refuse", "__name", "reason", "clientId", "extra", "repeatedDefined", "single", "client", "resolveClient", "sent", "onlyRegistered", "redirectUri", "matchesAnyRedirectUri", "state", "codeChallenge", "isWellFormedChallenge", "resource", "sameResource", "permitted", "fallback", "s", "scopes", "resolveScopes", "policy", "outcome", "decide", "frozenCopy", "flowId", "randomToken", "binding", "upstreamVerifier", "nonce", "pending", "sha256Hex", "location", "pkceChallenge", "redirect", "hostCookie", "bindingCookieName", "randomUUID", "documentHost", "url", "__name", "consentPageResponse", "context", "request", "consent", "formToken", "extras", "options", "page", "frozenCopy", "redirectHostOf", "isLoopbackRedirectUri", "CONSENT_FIELDS", "parseLocales", "target", "originOf", "cookies", "hostCookie", "bindingCookieName", "html", "SUBJECT_LENGTH_LIMIT", "isUsableSubject", "subject", "hasHiddenCharacters", "__name", "readMapped", "value", "displayName", "resolvePerson", "context", "login", "client", "mapSubject", "allowSubject", "limits", "claims", "rawName", "mapped", "frozenCopy", "cleanClientName", "person", "outcome", "decide", "CALLBACK_PARAMETERS", "handleCallback", "context", "request", "options", "params", "groupParams", "repeatedDefined", "flowId", "single", "cookieName", "bindingCookieName", "dropCookie", "clearHostCookie", "observed", "hasExpired", "binding", "readCookie", "constantTimeEqual", "sha256Hex", "pending", "providerError", "cancelled", "redirect", "buildRedirect", "code", "iss", "login", "error", "releaseUpstream", "__name", "revokeUpstreamToken", "client", "resolved", "resolvePerson", "person", "details", "jsonCopy", "frozenCopy", "now", "grantId", "randomUUID", "formToken", "randomToken", "sealer", "lifetime", "UPSTREAM_LIFETIME_CEILING_SECONDS", "consent", "response", "consentPageResponse", "BodyTooLargeError", "__name", "hasContentType", "request", "expected", "value", "type", "__name", "readBody", "maxBytes", "declared", "BodyTooLargeError", "reader", "chunks", "total", "chunk", "bytes", "RESERVED_FIELDS", "CONSENT_FIELDS", "FIELD_ERROR_LIMIT", "FIELD_TEXT_LIMIT", "extraFields", "form", "fields", "name", "values", "first", "__name", "readFieldErrors", "value", "isRecord", "entries", "errors", "message", "hookClient", "consent", "readFields", "context", "hook", "result", "frozenCopy", "jsonCopy", "fieldErrors", "handleConsent", "request", "options", "origin", "hasContentType", "FORM_MEDIA_TYPE", "groupParams", "readBody", "error", "BodyTooLargeError", "repeatedDefined", "interactionId", "single", "formToken", "store", "sealer", "cookieName", "bindingCookieName", "dropCookie", "clearHostCookie", "binding", "readCookie", "ended", "replayKey", "sha256Hex", "replay", "sealed", "outcome", "hasExpired", "redirect", "releaseUpstream", "revokeSealedUpstreamToken", "abandon", "tokenOk", "constantTimeEqual", "browserOk", "approve", "CONSENT_APPROVE", "grantData", "verdict", "consentPageResponse", "answer", "params", "location", "buildRedirect", "expiresAt", "before", "decide", "now", "grant", "code", "randomToken", "after", "grantSummary", "badMetadata", "description", "oauthError", "__name", "badRedirect", "handleRegister", "context", "request", "options", "limits", "hasContentType", "body", "readBody", "error", "BodyTooLargeError", "isRecord", "uris", "isStringArray", "uri", "problem", "redirectUriProblem", "authMethod", "grantTypes", "parseGrantTypes", "responseTypes", "scopes", "parseClientScopes", "clientName", "cleanClientName", "policy", "permits", "frozenCopy", "issuedAt", "redirectUris", "client", "randomToken", "redirectHostOf", "json", "EndpointRefusal", "error", "description", "status", "challenge", "detail", "__name", "AUTH_SCHEME", "CLIENT_PARAMETERS", "schemeOf", "header", "scheme", "__name", "readPublicClientForm", "request", "maxBytes", "defined", "hasContentType", "FORM_MEDIA_TYPE", "EndpointRefusal", "form", "groupParams", "readBody", "error", "BodyTooLargeError", "repeated", "repeatedDefined", "authorization", "refusal", "single", "requireOwnResource", "context", "resource", "sameResource", "DONE_HEADERS", "REVOKE_PARAMETERS", "handleRevoke", "context", "request", "revoke", "store", "limits", "done", "__name", "oauthError", "form", "clientId", "readPublicClientForm", "token", "single", "EndpointRefusal", "isOpaqueToken", "hash", "sha256Hex", "callerMayAct", "owner", "asRefreshToken", "found", "asAccessToken", "order", "attempt", "error", "requireTokenIssue", "context", "grant", "grantType", "hook", "verdict", "grantSummary", "EndpointRefusal", "judge", "__name", "exchangeCode", "context", "form", "clientId", "revoke", "options", "code", "single", "verifier", "redirectUri", "missing", "name", "EndpointRefusal", "requireOwnResource", "unusable", "__name", "detail", "isOpaqueToken", "codeHash", "sha256Hex", "stored", "issued", "resource", "differs", "sameResource", "pkceMatches", "mismatch", "replayed", "hasExpired", "grant", "liveGrant", "requireTokenIssue", "redeemed", "tokens", "issueTokenPair", "exchangeRefreshToken", "context", "form", "clientId", "revoke", "options", "presented", "single", "EndpointRefusal", "requireOwnResource", "unusable", "__name", "isOpaqueToken", "hash", "sha256Hex", "now", "known", "hasExpired", "scopes", "narrowed", "asked", "s", "current", "liveGrant", "requireTokenIssue", "marked", "earlier", "withinRetryWindow", "grant", "tokens", "issueTokenPair", "TOKEN_PARAMETERS", "handleToken", "context", "request", "revoke", "clientId", "oauthError", "parsed", "readPublicClientForm", "EndpointRefusal", "byUrl", "isMetadataDocumentClientId", "client", "resolveClient", "grantType", "single", "tokens", "exchangeCode", "exchangeRefreshToken", "json", "error", "__name", "authorizationServerMetadata", "options", "at", "__name", "path", "protectedResourceMetadata", "guarded", "context", "where", "run", "fail", "error", "__name", "buildRoutes", "revoke", "options", "serverError", "oauthError", "pageError", "request", "routes", "add", "path", "entry", "document", "body", "json", "protectedResourceMetadata", "authorizationServerMetadata", "handleRegister", "handleAuthorize", "handleCallback", "handleConsent", "handleToken", "handleRevoke", "GrantInactiveError", "__name", "UpstreamTokenError", "__name", "message", "options", "UpstreamTokenCache", "__name", "#context", "#cache", "#inflight", "#revoking", "context", "grantId", "UPSTREAM_CACHE_GRANTS", "oldest", "forceRefresh", "running", "now", "cached", "hasExpired", "UPSTREAM_EXPIRY_SKEW_MS", "#start", "#load", "after", "#renew", "forced", "work", "promise", "options", "grant", "GrantInactiveError", "#refresh", "#remember", "token", "stored", "#fromGrant", "UpstreamTokenError", "refreshed", "refreshToken", "error", "expiresIn", "UPSTREAM_LIFETIME_CEILING_SECONDS", "expiresAt", "createMcpAuth", "options", "resolved", "resolveOptions", "defaultConsentPage", "defaultErrorPage", "context", "createContext", "upstream", "UpstreamTokenCache", "revoke", "__name", "grantId", "reason", "revokeGrantById", "id", "routes", "buildRoutes", "request", "entry", "preflight", "methodNotAllowed", "authenticateRequest", "forceRefresh", "_request", "error", "bearerChallenge", "data", "copy", "jsonCopy", "subject", "store", "McpAuthConfigError", "grants", "ended", "grant", "createCipheriv", "createDecipheriv", "randomBytes", "SealerError", "__name", "message", "IV_BYTES", "TAG_BYTES", "KID_PATTERN", "FORMAT", "attempt", "work", "error", "SealerError", "__name", "additionalData", "kid", "aad", "createAesGcmSealer", "options", "keys", "key", "current", "currentKid", "plaintext", "iv", "randomBytes", "cipher", "createCipheriv", "body", "sealed", "parts", "ivText", "bodyText", "tagText", "tag", "decipher", "createDecipheriv", "IdentityProviderError", "__name", "message", "code", "options", "createRemoteJWKSet", "customFetch", "jwtVerify", "DEFAULT_ALGORITHMS", "RESERVED_PARAMS", "optionalString", "value", "__name", "isDiscovery", "isRecord", "isStringArray", "isTokenResponse", "checkEndpoint", "name", "insecure", "url", "IdentityProviderError", "local", "LOOPBACK_HOSTS", "createOidcProvider", "options", "insecure", "timeoutMs", "doFetch", "algorithms", "DEFAULT_ALGORITHMS", "alg", "key", "RESERVED_PARAMS", "checkEndpoint", "error", "timedFetch", "__name", "url", "init", "discovery", "discover", "running", "base", "trimTrailing", "response", "IdentityProviderError", "document", "isDiscovery", "jwksUrl", "jwks", "createRemoteJWKSet", "customFetch", "clientAuth", "body", "methods", "user", "pass", "postToken", "headers", "parsed", "code", "isRecord", "isTokenResponse", "verifyIdToken", "idToken", "nonce", "payload", "jwtVerify", "state", "codeChallenge", "redirectUri", "value", "codeVerifier", "issuer", "tokens", "claims", "subject", "displayName", "refreshToken", "ClientCollection", "__name", "#held", "#idle", "#grantCounts", "#max", "#idleMs", "#now", "max", "idleMs", "now", "#toIdle", "client", "oldest", "clientId", "held", "idle", "hasExpired", "count", "Collection", "__name", "#items", "#max", "#expiresAt", "#now", "#onRemove", "max", "now", "expiresAt", "onRemove", "#remove", "key", "value", "hasExpired", "previous", "oldest", "includeExpired", "test", "expiry", "__name", "value", "settle", "work", "error", "createMemoryStore", "options", "max", "DEFAULT_MEMORY_STORE_ENTRIES", "now", "checkedClock", "clients", "ClientCollection", "DEFAULT_DYNAMIC_CLIENT_IDLE_SECONDS", "metadata", "Collection", "pending", "consents", "outcomes", "codes", "grants", "grant", "access", "refresh", "clientId", "client", "url", "expiresAt", "record", "id", "key", "sealedLocation", "subject", "g", "codeHash", "entry", "result", "grantId", "update", "removed", "token", "code", "tokenHash", "usedAt", "marked"]
3
+ "sources": ["../src/errors/mcp-auth-config-error.ts", "../src/helpers/json.ts", "../src/constants/defaults.ts", "../src/constants/protocol.ts", "../src/helpers/responses.ts", "../src/helpers/tokens.ts", "../src/helpers/freeze.ts", "../src/core/grant-context.ts", "../src/helpers/guards.ts", "../src/core/grant-summary.ts", "../src/helpers/time.ts", "../src/core/grants.ts", "../src/core/authenticate.ts", "../src/constants/error-pages.ts", "../src/helpers/client-ip.ts", "../src/helpers/locales.ts", "../src/core/policy.ts", "../src/core/context.ts", "../src/helpers/text.ts", "../src/helpers/html-document.ts", "../src/core/pages.ts", "../src/helpers/client-fields.ts", "../src/helpers/redirect-uris.ts", "../src/core/resolve-clients.ts", "../src/core/resolve-parts.ts", "../src/core/resolve-options.ts", "../src/core/client-metadata.ts", "../src/errors/client-metadata-error.ts", "../src/core/clients.ts", "../src/helpers/cookies.ts", "../src/helpers/params.ts", "../src/helpers/resource.ts", "../src/helpers/scopes.ts", "../src/handlers/authorize.ts", "../src/handlers/callback.ts", "../src/core/consent-page.ts", "../src/core/subject.ts", "../src/core/grant-expiry.ts", "../src/errors/body-too-large-error.ts", "../src/helpers/body.ts", "../src/handlers/consent.ts", "../src/handlers/register.ts", "../src/errors/endpoint-refusal.ts", "../src/core/client-form.ts", "../src/handlers/revoke.ts", "../src/core/token-policy.ts", "../src/core/code-exchange.ts", "../src/core/refresh-exchange.ts", "../src/handlers/token.ts", "../src/core/metadata.ts", "../src/core/router.ts", "../src/errors/grant-inactive-error.ts", "../src/errors/upstream-token-error.ts", "../src/core/upstream-tokens.ts", "../src/core/server.ts", "../src/crypto/aes-gcm-sealer.ts", "../src/errors/sealer-error.ts", "../src/errors/identity-provider-error.ts", "../src/providers/oidc-provider.ts", "../src/constants/oidc.ts", "../src/providers/oidc-documents.ts", "../src/stores/client-collection.ts", "../src/stores/collection.ts", "../src/stores/memory-store.ts"],
4
+ "sourcesContent": ["/**\n * Thrown by `createMcpAuth` when its options are invalid.\n *\n * @example\n * ```ts\n * import { createMcpAuth, McpAuthConfigError, type McpAuthOptions } from \"@gydr-ai/mcp-oauth\"\n *\n * export function tryCreate(options: McpAuthOptions) {\n * try {\n * return createMcpAuth(options)\n * } catch (error) {\n * if (error instanceof McpAuthConfigError) console.error(error.message)\n * throw error\n * }\n * }\n * ```\n */\nexport class McpAuthConfigError extends Error {\n /**\n * @param message - What is wrong with the configuration.\n */\n constructor(message: string) {\n super(message)\n this.name = \"McpAuthConfigError\"\n }\n}\n", "/**\n * Serialise a hook's result to JSON, check its size, and return a fresh\n * deep copy, so the server never keeps a live object the hook still holds.\n *\n * @param value - Whatever the hook returned.\n * @param limit - Largest serialised size in UTF-8 bytes.\n * @param label - Name of the hook, for the error message.\n * @returns A JSON-safe copy, or `undefined` when `value` is `undefined`.\n * @throws {TypeError} When the value is not JSON-serializable or is larger\n * than `limit` bytes.\n * @internal\n */\nexport function jsonCopy(\n value: unknown,\n limit: number,\n label: string,\n): unknown {\n if (value === undefined) return undefined\n\n // `unknown`: JSON.stringify returns undefined for a function or symbol.\n let text: unknown\n\n try {\n text = JSON.stringify(value)\n } catch {\n throw new TypeError(`${label} returned data that is not JSON-serializable`)\n }\n\n if (typeof text !== \"string\") {\n throw new TypeError(`${label} returned data that is not JSON-serializable`)\n }\n\n if (Buffer.byteLength(text, \"utf8\") > limit) {\n throw new TypeError(`${label} returned more than ${limit} bytes`)\n }\n\n const copy: unknown = JSON.parse(text)\n\n return copy\n}\n", "/**\n * Default lifetimes in seconds, applied when `lifetimes` omits a value.\n *\n * @internal\n */\nexport const DEFAULT_LIFETIMES = {\n accessToken: 1800,\n refreshToken: 1_209_600,\n grant: 7_776_000,\n grantIdle: 0,\n authorizationCode: 60,\n pendingAuthorization: 900,\n clientMetadataCache: 1800,\n clientMetadataFailure: 60,\n} as const\n\n/**\n * Smallest and largest configurable lifetimes, in seconds. The caps keep a\n * typo from creating codes or sign-in windows that live for days, or tokens\n * that never expire in practice.\n *\n * @internal\n */\nexport const LIFETIME_BOUNDS = {\n accessToken: { min: 1, max: 86_400 },\n refreshToken: { min: 1, max: 34_560_000 },\n grant: { min: 1, max: 34_560_000 },\n grantIdle: { min: 0, max: 34_560_000 },\n authorizationCode: { min: 1, max: 600 },\n pendingAuthorization: { min: 1, max: 3600 },\n clientMetadataCache: { min: 0, max: 86_400 },\n clientMetadataFailure: { min: 0, max: 3600 },\n} as const\n\n/**\n * Default size and count limits, applied when `limits` omits a value.\n *\n * @internal\n */\nexport const DEFAULT_LIMITS = {\n clientNameLength: 128,\n redirectUris: 8,\n redirectUriLength: 2048,\n stateLength: 2048,\n scopeLength: 1024,\n registrationBytes: 16 * 1024,\n formBytes: 32 * 1024,\n clientMetadataBytes: 5 * 1024,\n clientMetadataTimeoutMs: 4000,\n clientMetadataFailures: 10_000,\n grantContextBytes: 4096,\n consentDetailsBytes: 8192,\n grantDataBytes: 4096,\n} as const\n\n/**\n * Scopes offered when `scopes` is not set.\n *\n * @internal\n */\nexport const DEFAULT_SCOPES: readonly string[] = [\"mcp\"]\n\n/**\n * Retry window for a just-used refresh token, in seconds.\n *\n * @internal\n */\nexport const DEFAULT_REFRESH_REUSE_GRACE_SECONDS = 10\n\n/**\n * Longest refresh retry window that may be configured, in seconds.\n *\n * @internal\n */\nexport const MAX_REFRESH_REUSE_GRACE_SECONDS = 300\n\n/**\n * Default name prefix of the browser-binding cookie.\n *\n * @internal\n */\nexport const DEFAULT_COOKIE_PREFIX = \"__Host-mcp-auth-\"\n\n/**\n * Default capacity of each memory-store collection.\n *\n * @internal\n */\nexport const DEFAULT_MEMORY_STORE_ENTRIES = 100_000\n\n/**\n * Default idle lifetime of a grant-less dynamic client in the memory store, in seconds (28 days).\n *\n * @internal\n */\nexport const DEFAULT_DYNAMIC_CLIENT_IDLE_SECONDS = 2_419_200\n\n/**\n * Upstream access tokens are renewed this long before they expire.\n *\n * @internal\n */\nexport const UPSTREAM_EXPIRY_SKEW_MS = 60_000\n\n/**\n * Most grants whose upstream access token is cached in memory.\n *\n * @internal\n */\nexport const UPSTREAM_CACHE_GRANTS = 10_000\n\n/**\n * Upstream access tokens are treated as expiring within a day at most.\n *\n * @internal\n */\nexport const UPSTREAM_LIFETIME_CEILING_SECONDS = 86_400\n\n/**\n * `client_id` values longer than this are never looked up.\n *\n * @internal\n */\nexport const CLIENT_ID_LENGTH_LIMIT = 2048\n\n/**\n * Cache lifetime of metadata responses, in seconds.\n *\n * @internal\n */\nexport const METADATA_MAX_AGE_SECONDS = 600\n\n/**\n * Default seconds during which a repeated consent submission gets the same\n * answer.\n *\n * @internal\n */\nexport const DEFAULT_CONSENT_REPLAY_SECONDS = 10\n\n/**\n * Longest configurable consent replay window, in seconds.\n *\n * @internal\n */\nexport const MAX_CONSENT_REPLAY_SECONDS = 60\n", "/**\n * Well-known path of RFC 9728 protected resource metadata.\n *\n * @internal\n */\nexport const PROTECTED_RESOURCE_METADATA_PATH =\n \"/.well-known/oauth-protected-resource\"\n\n/**\n * Well-known path of RFC 8414 authorization server metadata.\n *\n * @internal\n */\nexport const AUTHORIZATION_SERVER_METADATA_PATH =\n \"/.well-known/oauth-authorization-server\"\n\n/**\n * Hostnames treated as the local machine (RFC 8252 \u00A7 7.3).\n *\n * @internal\n */\nexport const LOOPBACK_HOSTS: ReadonlySet<string> = new Set([\n \"127.0.0.1\",\n \"[::1]\",\n \"localhost\",\n])\n\n/**\n * Names of the fields the consent form posts back.\n *\n * @internal\n */\nexport const CONSENT_FIELDS = {\n id: \"interaction\",\n csrf: \"form_token\",\n decision: \"choice\",\n} as const\n\n/**\n * The value of the decision field that grants access.\n *\n * @internal\n */\nexport const CONSENT_APPROVE = \"approve\"\n\n/**\n * Media type of form bodies.\n *\n * @internal\n */\nexport const FORM_MEDIA_TYPE = \"application/x-www-form-urlencoded\"\n\n/**\n * Media type of JSON bodies.\n *\n * @internal\n */\nexport const JSON_MEDIA_TYPE = \"application/json\"\n", "import { METADATA_MAX_AGE_SECONDS } from \"../constants/defaults.js\"\nimport { JSON_MEDIA_TYPE } from \"../constants/protocol.js\"\n\n/** Caching headers every OAuth response carries (RFC 6749 \u00A7 5.1). */\nconst NO_STORE: Readonly<Record<string, string>> = {\n \"Cache-Control\": \"no-store\",\n Pragma: \"no-cache\",\n}\n\n/**\n * `Strict-Transport-Security` value: two years, subdomains included\n * (RFC 6797). No `preload`: that is a decision for the domain owner.\n */\nexport const STRICT_TRANSPORT_SECURITY = \"max-age=63072000; includeSubDomains\"\n\n/** Open CORS for the cookie-free endpoints that browser-based clients call. */\nconst CORS: Readonly<Record<string, string>> = {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Expose-Headers\": \"WWW-Authenticate\",\n}\n\n/** Options for {@link json}. */\ninterface JsonOptions {\n /** Add open CORS headers. Default: `false`. */\n readonly cors?: boolean\n /** Allow shared caches to keep the response briefly. Default: `false`. */\n readonly cacheable?: boolean\n /** Extra headers. */\n readonly headers?: Readonly<Record<string, string>>\n}\n\n/**\n * Build a JSON response, not cacheable unless asked.\n *\n * @param status - HTTP status.\n * @param body - Value to serialize.\n * @param options - CORS, caching and extra headers.\n * @returns The response with `Content-Type: application/json`.\n * @internal\n */\nexport function json(\n status: number,\n body: unknown,\n options: JsonOptions = {},\n): Response {\n const headers = new Headers({\n \"Content-Type\": JSON_MEDIA_TYPE,\n ...(options.cacheable\n ? { \"Cache-Control\": `public, max-age=${METADATA_MAX_AGE_SECONDS}` }\n : NO_STORE),\n ...(options.cors ? CORS : {}),\n ...options.headers,\n })\n\n return new Response(JSON.stringify(body), { status, headers })\n}\n\n/**\n * Build an OAuth error response (RFC 6749 \u00A7 5.2).\n *\n * @param status - HTTP status.\n * @param error - OAuth error code.\n * @param description - Optional `error_description`.\n * @param challenge - Optional `WWW-Authenticate` value.\n * @returns A JSON response with open CORS and `Cache-Control: no-store`.\n * @internal\n */\nexport function oauthError(\n status: number,\n error: string,\n description?: string,\n challenge?: string,\n): Response {\n return json(\n status,\n description === undefined\n ? { error }\n : { error, error_description: description },\n {\n cors: true,\n ...(challenge !== undefined\n ? { headers: { \"WWW-Authenticate\": challenge } }\n : {}),\n },\n )\n}\n\n/**\n * Answer a CORS preflight for a browser-callable endpoint.\n *\n * @param methods - Comma-separated allowed methods.\n * @returns A 204 response.\n * @internal\n */\nexport function preflight(methods: string): Response {\n return new Response(null, {\n status: 204,\n headers: {\n ...CORS,\n \"Access-Control-Allow-Methods\": methods,\n \"Access-Control-Allow-Headers\":\n \"Authorization, Content-Type, MCP-Protocol-Version\",\n \"Access-Control-Max-Age\": \"600\",\n },\n })\n}\n\n/**\n * Answer 405 with the methods the route does accept.\n *\n * @param allow - Comma-separated allowed methods.\n * @returns A 405 response.\n * @internal\n */\nexport function methodNotAllowed(allow: string): Response {\n return new Response(null, {\n status: 405,\n headers: { Allow: allow, ...NO_STORE },\n })\n}\n\n/**\n * Security headers for every HTML page: a strict Content-Security-Policy\n * (inline styles only, no scripts, forms only to this server and the listed\n * origins, no framing), plus no caching and no referrer.\n *\n * @param formActionOrigins - Extra origins a form may submit or redirect to.\n * @param scriptHashes - Inline script hashes (`sha256-\u2026`) the page may run.\n * @returns Header names and values for the page response.\n * @internal\n */\nexport function pageHeaders(\n formActionOrigins: readonly string[] = [],\n scriptHashes: readonly string[] = [],\n): Record<string, string> {\n const formAction = [\"'self'\", ...formActionOrigins].join(\" \")\n const scripts =\n scriptHashes.length === 0\n ? \"\"\n : ` script-src ${scriptHashes.map((h) => `'${h}'`).join(\" \")};`\n\n return {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"Content-Security-Policy\": `default-src 'none';${scripts} style-src 'unsafe-inline'; img-src data:; form-action ${formAction}; frame-ancestors 'none'; base-uri 'none'`,\n \"X-Frame-Options\": \"DENY\",\n \"X-Content-Type-Options\": \"nosniff\",\n \"Referrer-Policy\": \"no-referrer\",\n ...NO_STORE,\n }\n}\n\n/** Options for {@link html}. */\ninterface HtmlOptions {\n /** Extra origins the page's form may submit or redirect to. */\n readonly formActionOrigins?: readonly string[]\n /** `Set-Cookie` header values to add. */\n readonly setCookies?: readonly string[]\n /** Inline script hashes the page may run. */\n readonly scriptHashes?: readonly string[]\n}\n\n/**\n * Build an HTML page response with the page security headers.\n *\n * @param status - HTTP status.\n * @param body - The complete HTML document.\n * @param options - Form origins and cookies.\n * @returns The HTML response.\n * @internal\n */\nexport function html(\n status: number,\n body: string,\n options: HtmlOptions = {},\n): Response {\n const headers = new Headers(\n pageHeaders(options.formActionOrigins, options.scriptHashes),\n )\n\n for (const cookie of options.setCookies ?? []) {\n headers.append(\"Set-Cookie\", cookie)\n }\n\n return new Response(body, { status, headers })\n}\n\n/**\n * Build a redirect that is never cached and sends no referrer.\n *\n * @param location - Target URL.\n * @param setCookies - `Set-Cookie` header values to add.\n * @param status - 302, or 303 after a form POST so the browser follows with\n * GET (OAuth 2.1 \u00A7 7.5.4).\n * @returns A redirect response.\n * @internal\n */\nexport function redirect(\n location: string,\n setCookies: readonly string[] = [],\n status: 302 | 303 = 302,\n): Response {\n const headers = new Headers({\n Location: location,\n \"Referrer-Policy\": \"no-referrer\",\n ...NO_STORE,\n })\n\n for (const cookie of setCookies) headers.append(\"Set-Cookie\", cookie)\n\n return new Response(null, { status, headers })\n}\n\n/**\n * Add `Strict-Transport-Security` to a response this package built, so a\n * browser that reached the server over HTTPS once never falls back to plain\n * HTTP for it (RFC 6797). Browsers ignore the header on an HTTP response.\n *\n * @param response - A response built by this package (mutable headers).\n * @param enabled - `false` leaves the response unchanged.\n * @returns The same response.\n * @internal\n */\nexport function withTransportSecurity(\n response: Response,\n enabled: boolean,\n): Response {\n if (enabled) {\n response.headers.set(\"Strict-Transport-Security\", STRICT_TRANSPORT_SECURITY)\n }\n\n return response\n}\n", "import { createHash, randomBytes, timingSafeEqual } from \"node:crypto\"\n\n/** Entropy of every token, code, identifier and cookie value we mint. */\nconst SECRET_BYTES = 32\n\n/** Length of a base64url-encoded SHA-256 digest or of 32 random bytes. */\nconst ENCODED_32_BYTES = 43\n\n/**\n * A new unguessable value: 256 random bits, base64url without padding.\n *\n * @returns 43 base64url characters.\n * @internal\n */\nexport function randomToken(): string {\n return randomBytes(SECRET_BYTES).toString(\"base64url\")\n}\n\n/**\n * Hex SHA-256 of a UTF-8 string. Tokens and codes are stored only like this.\n *\n * @param value - Any UTF-8 string, typically an opaque token, code or cookie value.\n * @returns 64 lower-case hexadecimal characters.\n * @internal\n */\nexport function sha256Hex(value: string): string {\n return createHash(\"sha256\").update(value, \"utf8\").digest(\"hex\")\n}\n\n/**\n * The S256 code challenge for a verifier (RFC 7636 \u00A7 4.2).\n *\n * @param verifier - An RFC 7636 code verifier (ASCII).\n * @returns The base64url challenge.\n * @internal\n */\nexport function pkceChallenge(verifier: string): string {\n return createHash(\"sha256\").update(verifier, \"ascii\").digest(\"base64url\")\n}\n\n/**\n * Compare two strings without leaking where they differ.\n *\n * @param a - First value, typically a freshly computed hash.\n * @param b - Second value, typically the stored hash.\n * @returns `true` when equal.\n * @internal\n */\nexport function constantTimeEqual(a: string, b: string): boolean {\n const left = Buffer.from(a, \"utf8\")\n const right = Buffer.from(b, \"utf8\")\n\n return left.length === right.length && timingSafeEqual(left, right)\n}\n\nconst BASE64URL_EXTRA = \"-_\"\nconst VERIFIER_EXTRA = \"-._~\"\n\nfunction charsetCheck(extra: string) {\n return (text: string): boolean => {\n for (let i = 0; i < text.length; i += 1) {\n const c = text.charCodeAt(i)\n const alphanumeric =\n (c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122)\n\n if (!alphanumeric && !extra.includes(text.charAt(i))) return false\n }\n\n return true\n }\n}\n\nconst onlyBase64Url = charsetCheck(BASE64URL_EXTRA)\nconst onlyVerifierChars = charsetCheck(VERIFIER_EXTRA)\n\n/**\n * A code verifier as RFC 7636 \u00A7 4.1 defines it: 43 to 128 unreserved characters.\n *\n * @param verifier - The raw `code_verifier` form value.\n * @returns `true` when well formed.\n * @internal\n */\nexport function isWellFormedVerifier(verifier: string): boolean {\n return (\n verifier.length >= 43 &&\n verifier.length <= 128 &&\n onlyVerifierChars(verifier)\n )\n}\n\n/**\n * An S256 challenge: a SHA-256 digest in base64url, 43 characters.\n *\n * @param challenge - The raw `code_challenge` query value.\n * @returns `true` when well formed.\n * @internal\n */\nexport function isWellFormedChallenge(challenge: string): boolean {\n return challenge.length === ENCODED_32_BYTES && onlyBase64Url(challenge)\n}\n\n/**\n * Shaped like a token minted by {@link randomToken}.\n *\n * @param value - A bearer value, code or refresh token as sent by a client, unvalidated.\n * @returns `true` when well formed.\n * @internal\n */\nexport function isOpaqueToken(value: string): boolean {\n return value.length === ENCODED_32_BYTES && onlyBase64Url(value)\n}\n\n/**\n * Does `verifier` hash to `challenge`? Malformed verifiers never do.\n *\n * @param verifier - The raw `code_verifier` form value; malformed values never match.\n * @param challenge - The S256 challenge stored with the authorization code.\n * @returns `true` on a match.\n * @internal\n */\nexport function pkceMatches(verifier: string, challenge: string): boolean {\n return (\n isWellFormedVerifier(verifier) &&\n constantTimeEqual(pkceChallenge(verifier), challenge)\n )\n}\n", "/**\n * Freeze an object graph in place, recursively.\n *\n * @param value - A value produced by `structuredClone` (plain data only).\n * @returns The same value, now deeply frozen.\n */\nfunction deepFreeze<T>(value: T): T {\n if (typeof value === \"object\" && value !== null && !Object.isFrozen(value)) {\n Object.freeze(value)\n\n for (const key of Reflect.ownKeys(value)) {\n deepFreeze(Reflect.get(value, key))\n }\n }\n\n return value\n}\n\n/**\n * A deeply frozen copy of plain data, handed to integrator hooks so that a\n * hook can read validated state but can never change what the server goes\n * on to use.\n *\n * @param value - Plain, structured-cloneable data (no functions, no `Request`).\n * @returns An independent, deeply frozen copy.\n * @internal\n */\nexport function frozenCopy<T>(value: T): T {\n return deepFreeze(structuredClone(value))\n}\n\n/**\n * Deeply freeze data that the server owns and shares with hooks or callers,\n * such as pre-registered client records.\n *\n * @param value - Plain data to freeze in place.\n * @returns The same value, deeply frozen.\n * @internal\n */\nexport function freezeDeep<T>(value: T): T {\n return deepFreeze(value)\n}\n", "import { jsonCopy } from \"../helpers/json.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { GrantRecord } from \"../interfaces/records.js\"\n\n/**\n * Ask `getGrantContext` for the data to expose on `AuthResult.context`.\n *\n * The hook sees a copy of the grant's public fields and its result is\n * returned as a fresh JSON copy, so it can neither change the grant nor leak\n * live objects between requests. A result that cannot be serialized, or is\n * larger than `limits.grantContextBytes`, is an integration error.\n *\n * @param context - Context supplying the `getGrantContext` hook and `limits.grantContextBytes`.\n * @param grant - The grant behind the token.\n * @returns A JSON-safe copy of the hook's result, or `undefined` without a hook.\n * @throws {TypeError} When the result is not JSON-serializable or too large.\n * @internal\n */\nexport async function grantContextFor(\n context: HandlerContext,\n grant: GrantRecord,\n): Promise<unknown> {\n const hook = context.options.getGrantContext\n\n if (hook === undefined) return undefined\n\n const result = await hook(\n frozenCopy({\n grantId: grant.grantId,\n clientId: grant.clientId,\n subject: grant.subject,\n scopes: [...grant.scopes],\n resource: grant.resource,\n createdAt: grant.createdAt,\n ...(grant.data !== undefined ? { data: grant.data } : {}),\n }),\n )\n\n return jsonCopy(\n result,\n context.options.limits.grantContextBytes,\n \"getGrantContext\",\n )\n}\n", "/**\n * Whether a value is a plain object (not `null`, not an array).\n *\n * @param value - Any value.\n * @returns `true` for an object record.\n * @internal\n */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\n/**\n * Whether a value is an array of strings.\n *\n * @param value - Any value.\n * @returns `true` for a string array.\n * @internal\n */\nexport function isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === \"string\")\n}\n", "import type { GrantSummary } from \"../interfaces/consent.js\"\nimport type { GrantRecord } from \"../interfaces/records.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\n\n/**\n * A deeply frozen copy of a grant without its encrypted upstream tokens,\n * for hooks and events.\n *\n * @param grant - The stored grant.\n * @returns The summary.\n * @internal\n */\nexport function grantSummary(grant: GrantRecord): GrantSummary {\n // An allow-list, so a field added to GrantRecord later is never exposed\n // by accident.\n return frozenCopy({\n grantId: grant.grantId,\n clientId: grant.clientId,\n subject: grant.subject,\n scopes: grant.scopes,\n resource: grant.resource,\n createdAt: grant.createdAt,\n expiresAt: grant.expiresAt,\n ...(grant.clientName !== undefined ? { clientName: grant.clientName } : {}),\n ...(grant.displayName !== undefined\n ? { displayName: grant.displayName }\n : {}),\n ...(grant.allowRefresh !== undefined\n ? { allowRefresh: grant.allowRefresh }\n : {}),\n ...(grant.data !== undefined ? { data: grant.data } : {}),\n ...(grant.redirectUri !== undefined\n ? { redirectUri: grant.redirectUri }\n : {}),\n })\n}\n", "/**\n * Whether something with this expiry has expired at `now`. Written so that a\n * non-finite value on either side (`NaN`, `undefined` coerced to a number)\n * counts as expired: a broken clock or record must never keep a token alive.\n *\n * @param expiresAt - Expiry in milliseconds since the epoch, as stored.\n * @param now - Current time in milliseconds since the epoch.\n * @returns `true` unless `expiresAt` is a number strictly greater than `now`.\n * @internal\n */\nexport function hasExpired(expiresAt: number, now: number): boolean {\n return !(\n Number.isFinite(expiresAt) &&\n Number.isFinite(now) &&\n expiresAt > now\n )\n}\n\n/**\n * Wrap a clock so that it can only return finite numbers. Anything else\n * throws, which fails the current request closed instead of silently\n * disabling every expiry check.\n *\n * @param clock - Returns milliseconds since the epoch, such as `Date.now`.\n * @returns A clock with the same values, or throwing for a non-finite one.\n * @internal\n */\nexport function checkedClock(clock: () => number): () => number {\n return () => {\n const value: unknown = clock()\n\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n throw new TypeError(\"The configured clock returned a non-finite time\")\n }\n\n return value\n }\n}\n", "import { randomToken, sha256Hex } from \"../helpers/tokens.js\"\nimport { isRecord } from \"../helpers/guards.js\"\nimport { grantSummary } from \"./grant-summary.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { GrantRevokeReason } from \"../interfaces/events.js\"\nimport type { GrantRecord } from \"../interfaces/records.js\"\nimport type { TokenResponse } from \"../interfaces/token-response.js\"\nimport { hasExpired } from \"../helpers/time.js\"\n\n/**\n * Mint and store an access token (and, unless the client did not register\n * the refresh_token grant, a refresh token) for a grant. The refresh token's\n * window slides but never passes the grant's absolute expiry.\n *\n * @param context - Context supplying the store, clock and token lifetimes.\n * @param grant - The live grant the tokens are issued under; its `expiresAt` caps both token lifetimes and `allowRefresh` decides whether a refresh token is issued.\n * @param scopes - Scopes the access token carries; already checked to be within the grant's scopes.\n * @param refreshScopes - Scopes the refresh token carries. Default: `scopes`.\n * @returns The RFC 6749 \u00A7 5.1 response body; `refresh_token` is absent when the grant does not allow refresh.\n * @internal\n */\nexport async function issueTokenPair(\n context: HandlerContext,\n grant: GrantRecord,\n scopes: readonly string[],\n refreshScopes: readonly string[] = scopes,\n): Promise<TokenResponse> {\n const { options } = context\n const now = options.now()\n const accessToken = randomToken()\n const accessExpiresAt = Math.min(\n now + options.accessTokenTtlMs,\n grant.expiresAt,\n )\n\n await options.store.putAccessToken({\n tokenHash: sha256Hex(accessToken),\n grantId: grant.grantId,\n clientId: grant.clientId,\n scopes,\n resource: grant.resource,\n expiresAt: accessExpiresAt,\n })\n\n const response = {\n access_token: accessToken,\n token_type: \"Bearer\" as const,\n expires_in: Math.max(0, Math.floor((accessExpiresAt - now) / 1000)),\n scope: scopes.join(\" \"),\n }\n\n if (grant.allowRefresh === false) return response\n\n const refreshToken = randomToken()\n\n await options.store.putRefreshToken({\n tokenHash: sha256Hex(refreshToken),\n grantId: grant.grantId,\n clientId: grant.clientId,\n scopes: refreshScopes,\n resource: grant.resource,\n expiresAt: Math.min(now + options.refreshTokenTtlMs, grant.expiresAt),\n })\n\n return { ...response, refresh_token: refreshToken }\n}\n\n/**\n * The grant when it exists, is unexpired and belongs to `clientId`.\n *\n * @param context - Context supplying the store and clock.\n * @param grantId - Grant id taken from a stored token or code record.\n * @param clientId - Client the caller claims to be; the grant must belong to it.\n * @returns The grant when it exists, has not reached its absolute expiry and belongs to `clientId`; otherwise `undefined`.\n * @internal\n */\nexport async function liveGrant(\n context: HandlerContext,\n grantId: string,\n clientId: string,\n): Promise<GrantRecord | undefined> {\n const grant = await context.options.store.getGrant(grantId)\n\n if (\n grant === undefined ||\n grant.clientId !== clientId ||\n hasExpired(grant.expiresAt, context.options.now())\n ) {\n return undefined\n }\n\n return grant\n}\n\n/**\n * Revoke an upstream refresh token at the IdP. Best effort, never throws.\n *\n * @param context - Context supplying the identity provider whose optional `revoke` is called.\n * @param refreshToken - Plaintext upstream refresh token, as returned by the identity provider.\n * @internal\n */\nexport async function revokeUpstreamToken(\n context: HandlerContext,\n refreshToken: string,\n): Promise<void> {\n const provider = context.options.identityProvider\n\n if (provider.revoke === undefined) return\n\n try {\n await provider.revoke(refreshToken)\n } catch {\n // Best effort: nothing on our side holds the token any more.\n }\n}\n\n/**\n * Open a sealed upstream refresh token and revoke it. Best effort.\n *\n * @param context - Context supplying the sealer and identity provider.\n * @param sealed - Upstream refresh token encrypted with additional data `grantId`; nothing happens when `undefined`.\n * @param grantId - Grant id the value was encrypted for; used as the additional authenticated data.\n * @internal\n */\nexport async function revokeSealedUpstreamToken(\n context: HandlerContext,\n sealed: string | undefined,\n grantId: string,\n): Promise<void> {\n if (\n sealed === undefined ||\n context.options.identityProvider.revoke === undefined\n ) {\n return\n }\n\n let plaintext: string\n\n try {\n plaintext = await context.options.sealer.open(sealed, grantId)\n } catch {\n return\n }\n\n await revokeUpstreamToken(context, plaintext)\n}\n\n/**\n * End a grant. The `grant_revoked` event (with a copy of the grant, minus\n * its encrypted upstream tokens) is emitted first. Removing the record\n * invalidates all tokens tied to it.\n *\n * The upstream refresh token revoked at the identity provider is the last\n * one stored: the record the store returns from `deleteGrant`, or else the\n * one read just before deleting. `onRevoking` runs before both, so an\n * upstream renewal under way in this process sees the grant ending and\n * revokes the token it was about to store itself.\n *\n * @param context - Context supplying the store, sealer, identity provider and event hook.\n * @param grantId - Id of the grant to end; unknown ids are a silent no-op.\n * @param reason - Why the grant ends; reported in the `grant_revoked` event.\n * @param onRevoking - Called synchronously before the delete, so in-memory state for the grant can be dropped.\n * @internal\n */\nexport async function revokeGrantById(\n context: HandlerContext,\n grantId: string,\n reason: GrantRevokeReason,\n onRevoking: (grantId: string) => void,\n): Promise<void> {\n const { options } = context\n const grant = await options.store.getGrant(grantId)\n\n // Reported before the delete, so a listener still finds the grant (and\n // whatever it keeps about it) in the store.\n if (grant !== undefined) {\n await context.emit({\n type: \"grant_revoked\",\n grantId,\n reason,\n grant: grantSummary(grant),\n })\n }\n\n onRevoking(grantId)\n\n // Read again: a renewal may have stored a new upstream token meanwhile.\n const latest = await options.store.getGrant(grantId)\n const deleted: unknown = await options.store.deleteGrant(grantId)\n const sealed =\n isRecord(deleted) && deleted.grantId === grantId\n ? deleted.sealedUpstreamRefreshToken\n : (latest ?? grant)?.sealedUpstreamRefreshToken\n\n if (sealed !== undefined && typeof sealed !== \"string\") return\n\n await revokeSealedUpstreamToken(context, sealed, grantId)\n}\n", "import { isOpaqueToken, sha256Hex } from \"../helpers/tokens.js\"\nimport type { AuthResult, BearerError } from \"../interfaces/auth.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport { grantContextFor } from \"./grant-context.js\"\nimport { liveGrant } from \"./grants.js\"\nimport { hasExpired } from \"../helpers/time.js\"\n\n/**\n * Quote a value for an `auth-param` (RFC 9110 \u00A7 11.4), escaping quotes and\n * backslashes and folding line breaks.\n *\n * @param value - The raw value.\n * @returns The quoted string.\n */\nfunction quoted(value: string): string {\n return `\"${value.replace(/[\\\\\"]/g, \"\\\\$&\").replace(/[\\r\\n]/g, \" \")}\"`\n}\n\n/**\n * HTTP status for a bearer error (RFC 6750 \u00A7 3.1).\n *\n * @param error - The error code, if any.\n * @returns 400, 401 or 403.\n */\nfunction challengeStatus(error: BearerError[\"error\"]): number {\n switch (error) {\n case \"insufficient_scope\":\n return 403\n case \"invalid_request\":\n return 400\n case \"invalid_token\":\n case undefined:\n return 401\n }\n}\n\n/**\n * A 401 (or 403 for `insufficient_scope`) carrying\n * `WWW-Authenticate: Bearer resource_metadata=\"\u2026\"` (RFC 6750 and RFC 9728\n * \u00A7 5.1), so an MCP client can discover the authorization server.\n *\n * @param context - Context supplying the resource metadata URL and default scopes for the challenge.\n * @param error - What to report.\n * @returns A JSON response with status 400, 401 or 403 and the `WWW-Authenticate: Bearer` header.\n * @internal\n */\nexport function bearerChallenge(\n context: HandlerContext,\n error: BearerError = {},\n): Response {\n const { options } = context\n const params = [`resource_metadata=${quoted(options.resourceMetadataUrl)}`]\n const scope = error.scope ?? options.defaultScopes\n\n if (error.error !== undefined) params.push(`error=${quoted(error.error)}`)\n\n if (error.description !== undefined) {\n params.push(`error_description=${quoted(error.description)}`)\n }\n\n params.push(`scope=${quoted(scope.join(\" \"))}`)\n\n const status = challengeStatus(error.error)\n\n return new Response(\n JSON.stringify({\n error: error.error ?? \"unauthorized\",\n ...(error.description !== undefined\n ? { error_description: error.description }\n : {}),\n }),\n {\n status,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Cache-Control\": \"no-store\",\n \"WWW-Authenticate\": `Bearer ${params.join(\", \")}`,\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Expose-Headers\": \"WWW-Authenticate\",\n },\n },\n )\n}\n\n/**\n * A generic 500 for a request the server could not finish, with no detail.\n *\n * @returns A JSON `server_error` response that is never cached.\n */\nfunction serverFailure(): Response {\n return new Response(JSON.stringify({ error: \"server_error\" }), {\n status: 500,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Cache-Control\": \"no-store\",\n },\n })\n}\n\n/**\n * The bearer token of a request.\n *\n * @param request - The request.\n * @returns The token; `null` when no Bearer credentials were sent (no\n * `Authorization` header, or one with another scheme); `undefined` when a\n * Bearer credential is empty.\n */\nfunction bearerToken(request: Request): string | null | undefined {\n const header = request.headers.get(\"authorization\")\n\n if (header === null) return null\n\n const trimmed = header.trim()\n const space = trimmed.indexOf(\" \")\n const scheme = space === -1 ? trimmed : trimmed.slice(0, space)\n\n // RFC 6750 \u00A7 3.1: a request without Bearer credentials gets the bare\n // challenge, with no error code.\n if (scheme.toLowerCase() !== \"bearer\") return null\n\n const token = space === -1 ? \"\" : trimmed.slice(space + 1).trim()\n\n return token === \"\" ? undefined : token\n}\n\n/**\n * Check the bearer token on a request to the protected resource. It must be\n * a token this server issued, not expired, addressed to this resource\n * (RFC 8707 audience), and its grant must still exist for the same client.\n *\n * @param context - Context supplying the store, clock, configured resource and `getGrantContext` hook.\n * @param request - The request.\n * @param upstream - Produces an upstream access token for a grant id.\n * @returns The outcome. When `getGrantContext` throws, returns data that is\n * not JSON-serializable, or exceeds `limits.grantContextBytes`, the result\n * is a failure with a generic 500 response and a `grant_context_failed`\n * event; the hook's error never leaves this function.\n * @internal\n */\nexport async function authenticateRequest(\n context: HandlerContext,\n request: Request,\n upstream: (grantId: string, forceRefresh: boolean) => Promise<string>,\n): Promise<AuthResult> {\n const { options } = context\n const token = bearerToken(request)\n\n if (token === null) {\n return { ok: false, response: bearerChallenge(context) }\n }\n\n const invalid = (description: string): AuthResult => ({\n ok: false,\n response: bearerChallenge(context, { error: \"invalid_token\", description }),\n })\n\n if (token === undefined || !isOpaqueToken(token)) {\n return invalid(\"The bearer value is not in this server's token format\")\n }\n\n const row = await options.store.getAccessToken(sha256Hex(token))\n\n if (row === undefined || hasExpired(row.expiresAt, options.now())) {\n return invalid(\"The token is unknown here or past its expiry\")\n }\n\n if (row.resource !== options.resource) {\n return invalid(\"The token's audience is a different resource\")\n }\n\n const grant = await liveGrant(context, row.grantId, row.clientId)\n\n if (grant === undefined) {\n return invalid(\"Access behind this token has been withdrawn\")\n }\n\n let grantContext: unknown\n\n try {\n grantContext = await grantContextFor(context, grant)\n } catch {\n // The hook's message may contain anything; it is neither logged nor\n // returned, so a host error handler cannot leak it.\n await context.emit({ type: \"grant_context_failed\", grantId: grant.grantId })\n\n return { ok: false, response: serverFailure() }\n }\n\n return {\n ok: true,\n subject: grant.subject,\n grantId: grant.grantId,\n clientId: grant.clientId,\n scopes: row.scopes,\n context: grantContext,\n getUpstreamAccessToken: (options) =>\n upstream(grant.grantId, options?.forceRefresh === true),\n }\n}\n", "/**\n * Stable reason codes for error pages. A custom `renderErrorPage` can switch\n * on them to show its own wording or language.\n *\n * @example\n * ```ts\n * import type { ErrorPageReason } from \"@gydr-ai/mcp-oauth\"\n *\n * export function heading(reason: ErrorPageReason): string {\n * switch (reason) {\n * case \"access_denied\":\n * return \"Access not granted\"\n * case \"rate_limited\":\n * return \"Slow down\"\n * default:\n * return \"Something went wrong\"\n * }\n * }\n * ```\n */\nexport type ErrorPageReason =\n | \"rate_limited\"\n | \"repeated_parameter\"\n | \"client_unknown\"\n | \"redirect_uri_not_registered\"\n | \"invalid_request\"\n | \"unsupported_response_type\"\n | \"invalid_target\"\n | \"invalid_scope\"\n | \"consent_required\"\n | \"access_denied\"\n | \"flow_ended\"\n | \"malformed_response\"\n | \"browser_mismatch\"\n | \"sign_in_not_confirmed\"\n | \"subject_not_permitted\"\n | \"consent_unavailable\"\n | \"unsupported_submission\"\n | \"server_error\"\n\n/** Status, title and message of one built-in error page. */\ninterface ErrorPageText {\n /** HTTP status. */\n readonly status: number\n /** Heading. */\n readonly title: string\n /** Explanation for the person. */\n readonly message: string\n}\n\n/**\n * Built-in status, title and message for every error page reason.\n *\n * @internal\n */\nexport const ERROR_PAGES: Readonly<Record<ErrorPageReason, ErrorPageText>> = {\n rate_limited: {\n status: 429,\n title: \"Slow down\",\n message:\n \"An unusual number of requests came from your network just now. Please wait a little, then start again from your application.\",\n },\n repeated_parameter: {\n status: 400,\n title: \"Malformed link\",\n message:\n \"The link that opened this page names the same setting more than once, so it cannot be trusted.\",\n },\n client_unknown: {\n status: 400,\n title: \"Application not recognised\",\n message:\n \"The application that opened this page is not known to this server. Try connecting again from the application.\",\n },\n redirect_uri_not_registered: {\n status: 400,\n title: \"Unexpected return address\",\n message:\n \"The application asked for the result to go somewhere other than the return addresses it registered, so nothing was sent.\",\n },\n invalid_request: {\n status: 400,\n title: \"Incomplete request\",\n message:\n \"The application sent a request this server cannot accept. Please try connecting again from the application.\",\n },\n unsupported_response_type: {\n status: 400,\n title: \"Unsupported request\",\n message:\n \"The application asked for a kind of sign-in this server does not offer.\",\n },\n invalid_target: {\n status: 400,\n title: \"Wrong service\",\n message:\n \"The application asked for access to a service this server does not protect.\",\n },\n invalid_scope: {\n status: 400,\n title: \"Unavailable permissions\",\n message:\n \"The application asked for permissions that are not available to it.\",\n },\n consent_required: {\n status: 400,\n title: \"Approval needed\",\n message:\n \"This server always asks you before granting access, but the application asked to skip that step.\",\n },\n access_denied: {\n status: 403,\n title: \"Not permitted\",\n message: \"This application may not connect to this service.\",\n },\n flow_ended: {\n status: 400,\n title: \"This sign-in has ended\",\n message:\n \"The sign-in you returned from is no longer open, either because it took too long or because it was already completed. Please connect again from your application.\",\n },\n malformed_response: {\n status: 400,\n title: \"Malformed response\",\n message:\n \"The identity provider's response could not be used to finish signing in.\",\n },\n browser_mismatch: {\n status: 400,\n title: \"Different browser\",\n message:\n \"This sign-in has to finish in the browser where it began. Open your application on this device and connect again.\",\n },\n sign_in_not_confirmed: {\n status: 400,\n title: \"Sign-in not confirmed\",\n message:\n \"Your identity provider did not confirm who you are. Please connect again from your application.\",\n },\n subject_not_permitted: {\n status: 403,\n title: \"Access not permitted\",\n message: \"The account you signed in with may not connect this application.\",\n },\n consent_unavailable: {\n status: 400,\n title: \"Nothing to approve\",\n message:\n \"There is no open request to approve here. It may have been answered already, taken too long, or been opened in another browser. Please connect again from your application.\",\n },\n unsupported_submission: {\n status: 415,\n title: \"Unexpected submission\",\n message: \"Your answer must be sent with the form on the previous page.\",\n },\n server_error: {\n status: 500,\n title: \"Unexpected problem\",\n message:\n \"Something on this server stopped the request from finishing. Please start again from your application.\",\n },\n}\n", "import type { ResolvedOptions } from \"../interfaces/options.js\"\n\n/**\n * The client address used as the rate-limit key: `getClientIp` when given,\n * else the right-most `X-Forwarded-For` entry when `trustProxy` is set, else\n * `\"unknown\"`. The right-most entry is the one the nearest proxy added; the\n * others are supplied by the client and can be forged.\n *\n * @param options - Checked options.\n * @param request - The request.\n * @returns The key.\n * @internal\n */\nexport function clientIp(\n options: Pick<ResolvedOptions, \"getClientIp\" | \"trustProxy\">,\n request: Request,\n): string {\n const custom = options.getClientIp?.(request)\n\n if (custom !== undefined && custom !== \"\") return custom\n\n if (!options.trustProxy) return \"unknown\"\n\n const forwarded = request.headers.get(\"x-forwarded-for\")\n const hops = (forwarded ?? \"\")\n .split(\",\")\n .map((hop) => hop.trim())\n .filter((hop) => hop !== \"\")\n\n return hops[hops.length - 1] ?? \"unknown\"\n}\n", "/** Most language tags taken from one `Accept-Language` header. */\nconst LOCALE_LIMIT = 10\n\n/** A BCP 47-shaped language tag (letters, digits and hyphens). */\nconst TAG_PATTERN = /^[A-Za-z]{1,8}(?:-[A-Za-z0-9]{1,8})*$/\n\n/**\n * The languages a browser prefers, from its `Accept-Language` header, most\n * preferred first. Malformed entries, `*` and `q=0` entries are dropped.\n *\n * @param header - The raw `Accept-Language` header value, or `null`.\n * @returns Up to 10 language tags, such as `[\"de-CH\", \"de\", \"en\"]`.\n * @internal\n */\nexport function parseLocales(header: string | null): string[] {\n if (header === null) return []\n\n const ranked: { tag: string; q: number; order: number }[] = []\n\n for (const [order, part] of header.split(\",\").entries()) {\n const [rawTag = \"\", ...params] = part.trim().split(\";\")\n const tag = rawTag.trim()\n const qParam = params.map((p) => p.trim()).find((p) => p.startsWith(\"q=\"))\n const q = qParam === undefined ? 1 : Number(qParam.slice(2))\n\n if (TAG_PATTERN.test(tag) && Number.isFinite(q) && q > 0 && q <= 1) {\n ranked.push({ tag, q, order })\n }\n }\n\n ranked.sort((a, b) => b.q - a.q || a.order - b.order)\n\n return [...new Set(ranked.map((entry) => entry.tag))].slice(0, LOCALE_LIMIT)\n}\n", "import { isRecord } from \"../helpers/guards.js\"\nimport type { MaybePromise, PolicyDecision } from \"../interfaces/policy.js\"\n\n/** A refusal reason: short, machine-readable, safe to pass to a renderer. */\nconst REASON_PATTERN = /^[A-Za-z0-9_.:-]{1,64}$/\n\n/** The outcome of a policy hook, after validation. */\nexport interface PolicyOutcome {\n /** Whether the request may continue. */\n readonly allowed: boolean\n /** The hook's reason for a refusal, when it gave a valid one. */\n readonly reason?: string\n}\n\n/**\n * Run a policy hook that may only refuse.\n *\n * Only a literal `true`, or an object whose `allow` is the literal `true`,\n * lets the request continue. `false`, any other value, a rejection or a\n * throw refuses, so a broken hook can never open a door. A refusal may carry\n * a `reason` of 1\u201364 characters from `A-Z a-z 0-9 _ . : -`; any other reason\n * is dropped (the request is still refused).\n *\n * @param check - Calls the hook; may return a value or a promise.\n * @returns Whether the request may continue, and a valid refusal reason.\n * @internal\n */\nexport async function decide(\n check: () => MaybePromise<PolicyDecision>,\n): Promise<PolicyOutcome> {\n let verdict: unknown\n\n try {\n // Hooks are written in plain JavaScript too: trust no declared type.\n verdict = await check()\n } catch {\n return { allowed: false }\n }\n\n return judge(verdict)\n}\n\n/**\n * Read a policy verdict that has already been obtained (see {@link decide}).\n *\n * @param verdict - Whatever the hook returned.\n * @returns Whether the request may continue, and a valid refusal reason.\n * @internal\n */\nexport function judge(verdict: unknown): PolicyOutcome {\n if (verdict === true) return { allowed: true }\n\n if (!isRecord(verdict)) return { allowed: false }\n\n if (verdict.allow === true) return { allowed: true }\n\n const reason = verdict.reason\n\n return typeof reason === \"string\" && REASON_PATTERN.test(reason)\n ? { allowed: false, reason }\n : { allowed: false }\n}\n\n/**\n * Run a policy hook and report only whether the request may continue.\n *\n * @param check - Calls the hook; may return a value or a promise.\n * @returns `true` only for an allowing verdict (see {@link decide}).\n * @internal\n */\nexport async function permits(\n check: () => MaybePromise<PolicyDecision>,\n): Promise<boolean> {\n return (await decide(check)).allowed\n}\n", "import { ERROR_PAGES } from \"../constants/error-pages.js\"\nimport { clientIp } from \"../helpers/client-ip.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\nimport { parseLocales } from \"../helpers/locales.js\"\nimport { permits } from \"./policy.js\"\nimport { html } from \"../helpers/responses.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { ResolvedOptions } from \"../interfaces/options.js\"\n\n/**\n * Build the context shared by every handler of one `createMcpAuth` instance.\n *\n * @param options - Checked options.\n * @returns The handler context.\n * @internal\n */\nexport function createContext(options: ResolvedOptions): HandlerContext {\n const emit: HandlerContext[\"emit\"] = async (event) => {\n if (!options.onEvent) return\n\n try {\n await options.onEvent(event)\n } catch {\n // Logging trouble must not change an authorization outcome.\n }\n }\n\n return {\n options,\n cimd: { failures: new Map(), inflight: new Map() },\n emit,\n async allowed(request, scope) {\n if (!options.rateLimit) return true\n\n const limiter = options.rateLimit\n // Same rule as the policy hooks: only a literal `true` admits the\n // request; any other value, a rejection or a throw refuses it.\n const ok = await permits(() => limiter(clientIp(options, request), scope))\n\n if (!ok) await emit({ type: \"request_throttled\", scope })\n\n return ok\n },\n errorPage(request, reason, extra = {}) {\n const text = ERROR_PAGES[reason]\n const page = options.renderErrorPage(\n frozenCopy({\n status: text.status,\n reason,\n title: text.title,\n message: text.message,\n locales: parseLocales(request.headers.get(\"accept-language\")),\n ...(extra.detail !== undefined ? { detail: extra.detail } : {}),\n ...(extra.policyReason !== undefined\n ? { policyReason: extra.policyReason }\n : {}),\n }),\n )\n\n return html(text.status, page, { setCookies: extra.setCookies ?? [] })\n },\n }\n}\n", "/**\n * Characters a display name must never contain: controls (Cc, which covers\n * C0, DEL and C1), every format character (Cf: soft hyphen, zero-width\n * space/joiners, LRM/RLM, bidirectional embeddings, overrides and isolates,\n * word joiner and invisible operators, BOM, ...), line and paragraph\n * separators (Zl, Zp), private-use (Co), lone surrogates (Cs) and\n * unassigned code points (Cn).\n */\nconst DISALLOWED = /[\\p{Cc}\\p{Cf}\\p{Zl}\\p{Zp}\\p{Co}\\p{Cs}\\p{Cn}]/u\n\n/**\n * True when `value` has a character that is invisible or changes how the\n * surrounding text is displayed. Such characters let an application name\n * pass itself off as another one on the consent page.\n *\n * @param value - Self-declared text to show a person, such as a client name or scope description.\n * @returns `true` when such a character is present.\n * @internal\n */\nexport function hasHiddenCharacters(value: string): boolean {\n return DISALLOWED.test(value)\n}\n\nconst HTML_ESCAPES: Readonly<Record<string, string>> = {\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#39;\",\n}\n\n/**\n * Escape a value for HTML text AND for a double- or single-quoted attribute.\n * Every interpolated value on a page goes through this.\n *\n * @param value - Text to escape.\n * @returns The escaped text.\n * @example\n * ```ts\n * import { type ConsentPageContext, escapeHtml } from \"@gydr-ai/mcp-oauth\"\n *\n * export function renderConsentPage(context: ConsentPageContext): string {\n * const fields = Object.entries(context.hiddenFields)\n * .map(([name, value]) => `<input type=\"hidden\" name=\"${escapeHtml(name)}\" value=\"${escapeHtml(value)}\">`)\n * .join(\"\")\n *\n * return `<form method=\"post\" action=\"${escapeHtml(context.formAction)}\">${fields}\n * <button name=\"${escapeHtml(context.decisionField)}\" value=\"approve\">Allow</button>\n * <button name=\"${escapeHtml(context.decisionField)}\" value=\"deny\">Deny</button></form>`\n * }\n * ```\n */\nexport function escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (char) => HTML_ESCAPES[char] ?? char)\n}\n\n/**\n * True for an RFC 6749 \u00A7 3.3 scope-token: one or more of\n * %x21 / %x23-5B / %x5D-7E.\n *\n * @param value - One space-separated element of a `scope` value.\n * @returns `true` for a valid scope token.\n * @internal\n */\nexport function isScopeToken(value: string): boolean {\n if (value.length === 0) return false\n\n for (let i = 0; i < value.length; i += 1) {\n const code = value.charCodeAt(i)\n\n if (\n code !== 0x21 &&\n !(code >= 0x23 && code <= 0x5b) &&\n !(code >= 0x5d && code <= 0x7e)\n ) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Remove every trailing occurrence of one character, in linear time. Used in\n * place of an end-anchored repeat pattern such as `/\\/+$/`, whose regular\n * expression engine cost grows polynomially on long runs of the character.\n *\n * @param text - The string to trim, for example a URL path or host name.\n * @param char - The single character to strip from the end, such as `\"/\"`.\n * @returns `text` without any trailing `char`.\n */\nexport function trimTrailing(text: string, char: string): string {\n let end = text.length\n\n while (end > 0 && text[end - 1] === char) end -= 1\n\n return text.slice(0, end)\n}\n", "import { escapeHtml } from \"./text.js\"\n\n/**\n * Wrap page content in a complete, self-contained HTML document with inline\n * styles only, so it works under the pages' Content-Security-Policy.\n *\n * @param title - Page title; escaped here.\n * @param body - Body HTML; the caller escapes its values.\n * @returns The HTML document.\n * @internal\n */\nexport function document(title: string, body: string): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width\">\n<title>${escapeHtml(title)}</title>\n<style>\nhtml{font:16px/1.55 ui-sans-serif,system-ui,sans-serif;background:Canvas;color:CanvasText}\nbody{margin:0 auto;max-width:34rem;padding:2rem 1rem}\nh1{font-size:1.3rem;font-weight:600}\n.facts{padding:.75rem 1rem;border-left:3px solid GrayText;margin:1.25rem 0}\n.facts span{display:block;font-size:.8rem;text-transform:uppercase;letter-spacing:.04em;opacity:.7}\n.facts p{margin:.6rem 0;overflow-wrap:anywhere}\n.note{padding:.75rem 1rem;border:1px dashed #b45309}\n.choices{display:flex;flex-wrap:wrap;gap:.75rem;margin-top:1.5rem}\nbutton{font:inherit;padding:.55rem 1.4rem;border:1px solid GrayText;background:transparent;color:inherit;cursor:pointer}\nbutton.go{background:#0f766e;border-color:#0f766e;color:#fff}\n</style>\n</head>\n<body>\n${body}\n</body>\n</html>`\n}\n", "import { document } from \"../helpers/html-document.js\"\nimport { escapeHtml } from \"../helpers/text.js\"\nimport type {\n ConsentPageContext,\n ErrorPageContext,\n} from \"../interfaces/pages.js\"\n\n/**\n * The sentence telling the person who vouches for the application.\n *\n * @param context - Consent page data.\n * @returns HTML for the sentence.\n */\nfunction provenance(context: ConsentPageContext): string {\n const unchecked = `<p class=\"note\"><strong>Self-registered application.</strong> Nobody has checked the name above. Go on only if you just asked this application to connect.</p>`\n\n switch (context.clientKind) {\n case \"static\":\n return \"<p>The operator of this service added this application.</p>\"\n case \"metadata_document\":\n return context.clientHost === undefined || context.clientHost === \"\"\n ? unchecked\n : `<p>The description of this application is published at <strong>${escapeHtml(context.clientHost)}</strong>. The name above is the application's own claim.</p>`\n case \"dynamic\":\n return unchecked\n }\n}\n\n/**\n * The extra fields `readConsentFields` asked to correct, shown again with\n * their messages and the values the person sent, all escaped.\n *\n * @param context - Consent page data.\n * @returns HTML for the fields, or an empty string on a first showing.\n */\nfunction correctionFields(context: ConsentPageContext): string {\n const errors = context.fieldErrors ?? {}\n const submitted = context.submitted ?? {}\n const names = [\n ...new Set([...Object.keys(errors), ...Object.keys(submitted)]),\n ]\n\n if (names.length === 0) return \"\"\n\n const items = names.map((name, index) => {\n const id = `field-${index}`\n const message = Object.hasOwn(errors, name) ? errors[name] : undefined\n const value = Object.hasOwn(submitted, name) ? submitted[name] : undefined\n const note =\n message === undefined\n ? \"\"\n : `<span class=\"note\" id=\"${id}-error\">${escapeHtml(message)}</span>`\n const described =\n message === undefined\n ? \"\"\n : ` aria-invalid=\"true\" aria-describedby=\"${id}-error\"`\n\n return `<p><label for=\"${id}\">${escapeHtml(name)}</label>\n<input type=\"text\" id=\"${id}\" name=\"${escapeHtml(name)}\" value=\"${escapeHtml(value ?? \"\")}\"${described}>\n${note}</p>`\n })\n\n return `<section class=\"facts\" role=\"alert\">${items.join(\"\\n\")}</section>`\n}\n\n/**\n * The consent page used unless `renderConsentPage` is set.\n *\n * @param context - Consent page data.\n * @returns The HTML document.\n * @internal\n */\nexport function defaultConsentPage(context: ConsentPageContext): string {\n const title = context.clientName ?? \"An application without a name\"\n const delivery = context.redirectIsLoopback\n ? `a program running on this device (${escapeHtml(context.redirectHost)})`\n : escapeHtml(context.redirectHost)\n const account =\n context.subjectDisplayName === undefined\n ? \"\"\n : `<p>You are signed in as <strong>${escapeHtml(context.subjectDisplayName)}</strong>.</p>`\n const scopeItems = context.scopes\n .map((scope) => {\n // Own properties only: a scope named like a prototype member\n // (`constructor`, `toString`) has no description unless configured.\n const description = Object.hasOwn(context.scopeDescriptions, scope)\n ? context.scopeDescriptions[scope]\n : undefined\n const detail =\n description === undefined ? \"\" : ` \u2014 ${escapeHtml(description)}`\n\n return `<li><code>${escapeHtml(scope)}</code>${detail}</li>`\n })\n .join(\"\")\n const fields = Object.entries(context.hiddenFields)\n .map(\n ([name, value]) =>\n `<input type=\"hidden\" name=\"${escapeHtml(name)}\" value=\"${escapeHtml(value)}\">`,\n )\n .join(\"\\n\")\n const decision = escapeHtml(context.decisionField)\n const corrections = correctionFields(context)\n\n return document(\n \"Connect application\",\n `<h1>Connect <strong>${escapeHtml(title)}</strong>?</h1>\n${account}\n${provenance(context)}\n<section class=\"facts\">\n<p><span>Permissions</span></p><ul>${scopeItems}</ul>\n<p><span>Service</span> ${escapeHtml(context.resource)}</p>\n<p><span>Result goes to</span> ${delivery}</p>\n<p><span>Application id</span> <code>${escapeHtml(context.clientId)}</code></p>\n</section>\n<p>Connect only applications you recognise. The connection can be removed again at any time.</p>\n<form method=\"post\" action=\"${escapeHtml(context.formAction)}\">\n${fields}\n${corrections}\n<p class=\"choices\">\n<button type=\"submit\" name=\"${decision}\" value=\"approve\" class=\"go\">Connect</button>\n<button type=\"submit\" name=\"${decision}\" value=\"deny\">Cancel</button>\n</p>\n</form>`,\n )\n}\n\n/**\n * The error page used unless `renderErrorPage` is set.\n *\n * @param context - Error page data.\n * @returns The HTML document.\n * @internal\n */\nexport function defaultErrorPage(context: ErrorPageContext): string {\n return document(\n context.title,\n `<h1>${escapeHtml(context.title)}</h1>\n<p>${escapeHtml(context.message)}</p>${\n context.detail === undefined\n ? \"\"\n : `\\n<p class=\"note\">${escapeHtml(context.detail)}</p>`\n }`,\n )\n}\n", "import { CLIENT_ID_LENGTH_LIMIT } from \"../constants/defaults.js\"\nimport type { SupportedGrantType } from \"../interfaces/records.js\"\nimport { isStringArray } from \"./guards.js\"\nimport { hasHiddenCharacters } from \"./text.js\"\n\n/**\n * Does this `client_id` point at a Client ID Metadata Document? It must be an\n * `https` URL with a non-root path and no credentials or fragment, written in\n * exactly the form a URL parser would print it (so the identifier and the\n * address that is fetched can never differ: no dot segments, no case or\n * percent-encoding variants).\n *\n * @param clientId - The raw `client_id` parameter, unvalidated.\n * @returns `true` for a metadata-document client id.\n * @internal\n */\nexport function isMetadataDocumentClientId(clientId: string): boolean {\n if (\n clientId.length > CLIENT_ID_LENGTH_LIMIT ||\n !clientId.startsWith(\"https://\") ||\n clientId.includes(\"#\")\n ) {\n return false\n }\n\n let parsed: URL\n\n try {\n parsed = new URL(clientId)\n } catch {\n return false\n }\n\n return (\n parsed.hostname !== \"\" &&\n parsed.username === \"\" &&\n parsed.password === \"\" &&\n parsed.pathname.length > 1 &&\n parsed.href === clientId\n )\n}\n\n/**\n * A self-declared client name, trimmed, or `undefined` when it is empty,\n * longer than `maxLength`, or contains invisible or control characters.\n *\n * @param value - The raw `client_name` from registration metadata or a metadata document, of unknown type.\n * @param maxLength - Longest name accepted after trimming, in UTF-16 code units (`limits.clientNameLength`).\n * @returns The cleaned name, or `undefined`.\n * @internal\n */\nexport function cleanClientName(\n value: unknown,\n maxLength: number,\n): string | undefined {\n if (typeof value !== \"string\") return undefined\n\n const name = value.trim()\n\n if (name.length === 0 || name.length > maxLength) return undefined\n\n return hasHiddenCharacters(name) ? undefined : name\n}\n\n/**\n * Whether a grant type is one this server issues.\n *\n * @param value - A grant type name.\n * @returns `true` for `authorization_code` or `refresh_token`.\n */\nfunction isSupportedGrantType(value: string): value is SupportedGrantType {\n return value === \"authorization_code\" || value === \"refresh_token\"\n}\n\n/**\n * Interpret registered `grant_types`. Absent means `omitted` (by default\n * `authorization_code` alone, RFC 7591 \u00A7 2). The list must contain `authorization_code`; with `strict`,\n * any type this server cannot issue makes it invalid, otherwise such types\n * are dropped. Returns `undefined` when invalid.\n *\n * @param value - The raw `grant_types` metadata value, of unknown type; `undefined` means omitted.\n * @param strict - `true` for registration (any unknown grant type refuses), `false` for metadata documents (unknown types are dropped).\n * @param omitted - The grant types used when `value` is absent (`defaultGrantTypes`).\n * @returns The grant types, or `undefined` when invalid.\n * @internal\n */\nexport function parseGrantTypes(\n value: unknown,\n strict: boolean,\n omitted: readonly SupportedGrantType[] = [\"authorization_code\"],\n): SupportedGrantType[] | undefined {\n if (value === undefined) return [...omitted]\n\n if (!isStringArray(value) || !value.includes(\"authorization_code\")) {\n return undefined\n }\n\n const known = value.filter(isSupportedGrantType)\n\n if (strict && known.length !== value.length) return undefined\n\n return [...new Set(known)]\n}\n\n/**\n * Interpret a registered `scope` string against the supported scopes.\n * `null`: none registered. `undefined`: invalid (not a string, too long,\n * nothing supported, or \u2014 with `strict` \u2014 anything unsupported).\n *\n * @param value - The raw `scope` metadata value, of unknown type; `undefined` means omitted.\n * @param supported - The server's supported scopes.\n * @param strict - `true` for registration (an unsupported scope refuses), `false` for metadata documents (unsupported scopes are dropped).\n * @param maxLength - Longest `scope` string accepted (`limits.scopeLength`).\n * @returns The scopes, `null` when none were registered, or `undefined` when invalid.\n * @internal\n */\nexport function parseClientScopes(\n value: unknown,\n supported: readonly string[],\n strict: boolean,\n maxLength: number,\n): string[] | undefined | null {\n if (value === undefined) return null\n\n if (typeof value !== \"string\" || value.length > maxLength) return undefined\n\n const requested = [...new Set(value.split(\" \").filter((s) => s.length > 0))]\n const usable = requested.filter((s) => supported.includes(s))\n\n if (usable.length === 0) return undefined\n\n if (strict && usable.length < requested.length) return undefined\n\n return usable\n}\n", "/**\n * Which redirect URIs a client may use, and when a requested one counts as\n * registered. Authorization codes are delivered to these addresses, so the\n * rules are deliberately narrow:\n *\n * - allowed: `https` with a host, or plain `http` to a loopback host\n * (`127.0.0.1`, `[::1]`, `localhost`) for native apps (RFC 8252 \u00A7 7.3);\n * never a fragment (RFC 6749 \u00A7 3.1.2), never embedded credentials;\n * - comparison is character-for-character, except that a loopback URI may\n * name any port, because native apps bind an ephemeral one.\n */\n\nimport { LOOPBACK_HOSTS } from \"../constants/protocol.js\"\n\n/**\n * Default for {@link redirectUriProblem}'s length limit.\n *\n * @internal\n */\nexport const DEFAULT_REDIRECT_URI_LENGTH = 2048\n\n/**\n * Why a redirect URI is not allowed.\n *\n * @internal\n */\nexport type RedirectUriProblem =\n | \"unparseable\"\n | \"over_length\"\n | \"has_fragment\"\n | \"has_credentials\"\n | \"disallowed_scheme\"\n | \"reserved_query_parameter\"\n\n/**\n * Parameters the server adds to the redirect; a registered URI may not\n * carry them already, or the client could read a forged value (RFC 9700\n * \u00A7 4.1.1, OAuth 2.1 \u00A7 7.5.4.1, RFC 9207).\n */\nconst RESERVED_REDIRECT_PARAMETERS: readonly string[] = [\n \"code\",\n \"state\",\n \"iss\",\n \"error\",\n \"error_description\",\n \"error_uri\",\n]\n\nfunction tryParse(text: string): URL | undefined {\n try {\n return new URL(text)\n } catch {\n return undefined\n }\n}\n\nfunction isLoopbackHttp(url: URL): boolean {\n return url.protocol === \"http:\" && LOOPBACK_HOSTS.has(url.hostname)\n}\n\n/**\n * True for a plain-`http` loopback URI.\n *\n * @param uri - A redirect URI, possibly unparseable.\n * @returns `true` for a loopback URI.\n * @internal\n */\nexport function isLoopbackRedirectUri(uri: string): boolean {\n const url = tryParse(uri)\n\n return url !== undefined && isLoopbackHttp(url)\n}\n\n/**\n * The reason `uri` is unusable for redirection, or `undefined` when usable.\n *\n * @param uri - A redirect URI from registration, a metadata document or configuration, unvalidated.\n * @param limits - Length limit to apply. Default: 2048 characters.\n * @param limits.redirectUriLength - Longest URI accepted, in characters (`limits.redirectUriLength`).\n * @returns The problem, or `undefined` when the URI is usable.\n * @internal\n */\nexport function redirectUriProblem(\n uri: string,\n limits: { readonly redirectUriLength: number } = {\n redirectUriLength: DEFAULT_REDIRECT_URI_LENGTH,\n },\n): RedirectUriProblem | undefined {\n if (uri.length > limits.redirectUriLength) return \"over_length\"\n\n const url = tryParse(uri)\n\n if (url === undefined) return \"unparseable\"\n\n // Checked on the raw text: the parser forgets an empty fragment.\n if (uri.includes(\"#\")) return \"has_fragment\"\n\n if (url.username.length > 0 || url.password.length > 0) {\n return \"has_credentials\"\n }\n\n const secure = url.protocol === \"https:\" && url.hostname.length > 0\n\n if (!secure && !isLoopbackHttp(url)) return \"disallowed_scheme\"\n\n return RESERVED_REDIRECT_PARAMETERS.some((name) => url.searchParams.has(name))\n ? \"reserved_query_parameter\"\n : undefined\n}\n\n/**\n * True when `uri` is allowed as a redirect URI.\n *\n * @param uri - A redirect URI, unvalidated.\n * @param limits - Length limit to apply. Default: 2048 characters.\n * @param limits.redirectUriLength - Longest URI accepted, in characters.\n * @returns `true` when usable.\n * @internal\n */\nexport function isAcceptableRedirectUri(\n uri: string,\n limits?: { readonly redirectUriLength: number },\n): boolean {\n return redirectUriProblem(uri, limits) === undefined\n}\n\nfunction isDigit(code: number): boolean {\n return code >= 48 && code <= 57\n}\n\n/**\n * `uri` with the `:port` after a loopback host removed, host lower-cased.\n *\n * @param uri - The raw URI text, exactly as registered or requested.\n * @param url - The same URI parsed; only its scheme and hostname are used.\n * @returns The URI without its port.\n */\nfunction portless(uri: string, url: URL): string {\n const head = `${url.protocol}//${url.hostname}`\n\n if (uri.slice(0, head.length).toLowerCase() !== head.toLowerCase()) {\n return uri\n }\n\n let tail = uri.slice(head.length)\n\n if (tail.startsWith(\":\")) {\n let i = 1\n\n while (i < tail.length && isDigit(tail.charCodeAt(i))) i += 1\n tail = tail.slice(i)\n }\n\n return head.toLowerCase() + tail\n}\n\n/**\n * Is `requested` the same redirect URI as the registered one?\n *\n * @param registered - A redirect URI stored on the client, already validated.\n * @param requested - The raw `redirect_uri` query value, unvalidated.\n * @param limits - Length limit for the requested URI. Default: 2048 characters.\n * @param limits.redirectUriLength - Longest URI accepted, in characters.\n * @returns `true` on a match.\n * @internal\n */\nexport function redirectUriMatches(\n registered: string,\n requested: string,\n limits?: { readonly redirectUriLength: number },\n): boolean {\n if (!isAcceptableRedirectUri(requested, limits)) return false\n\n if (requested === registered) return true\n\n const a = tryParse(registered)\n const b = tryParse(requested)\n\n if (a === undefined || b === undefined) return false\n\n if (!isLoopbackHttp(a) || !isLoopbackHttp(b)) return false\n\n // Compare the text, not parsed fields: parsing normalizes paths, and a\n // normalized match is not an exact one.\n return portless(registered, a) === portless(requested, b)\n}\n\n/**\n * Does `requested` match one of the registered URIs?\n *\n * @param registered - Redirect URIs stored on the client.\n * @param requested - The raw `redirect_uri` query value, unvalidated.\n * @param limits - Length limit for the requested URI. Default: 2048 characters.\n * @param limits.redirectUriLength - Longest URI accepted, in characters.\n * @returns `true` on a match.\n * @internal\n */\nexport function matchesAnyRedirectUri(\n registered: readonly string[],\n requested: string,\n limits?: { readonly redirectUriLength: number },\n): boolean {\n return registered.some((uri) => redirectUriMatches(uri, requested, limits))\n}\n\n/**\n * Host to show a person: loopback without its port, otherwise `host`.\n *\n * @param uri - A redirect URI already matched against the client's registration.\n * @returns The host, or an empty string.\n * @internal\n */\nexport function redirectHostOf(uri: string): string {\n const url = tryParse(uri)\n\n if (url === undefined) return \"\"\n\n return isLoopbackHttp(url) ? url.hostname : url.host\n}\n\n/**\n * `scheme://host[:port]` of a URI, or `undefined`.\n *\n * @param uri - A redirect URI already matched against the client's registration.\n * @returns The origin, or `undefined`.\n * @internal\n */\nexport function originOf(uri: string): string | undefined {\n return tryParse(uri)?.origin\n}\n\n/**\n * Add response parameters to a redirect URI that has already been matched,\n * keeping any query it has. `undefined` values are left out.\n *\n * @param redirectUri - A redirect URI already matched against the client's registration; never pass an unchecked value.\n * @param params - Query parameters to append (`code`, `state`, `iss`, `error`\u2026); `undefined` values are skipped.\n * @returns The redirect URL.\n * @internal\n */\nexport function buildRedirect(\n redirectUri: string,\n params: Readonly<Record<string, string | undefined>>,\n): string {\n const target = new URL(redirectUri)\n\n for (const [name, value] of Object.entries(params)) {\n if (value !== undefined) target.searchParams.append(name, value)\n }\n\n return target.toString()\n}\n", "import { McpAuthConfigError } from \"../errors/mcp-auth-config-error.js\"\nimport { freezeDeep } from \"../helpers/freeze.js\"\nimport {\n cleanClientName,\n isMetadataDocumentClientId,\n parseGrantTypes,\n} from \"../helpers/client-fields.js\"\nimport { redirectUriProblem } from \"../helpers/redirect-uris.js\"\nimport { hasHiddenCharacters } from \"../helpers/text.js\"\nimport type { Limits } from \"../interfaces/options.js\"\nimport type { StaticClient } from \"../interfaces/policy.js\"\nimport type { ClientRecord } from \"../interfaces/records.js\"\n\n/**\n * Check scope descriptions: each key must be a supported scope and each text\n * a visible string of at most 200 characters.\n *\n * @param scopes - The supported scopes after defaults were applied; every description key must be one of them.\n * @param descriptions - Configured descriptions.\n * @returns The descriptions.\n * @throws {McpAuthConfigError} When a key or text is invalid.\n * @internal\n */\nexport function resolveScopeDescriptions(\n scopes: readonly string[],\n descriptions: Readonly<Record<string, string>> = {},\n): Readonly<Record<string, string>> {\n for (const [scope, text] of Object.entries(descriptions)) {\n if (!scopes.includes(scope)) {\n throw new McpAuthConfigError(\n `scopeDescriptions names ${scope}, which is not in scopes`,\n )\n }\n\n if (text.trim() === \"\" || text.length > 200 || hasHiddenCharacters(text)) {\n throw new McpAuthConfigError(\n `scopeDescriptions.${scope} must be 1 to 200 visible characters`,\n )\n }\n }\n\n // A null-prototype copy, so lookups never reach Object.prototype.\n const own: Record<string, string> = Object.fromEntries(\n Object.entries(descriptions),\n )\n\n Object.setPrototypeOf(own, null)\n\n return Object.freeze(own)\n}\n\n/**\n * Check the pre-registered clients with the same rules as registration.\n *\n * @param clients - Configured clients.\n * @param scopes - The supported scopes after defaults were applied; a client's scopes must be a subset.\n * @param limits - Checked limits.\n * @param now - Clock.\n * @returns The clients by id.\n * @throws {McpAuthConfigError} When a client is invalid or listed twice.\n * @internal\n */\nexport function resolveStaticClients(\n clients: readonly StaticClient[] = [],\n scopes: readonly string[],\n limits: Required<Limits>,\n now: () => number,\n): ReadonlyMap<string, ClientRecord> {\n const byId = new Map<string, ClientRecord>()\n\n for (const client of clients) {\n const id = client.clientId\n const label = `clients[${JSON.stringify(id)}]`\n\n if (id.trim() === \"\" || id.length > 255 || hasHiddenCharacters(id)) {\n throw new McpAuthConfigError(\n `${label}: clientId must be 1 to 255 visible characters`,\n )\n }\n\n if (isMetadataDocumentClientId(id) || /^https:/i.test(id)) {\n throw new McpAuthConfigError(\n `${label}: clientId must not be an https URL`,\n )\n }\n\n if (byId.has(id)) throw new McpAuthConfigError(`${label}: listed twice`)\n\n if (\n client.redirectUris.length === 0 ||\n client.redirectUris.length > limits.redirectUris\n ) {\n throw new McpAuthConfigError(\n `${label}: list 1 to ${limits.redirectUris} redirectUris`,\n )\n }\n\n for (const uri of client.redirectUris) {\n const problem = redirectUriProblem(uri, limits)\n\n if (problem !== undefined) {\n throw new McpAuthConfigError(\n `${label}: redirect URI refused (${problem})`,\n )\n }\n }\n\n const clientName =\n client.clientName === undefined\n ? undefined\n : cleanClientName(client.clientName, limits.clientNameLength)\n\n if (client.clientName !== undefined && clientName === undefined) {\n throw new McpAuthConfigError(\n `${label}: clientName is not a valid display name`,\n )\n }\n\n const grantTypes = parseGrantTypes(\n client.grantTypes ?? [\"authorization_code\", \"refresh_token\"],\n true,\n )\n\n if (grantTypes === undefined) {\n throw new McpAuthConfigError(\n `${label}: grantTypes must include authorization_code`,\n )\n }\n\n if (client.scopes?.some((scope) => !scopes.includes(scope)) === true) {\n throw new McpAuthConfigError(\n `${label}: scopes must be a subset of scopes`,\n )\n }\n\n if (client.scopes?.length === 0) {\n throw new McpAuthConfigError(`${label}: scopes must not be empty`)\n }\n\n // Frozen: the record is shared with every request and every hook.\n byId.set(\n id,\n freezeDeep({\n clientId: id,\n redirectUris: [...new Set(client.redirectUris)],\n kind: \"static\",\n createdAt: now(),\n grantTypes,\n ...(client.scopes !== undefined ? { scopes: [...client.scopes] } : {}),\n ...(clientName !== undefined ? { clientName } : {}),\n }),\n )\n }\n\n return byId\n}\n", "import { trimTrailing } from \"../helpers/text.js\"\nimport { DEFAULT_LIMITS } from \"../constants/defaults.js\"\nimport { LOOPBACK_HOSTS } from \"../constants/protocol.js\"\nimport { McpAuthConfigError } from \"../errors/mcp-auth-config-error.js\"\nimport type { Limits, RoutePaths } from \"../interfaces/options.js\"\n\n/**\n * Parse and check the issuer or resource URL.\n *\n * @param name - Option name, for error messages.\n * @param value - The configured URL.\n * @param allowInsecureLocalhost - Whether plain `http` on loopback is allowed.\n * @returns The parsed URL, known to be `https` (or loopback `http` when allowed) without query, fragment or credentials.\n * @throws {McpAuthConfigError} When the URL is not absolute, carries a query,\n * fragment or credentials, or is not `https`.\n * @internal\n */\nexport function parseServerUrl(\n name: string,\n value: string,\n allowInsecureLocalhost: boolean,\n): URL {\n let url: URL\n\n try {\n url = new URL(value)\n } catch {\n throw new McpAuthConfigError(`${name} must be an absolute URL`)\n }\n\n if (value.includes(\"#\") || url.search !== \"\" || value.includes(\"?\")) {\n throw new McpAuthConfigError(`${name} must have no query or fragment`)\n }\n\n if (url.username !== \"\" || url.password !== \"\") {\n throw new McpAuthConfigError(`${name} must have no userinfo`)\n }\n\n const local = url.protocol === \"http:\" && LOOPBACK_HOSTS.has(url.hostname)\n\n if (url.protocol !== \"https:\" && !(local && allowInsecureLocalhost)) {\n throw new McpAuthConfigError(\n `${name} must use https (http is allowed only for localhost with allowInsecureLocalhost)`,\n )\n }\n\n return url\n}\n\n/**\n * Remove trailing slashes: `/a/b/` becomes `/a/b`, `/` becomes the empty string.\n *\n * @param pathname - A URL path.\n * @returns The path without trailing slashes.\n * @internal\n */\nexport function trimPath(pathname: string): string {\n return trimTrailing(pathname, \"/\")\n}\n\n/**\n * Turn a lifetime in seconds into milliseconds, checking it.\n *\n * @param name - Lifetime name, for error messages.\n * @param value - Configured seconds, if any.\n * @param fallback - Default seconds.\n * @param min - Smallest allowed value, in seconds.\n * @param max - Largest allowed value, in seconds.\n * @returns The lifetime in milliseconds.\n * @throws {McpAuthConfigError} When the value is not an integer from `min` to `max`.\n * @internal\n */\nexport function seconds(\n name: string,\n value: number | undefined,\n fallback: number,\n min: number,\n max: number,\n): number {\n const chosen = value ?? fallback\n\n if (!Number.isInteger(chosen) || chosen < min || chosen > max) {\n throw new McpAuthConfigError(\n `lifetimes.${name} must be an integer from ${min} to ${max}`,\n )\n }\n\n return chosen * 1000\n}\n\n/**\n * Check one size or count limit.\n *\n * @param name - Limit name, for error messages.\n * @param value - Configured value, if any.\n * @param fallback - Default value.\n * @returns The limit.\n * @throws {McpAuthConfigError} When the value is not a positive integer.\n * @internal\n */\nexport function positive(\n name: string,\n value: number | undefined,\n fallback: number,\n): number {\n const chosen = value ?? fallback\n\n if (!Number.isInteger(chosen) || chosen < 1) {\n throw new McpAuthConfigError(`limits.${name} must be a positive integer`)\n }\n\n return chosen\n}\n\n/**\n * Apply defaults to every limit.\n *\n * @param limits - Configured limits.\n * @returns Every limit, checked.\n * @throws {McpAuthConfigError} When a limit is not a positive integer.\n * @internal\n */\nexport function resolveLimits(limits: Limits = {}): Required<Limits> {\n const pick = (key: keyof Limits): number =>\n positive(key, limits[key], DEFAULT_LIMITS[key])\n\n return {\n clientNameLength: pick(\"clientNameLength\"),\n redirectUris: pick(\"redirectUris\"),\n redirectUriLength: pick(\"redirectUriLength\"),\n stateLength: pick(\"stateLength\"),\n scopeLength: pick(\"scopeLength\"),\n registrationBytes: pick(\"registrationBytes\"),\n formBytes: pick(\"formBytes\"),\n clientMetadataBytes: pick(\"clientMetadataBytes\"),\n clientMetadataTimeoutMs: pick(\"clientMetadataTimeoutMs\"),\n clientMetadataFailures: pick(\"clientMetadataFailures\"),\n grantContextBytes: pick(\"grantContextBytes\"),\n consentDetailsBytes: pick(\"consentDetailsBytes\"),\n grantDataBytes: pick(\"grantDataBytes\"),\n }\n}\n\n/**\n * Check one endpoint path.\n *\n * @param name - Path name, for error messages.\n * @param path - The configured path.\n * @returns The path.\n * @throws {McpAuthConfigError} When it does not start with exactly one `/`,\n * or contains `//`, a backslash, `?`, `#`, a dot segment or whitespace.\n * @internal\n */\nexport function checkPath(name: string, path: string): string {\n // `//host/x` and `\\\\host\\x` are protocol-relative on many stacks; a path\n // must be unambiguous wherever it is echoed or compared.\n const segments = path.split(\"/\").slice(1)\n\n if (\n !path.startsWith(\"/\") ||\n path.includes(\"//\") ||\n /[\\\\?#\\s]/.test(path) ||\n segments.some((segment) => segment === \".\" || segment === \"..\")\n ) {\n throw new McpAuthConfigError(\n `paths.${name} must be one absolute path without //, \\\\, ?, #, dot segments or whitespace`,\n )\n }\n\n return path\n}\n\n/**\n * Work out every endpoint path, defaulting to paths under the issuer's path.\n *\n * @param issuerPath - The issuer's path without a trailing slash.\n * @param paths - Configured paths.\n * @returns Every path.\n * @throws {McpAuthConfigError} When a path is invalid or two paths are equal.\n * @internal\n */\nexport function resolvePaths(\n issuerPath: string,\n paths: RoutePaths = {},\n): Required<RoutePaths> {\n const pick = (key: keyof RoutePaths): string =>\n checkPath(key, paths[key] ?? `${issuerPath}/${key}`)\n const resolved: Required<RoutePaths> = {\n authorize: pick(\"authorize\"),\n callback: pick(\"callback\"),\n consent: pick(\"consent\"),\n token: pick(\"token\"),\n register: pick(\"register\"),\n revoke: pick(\"revoke\"),\n }\n const all = Object.values(resolved)\n\n if (new Set(all).size !== all.length) {\n throw new McpAuthConfigError(\"paths must be distinct\")\n }\n\n return resolved\n}\n", "import {\n DEFAULT_CONSENT_REPLAY_SECONDS,\n DEFAULT_COOKIE_PREFIX,\n DEFAULT_LIFETIMES,\n DEFAULT_REFRESH_REUSE_GRACE_SECONDS,\n DEFAULT_SCOPES,\n LIFETIME_BOUNDS,\n MAX_CONSENT_REPLAY_SECONDS,\n MAX_REFRESH_REUSE_GRACE_SECONDS,\n} from \"../constants/defaults.js\"\nimport {\n AUTHORIZATION_SERVER_METADATA_PATH,\n PROTECTED_RESOURCE_METADATA_PATH,\n} from \"../constants/protocol.js\"\nimport { McpAuthConfigError } from \"../errors/mcp-auth-config-error.js\"\nimport { isScopeToken } from \"../helpers/text.js\"\nimport type { McpAuthOptions, ResolvedOptions } from \"../interfaces/options.js\"\nimport {\n resolveScopeDescriptions,\n resolveStaticClients,\n} from \"./resolve-clients.js\"\nimport {\n parseServerUrl,\n resolveLimits,\n resolvePaths,\n seconds,\n trimPath,\n} from \"./resolve-parts.js\"\nimport type {\n ConsentPageContext,\n ErrorPageContext,\n} from \"../interfaces/pages.js\"\nimport { checkedClock } from \"../helpers/time.js\"\nimport { parseGrantTypes } from \"../helpers/client-fields.js\"\n\n/** A CSP script hash: `sha256-` and the base64 of a 32-byte digest. */\nconst SCRIPT_HASH = /^sha256-[A-Za-z0-9+/]{43}=$/\n\n/** A `__Host-` cookie name prefix made of cookie-token characters. */\nconst COOKIE_PREFIX = /^__Host-[A-Za-z0-9_.-]{0,40}$/\n\n/**\n * Apply defaults to the options of `createMcpAuth` and check them.\n *\n * @param options - The options as given.\n * @param renderers - The built-in page renderers, used when none is given.\n * @param renderers.consent - Built-in consent page.\n * @param renderers.error - Built-in error page.\n * @returns The complete, checked options.\n * @throws {McpAuthConfigError} When any option is invalid.\n * @internal\n */\nexport function resolveOptions(\n options: McpAuthOptions,\n renderers: {\n readonly consent: (context: ConsentPageContext) => string\n readonly error: (context: ErrorPageContext) => string\n },\n): ResolvedOptions {\n const insecure = options.allowInsecureLocalhost === true\n const issuerUrl = parseServerUrl(\"issuer\", options.issuer, insecure)\n const issuerPath = trimPath(issuerUrl.pathname)\n const issuer = `${issuerUrl.origin}${issuerPath}`\n\n if (\n options.issuer !== issuer &&\n !(issuerPath === \"\" && options.issuer === `${issuer}/`)\n ) {\n throw new McpAuthConfigError(\n `issuer must be in normalized form, for example ${issuer}`,\n )\n }\n\n const resourceUrl = parseServerUrl(\"resource\", options.resource, insecure)\n const resourcePath = trimPath(resourceUrl.pathname)\n const scopes = options.scopes ?? DEFAULT_SCOPES\n\n if (scopes.length === 0 || !scopes.every(isScopeToken)) {\n throw new McpAuthConfigError(\"scopes must be non-empty scope tokens\")\n }\n\n const defaultScopes = options.defaultScopes ?? scopes\n\n if (\n defaultScopes.length === 0 ||\n !defaultScopes.every((scope) => scopes.includes(scope))\n ) {\n throw new McpAuthConfigError(\"defaultScopes must be a subset of scopes\")\n }\n\n const graceSeconds =\n options.refreshReuseGraceSeconds ?? DEFAULT_REFRESH_REUSE_GRACE_SECONDS\n\n if (\n !Number.isInteger(graceSeconds) ||\n graceSeconds < 0 ||\n graceSeconds > MAX_REFRESH_REUSE_GRACE_SECONDS\n ) {\n throw new McpAuthConfigError(\n `refreshReuseGraceSeconds must be an integer from 0 to ${MAX_REFRESH_REUSE_GRACE_SECONDS}`,\n )\n }\n\n const lifetimes = options.lifetimes ?? {}\n const lifetime = (key: keyof typeof LIFETIME_BOUNDS): number =>\n seconds(\n key,\n lifetimes[key],\n DEFAULT_LIFETIMES[key],\n LIFETIME_BOUNDS[key].min,\n LIFETIME_BOUNDS[key].max,\n )\n const grantIdleSeconds = lifetime(\"grantIdle\") / 1000\n\n if (\n grantIdleSeconds > 0 &&\n grantIdleSeconds < lifetime(\"accessToken\") / 1000\n ) {\n throw new McpAuthConfigError(\n \"lifetimes.grantIdle must be 0 or at least lifetimes.accessToken\",\n )\n }\n\n const defaultGrantTypes = parseGrantTypes(\n options.defaultGrantTypes ?? [\"authorization_code\"],\n true,\n )\n\n if (defaultGrantTypes === undefined) {\n throw new McpAuthConfigError(\n \"defaultGrantTypes may only list authorization_code and refresh_token, and must include authorization_code\",\n )\n }\n\n const paths = resolvePaths(issuerPath, options.paths)\n const cookiePrefix = options.cookiePrefix ?? DEFAULT_COOKIE_PREFIX\n\n if (!COOKIE_PREFIX.test(cookiePrefix)) {\n throw new McpAuthConfigError(\n \"cookiePrefix must start with __Host- and use only letters, digits, '.', '_' and '-'\",\n )\n }\n\n const limits = resolveLimits(options.limits)\n const now = checkedClock(options.now ?? Date.now)\n const scriptHashes = [...(options.scriptHashes ?? [])]\n\n for (const hash of scriptHashes) {\n if (!SCRIPT_HASH.test(hash)) {\n throw new McpAuthConfigError(\n \"scriptHashes entries must look like sha256-<base64 of 32 bytes>\",\n )\n }\n }\n\n const replaySeconds =\n options.consentReplaySeconds ?? DEFAULT_CONSENT_REPLAY_SECONDS\n\n if (\n !Number.isInteger(replaySeconds) ||\n replaySeconds < 0 ||\n replaySeconds > MAX_CONSENT_REPLAY_SECONDS\n ) {\n throw new McpAuthConfigError(\n `consentReplaySeconds must be an integer from 0 to ${MAX_CONSENT_REPLAY_SECONDS}`,\n )\n }\n\n const prm = PROTECTED_RESOURCE_METADATA_PATH\n const asm = AUTHORIZATION_SERVER_METADATA_PATH\n const resourceMetadataPaths =\n resourcePath === \"\" ? [prm] : [`${prm}${resourcePath}`, prm]\n const authorizationServerMetadataPaths =\n issuerPath === \"\" ? [asm] : [`${asm}${issuerPath}`, asm]\n // An endpoint must not shadow a metadata document or the MCP endpoint\n // itself: `handle()` would answer it and the real route would be unreachable.\n const reserved = new Set([\n ...resourceMetadataPaths,\n ...authorizationServerMetadataPaths,\n resourceUrl.pathname,\n resourcePath === \"\" ? \"/\" : resourcePath,\n ])\n\n for (const [name, path] of Object.entries(paths)) {\n if (reserved.has(path)) {\n throw new McpAuthConfigError(\n `paths.${name} collides with a metadata path or the resource path`,\n )\n }\n }\n\n return {\n issuer,\n issuerOrigin: issuerUrl.origin,\n resource: options.resource,\n resourceMetadataUrl: `${resourceUrl.origin}${prm}${resourcePath}`,\n resourceMetadataPaths,\n authorizationServerMetadataPaths,\n scopes: [...scopes],\n defaultScopes: [...defaultScopes],\n identityProvider: options.identityProvider,\n store: options.store,\n sealer: options.sealer,\n renderConsentPage: options.renderConsentPage ?? renderers.consent,\n renderErrorPage: options.renderErrorPage ?? renderers.error,\n accessTokenTtlMs: lifetime(\"accessToken\"),\n refreshTokenTtlMs: lifetime(\"refreshToken\"),\n grantTtlMs: lifetime(\"grant\"),\n grantIdleMs: lifetime(\"grantIdle\"),\n defaultGrantTypes,\n codeTtlMs: lifetime(\"authorizationCode\"),\n pendingTtlMs: lifetime(\"pendingAuthorization\"),\n clientMetadataCacheMs: lifetime(\"clientMetadataCache\"),\n clientMetadataFailureMs: lifetime(\"clientMetadataFailure\"),\n limits,\n cookiePrefix,\n scriptHashes,\n consentReplayMs: replaySeconds * 1000,\n refreshReuseGraceMs: graceSeconds * 1000,\n clientIdMetadataDocuments: options.clientIdMetadataDocuments ?? true,\n dynamicClientRegistration: options.dynamicClientRegistration ?? true,\n strictTransportSecurity: options.strictTransportSecurity !== false,\n paths,\n callbackUrl: `${issuerUrl.origin}${paths.callback}`,\n trustProxy: options.trustProxy ?? false,\n now,\n scopeDescriptions: resolveScopeDescriptions(\n scopes,\n options.scopeDescriptions,\n ),\n staticClients: resolveStaticClients(options.clients, scopes, limits, now),\n ...(options.resourceName !== undefined\n ? { resourceName: options.resourceName }\n : {}),\n ...(options.rateLimit ? { rateLimit: options.rateLimit } : {}),\n ...(options.getClientIp ? { getClientIp: options.getClientIp } : {}),\n ...(options.onEvent ? { onEvent: options.onEvent } : {}),\n ...(options.fetchClientMetadata\n ? { fetchClientMetadata: options.fetchClientMetadata }\n : {}),\n ...(options.authorizeClient\n ? { authorizeClient: options.authorizeClient }\n : {}),\n ...(options.allowRegistration\n ? { allowRegistration: options.allowRegistration }\n : {}),\n ...(options.mapSubject ? { mapSubject: options.mapSubject } : {}),\n ...(options.allowSubject ? { allowSubject: options.allowSubject } : {}),\n ...(options.allowClientMetadataUrl\n ? { allowClientMetadataUrl: options.allowClientMetadataUrl }\n : {}),\n ...(options.getGrantContext\n ? { getGrantContext: options.getGrantContext }\n : {}),\n ...(options.getConsentDetails\n ? { getConsentDetails: options.getConsentDetails }\n : {}),\n ...(options.readConsentFields\n ? { readConsentFields: options.readConsentFields }\n : {}),\n ...(options.beforeGrant ? { beforeGrant: options.beforeGrant } : {}),\n ...(options.afterGrant ? { afterGrant: options.afterGrant } : {}),\n ...(options.beforeTokenIssue\n ? { beforeTokenIssue: options.beforeTokenIssue }\n : {}),\n }\n}\n", "import { safeFetch } from \"@gydr-ai/safe-fetch\"\n\nimport { ClientMetadataError } from \"../errors/client-metadata-error.js\"\nimport {\n cleanClientName,\n parseClientScopes,\n parseGrantTypes,\n} from \"../helpers/client-fields.js\"\nimport { isRecord, isStringArray } from \"../helpers/guards.js\"\nimport { isAcceptableRedirectUri } from \"../helpers/redirect-uris.js\"\nimport type {\n ClientMetadataFetcher,\n ResolvedOptions,\n} from \"../interfaces/options.js\"\nimport type { ClientRecord } from \"../interfaces/records.js\"\n\n/**\n * Turn a fetched document into a client record, or throw\n * {@link ClientMetadataError}. The document must name its own URL as\n * `client_id` and describe a public client. Redirect URIs this server would\n * never use (custom schemes, plain `http` off loopback) are left out; at\n * least one usable URI must remain.\n *\n * @param url - The `client_id` the document was downloaded from; the document's `client_id` must equal it exactly.\n * @param document - The parsed JSON body, of unknown shape.\n * @param now - Time of the download, in milliseconds since the epoch; stored as `createdAt`.\n * @param options - Supported scopes and size limits used to filter the document's values.\n * @returns A `metadata_document` client record with only usable redirect URIs, supported grant types and scopes, and a cleaned name.\n * @throws {ClientMetadataError} When the document is not an object, names another `client_id`, describes a confidential client, or has no usable grant type, response type, scope or redirect URI.\n * @internal\n */\nexport function validateClientMetadata(\n url: string,\n document: unknown,\n now: number,\n options: Pick<ResolvedOptions, \"scopes\" | \"limits\" | \"defaultGrantTypes\">,\n): ClientRecord {\n if (!isRecord(document)) throw new ClientMetadataError(\"not_an_object\")\n\n if (document.client_id !== url) {\n throw new ClientMetadataError(\"client_id_differs_from_url\")\n }\n\n const methods = document.token_endpoint_auth_methods_supported\n\n // A client that names any way to authenticate other than `none` (singular,\n // or a plural list without `none`) is confidential.\n if (\n (document.token_endpoint_auth_method !== undefined &&\n document.token_endpoint_auth_method !== \"none\") ||\n (methods !== undefined &&\n (!isStringArray(methods) || !methods.includes(\"none\"))) ||\n document.client_secret !== undefined\n ) {\n throw new ClientMetadataError(\"confidential_client\")\n }\n\n const grantTypes = parseGrantTypes(\n document.grant_types,\n false,\n options.defaultGrantTypes,\n )\n\n if (grantTypes === undefined) {\n throw new ClientMetadataError(\"grant_types_unusable\")\n }\n\n if (\n document.response_types !== undefined &&\n (!isStringArray(document.response_types) ||\n !document.response_types.includes(\"code\"))\n ) {\n throw new ClientMetadataError(\"response_types_unusable\")\n }\n\n const scopes = parseClientScopes(\n document.scope,\n options.scopes,\n false,\n options.limits.scopeLength,\n )\n\n if (scopes === undefined) throw new ClientMetadataError(\"scope_unusable\")\n\n if (!isStringArray(document.redirect_uris)) {\n throw new ClientMetadataError(\"redirect_uris_absent\")\n }\n\n const redirectUris = document.redirect_uris\n .filter((uri) => isAcceptableRedirectUri(uri, options.limits))\n .slice(0, options.limits.redirectUris)\n\n if (redirectUris.length === 0) {\n throw new ClientMetadataError(\"redirect_uris_unusable\")\n }\n\n const clientName = cleanClientName(\n document.client_name,\n options.limits.clientNameLength,\n )\n\n return {\n clientId: url,\n redirectUris,\n kind: \"metadata_document\",\n createdAt: now,\n grantTypes,\n ...(scopes !== null ? { scopes } : {}),\n ...(clientName !== undefined ? { clientName } : {}),\n }\n}\n\n/**\n * The bundled fetcher: `@gydr-ai/safe-fetch`, redirects refused.\n *\n * @param options - Size limits supplying the download deadline and the largest accepted body.\n * @returns A fetcher with the configured deadline and size cap.\n */\nfunction bundledFetcher(\n options: Pick<ResolvedOptions, \"limits\">,\n): ClientMetadataFetcher {\n return (url, init) =>\n safeFetch(\n url,\n {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n redirect: \"error\",\n signal: init.signal,\n },\n {\n timeoutMs: options.limits.clientMetadataTimeoutMs,\n maxResponseBytes: options.limits.clientMetadataBytes,\n label: \"client metadata\",\n },\n )\n}\n\n/**\n * Resolve `work`, or reject as soon as `signal` aborts.\n *\n * @param work - Pending read or fetch to race against the deadline.\n * @param signal - Deadline signal created from `limits.clientMetadataTimeoutMs`.\n * @returns Whatever `work` resolves to.\n */\nfunction beforeAbort<T>(work: Promise<T>, signal: AbortSignal): Promise<T> {\n if (signal.aborted) return Promise.reject(new ClientMetadataError(\"timeout\"))\n\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => {\n reject(new ClientMetadataError(\"timeout\"))\n }\n\n signal.addEventListener(\"abort\", onAbort, { once: true })\n work.then(\n (value) => {\n signal.removeEventListener(\"abort\", onAbort)\n resolve(value)\n },\n (error: unknown) => {\n signal.removeEventListener(\"abort\", onAbort)\n reject(\n error instanceof Error\n ? error\n : new ClientMetadataError(failureReason(error)),\n )\n },\n )\n })\n}\n\n/**\n * Read at most `limit` bytes of a body as UTF-8, before the deadline.\n *\n * @param response - Response from the metadata fetcher with status 200.\n * @param limit - Largest body accepted, in bytes (`limits.clientMetadataBytes`); checked against `Content-Length` and while streaming.\n * @param signal - Deadline signal; reading stops with `timeout` when it fires.\n * @returns The body decoded as UTF-8.\n * @throws {ClientMetadataError} When the body is too large or the deadline passes.\n */\nasync function readLimited(\n response: Response,\n limit: number,\n signal: AbortSignal,\n): Promise<string> {\n const declared = Number(response.headers.get(\"content-length\"))\n\n if (Number.isFinite(declared) && declared > limit) {\n await response.body?.cancel().catch(() => {})\n throw new ClientMetadataError(\"too_large\")\n }\n\n if (response.body === null) return \"\"\n\n const reader = response.body.getReader()\n const parts: Uint8Array[] = []\n let size = 0\n\n try {\n for (;;) {\n const chunk = await beforeAbort(reader.read(), signal)\n\n if (chunk.done) break\n\n const bytes: unknown = chunk.value\n\n if (!(bytes instanceof Uint8Array)) {\n throw new ClientMetadataError(\"not_bytes\")\n }\n\n size += bytes.byteLength\n\n if (size > limit) throw new ClientMetadataError(\"too_large\")\n\n parts.push(bytes)\n }\n } catch (error) {\n await reader.cancel().catch(() => {})\n throw error\n }\n\n return Buffer.concat(parts).toString(\"utf8\")\n}\n\n/**\n * A short reason for a failed download, for events.\n *\n * @param error - What was thrown.\n * @returns The reason.\n * @internal\n */\nexport function failureReason(error: unknown): string {\n if (error instanceof ClientMetadataError) return error.reason\n\n if (!isRecord(error)) return \"unreachable\"\n\n if (typeof error.code === \"string\") return error.code\n\n return typeof error.reason === \"string\" ? error.reason : \"unreachable\"\n}\n\n/** A downloaded metadata document and how long it may be cached. */\nexport interface FetchedClient {\n /** The validated client. */\n readonly client: ClientRecord\n /**\n * The document's own `Cache-Control` lifetime in seconds: `max-age`, or\n * 0 for `no-store` / `no-cache`; `undefined` when it states none.\n */\n readonly maxAgeSeconds: number | undefined\n}\n\n/**\n * The cache lifetime a `Cache-Control` header states.\n *\n * @param header - The header value, if any.\n * @returns Seconds (0 for `no-store` / `no-cache`), or `undefined` when the\n * header states no lifetime.\n * @internal\n */\nexport function cacheControlSeconds(header: string | null): number | undefined {\n if (header === null) return undefined\n\n let seconds: number | undefined\n\n for (const part of header.split(\",\")) {\n const [rawName = \"\", rawValue = \"\"] = part.split(\"=\", 2)\n const name = rawName.trim().toLowerCase()\n\n switch (name) {\n case \"no-store\":\n case \"no-cache\":\n return 0\n case \"max-age\": {\n const value = rawValue.trim().replace(/^\"(.*)\"$/, \"$1\")\n\n seconds = /^\\d{1,10}$/.test(value) ? Number(value) : 0\n break\n }\n default:\n break\n }\n }\n\n return seconds\n}\n\n/**\n * Download and validate one metadata document, without any caching. The\n * deadline, size cap, status check and document checks always apply, also\n * with a custom fetcher.\n *\n * @param url - A `client_id` already confirmed to be a metadata-document URL.\n * @param now - Time of the download, in milliseconds since the epoch.\n * @param options - Supported scopes, size limits, and the optional custom fetcher.\n * @returns The `metadata_document` client record and its stated cache lifetime.\n * @throws {ClientMetadataError} When the download fails, times out, is not status 200, is too large, is not JSON, or fails validation.\n * @internal\n */\nexport async function fetchClientMetadata(\n url: string,\n now: number,\n options: Pick<\n ResolvedOptions,\n \"scopes\" | \"limits\" | \"fetchClientMetadata\" | \"defaultGrantTypes\"\n >,\n): Promise<FetchedClient> {\n const fetcher = options.fetchClientMetadata ?? bundledFetcher(options)\n const signal = AbortSignal.timeout(options.limits.clientMetadataTimeoutMs)\n let text: string\n let maxAgeSeconds: number | undefined\n\n try {\n const response = await beforeAbort(fetcher(url, { signal }), signal)\n\n if (response.status !== 200) {\n await response.body?.cancel().catch(() => {})\n throw new ClientMetadataError(`status_${response.status}`)\n }\n\n maxAgeSeconds = cacheControlSeconds(response.headers.get(\"cache-control\"))\n text = await readLimited(\n response,\n options.limits.clientMetadataBytes,\n signal,\n )\n } catch (error) {\n throw error instanceof ClientMetadataError\n ? error\n : new ClientMetadataError(failureReason(error))\n }\n\n let document: unknown\n\n try {\n document = JSON.parse(text)\n } catch {\n throw new ClientMetadataError(\"invalid_json\")\n }\n\n return {\n client: validateClientMetadata(url, document, now, options),\n maxAgeSeconds,\n }\n}\n", "/**\n * A Client ID Metadata Document was not accepted. Carries no document\n * content.\n *\n * @internal\n */\nexport class ClientMetadataError extends Error {\n /** Short machine-readable reason, reported in events. */\n readonly reason: string\n\n /**\n * @param reason - Short machine-readable reason.\n */\n constructor(reason: string) {\n super(`Client ID Metadata Document not accepted (${reason})`)\n this.name = \"ClientMetadataError\"\n this.reason = reason\n }\n}\n", "import { trimTrailing } from \"../helpers/text.js\"\nimport { CLIENT_ID_LENGTH_LIMIT } from \"../constants/defaults.js\"\nimport { isMetadataDocumentClientId } from \"../helpers/client-fields.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { ClientRecord } from \"../interfaces/records.js\"\nimport {\n failureReason,\n fetchClientMetadata,\n type FetchedClient,\n} from \"./client-metadata.js\"\nimport { permits } from \"./policy.js\"\n\n/**\n * The host of a URL, for events.\n *\n * @param url - Any URL.\n * @returns Its host, or an empty string.\n */\nfunction hostOf(url: string): string {\n try {\n return new URL(url).host\n } catch {\n return \"\"\n }\n}\n\n/**\n * A URL reduced to what identifies a server: scheme, host without a\n * trailing dot, port (the parser drops a default one) and path without\n * trailing slashes. Query and fragment are ignored.\n *\n * @param value - An absolute URL.\n * @returns The reduced form, or `undefined` when it does not parse.\n */\nfunction serverIdentity(value: string): string | undefined {\n let url: URL\n\n try {\n url = new URL(value)\n } catch {\n return undefined\n }\n\n const host = trimTrailing(url.hostname, \".\")\n const path = trimTrailing(url.pathname, \"/\")\n\n return `${url.protocol}//${host}:${url.port}${path}`\n}\n\n/**\n * Whether a URL names the issuer itself, compared after parsing.\n *\n * @param url - A metadata-document `client_id`.\n * @param issuer - The configured issuer.\n * @returns `true` when both identify the same server URL.\n * @internal\n */\nexport function isIssuerUrl(url: string, issuer: string): boolean {\n const given = serverIdentity(url)\n\n return given !== undefined && given === serverIdentity(issuer)\n}\n\n/**\n * Find the client behind a `client_id`. Opaque ids are looked up among the\n * pre-registered clients, then in the store; a URL id is checked against\n * `allowClientMetadataUrl`, then served from the metadata cache or\n * downloaded.\n *\n * @param context - Context supplying the store, pre-registered clients, metadata policy hook and the per-instance download state.\n * @param clientId - The `client_id` as sent.\n * @returns The client, or `undefined` when it is unknown or unusable.\n * @internal\n */\nexport async function resolveClient(\n context: HandlerContext,\n clientId: string,\n): Promise<ClientRecord | undefined> {\n const { options } = context\n\n if (clientId.length > CLIENT_ID_LENGTH_LIMIT) return undefined\n\n if (!isMetadataDocumentClientId(clientId)) {\n const listed = options.staticClients.get(clientId)\n\n if (listed !== undefined) return listed\n\n const stored = await options.store.getClient(clientId)\n\n // Only a real download may produce a metadata-document client.\n return stored?.kind === \"metadata_document\" ? undefined : stored\n }\n\n if (!options.clientIdMetadataDocuments) return undefined\n\n // The authorization server's own URL can never be a client of it\n // (CIMD-03 security considerations).\n if (isIssuerUrl(clientId, options.issuer)) {\n await context.emit({\n type: \"client_metadata_refused\",\n host: hostOf(clientId),\n reason: \"client_id_is_issuer\",\n })\n\n return undefined\n }\n\n const allow = options.allowClientMetadataUrl\n\n if (allow !== undefined && !(await permits(() => allow(clientId)))) {\n await context.emit({\n type: \"client_metadata_refused\",\n host: hostOf(clientId),\n reason: \"not_allowed_by_policy\",\n })\n\n return undefined\n }\n\n const cached = await options.store.getCachedClientMetadata(clientId)\n\n if (cached?.clientId === clientId) return cached\n\n const { failures, inflight } = context.cimd\n const retryAfter = failures.get(clientId)\n\n // A URL that recently failed is not contacted again until its back-off\n // ends: repeated authorization attempts cannot multiply outbound requests.\n if (retryAfter !== undefined) {\n if (retryAfter > options.now()) return undefined\n\n failures.delete(clientId)\n }\n\n // Simultaneous requests for the same URL wait on one download.\n const pending = inflight.get(clientId)\n\n if (pending !== undefined) return pending\n\n const download = downloadAndRemember(context, clientId).finally(() => {\n inflight.delete(clientId)\n })\n\n inflight.set(clientId, download)\n\n return download\n}\n\n/**\n * Download a metadata document, cache it on success and remember a failure.\n *\n * @param context - Context supplying the clock, cache lifetime, fetcher and event hook.\n * @param clientId - The document URL.\n * @returns The client, or `undefined` on failure.\n */\nasync function downloadAndRemember(\n context: HandlerContext,\n clientId: string,\n): Promise<ClientRecord | undefined> {\n const { options } = context\n const startedAt = options.now()\n let fetched: FetchedClient\n\n try {\n fetched = await fetchClientMetadata(clientId, startedAt, options)\n } catch (error) {\n rememberFailure(context, clientId)\n await context.emit({\n type: \"client_metadata_refused\",\n host: hostOf(clientId),\n reason: failureReason(error),\n })\n\n return undefined\n }\n\n // The document's own `Cache-Control` is honoured, never beyond the\n // configured lifetime (CIMD-03 \u00A7 4.2).\n const stated = fetched.maxAgeSeconds\n const cacheMs =\n stated === undefined\n ? options.clientMetadataCacheMs\n : Math.min(stated * 1000, options.clientMetadataCacheMs)\n\n if (cacheMs > 0) {\n await options.store.putCachedClientMetadata(\n clientId,\n fetched.client,\n startedAt + cacheMs,\n )\n }\n\n return fetched.client\n}\n\n/**\n * Remember that a URL failed, evicting the oldest entry when the list is full.\n *\n * @param context - Context holding the per-instance failure map and its configured size and back-off.\n * @param clientId - The document URL.\n */\nfunction rememberFailure(context: HandlerContext, clientId: string): void {\n const { options } = context\n const { failures } = context.cimd\n\n if (options.clientMetadataFailureMs === 0) return\n\n failures.delete(clientId)\n\n while (failures.size >= options.limits.clientMetadataFailures) {\n const oldest = failures.keys().next()\n\n if (oldest.done === true) break\n\n failures.delete(oldest.value)\n }\n\n failures.set(clientId, options.now() + options.clientMetadataFailureMs)\n}\n", "import { sha256Hex } from \"./tokens.js\"\n\n/**\n * Read one cookie from the `Cookie` header; the first occurrence wins.\n *\n * @param request - The request.\n * @param name - Cookie name.\n * @returns The cookie value without surrounding spaces, or `undefined` when absent.\n * @internal\n */\nexport function readCookie(request: Request, name: string): string | undefined {\n const header = request.headers.get(\"cookie\")\n\n if (header === null) return undefined\n\n for (const part of header.split(\";\")) {\n const index = part.indexOf(\"=\")\n\n if (index !== -1 && part.slice(0, index).trim() === name) {\n return part.slice(index + 1).trim()\n }\n }\n\n return undefined\n}\n\n/**\n * A `Set-Cookie` value for a `__Host-` cookie: `Secure`, `HttpOnly`,\n * `SameSite=Lax`, `Path=/` and no `Domain`, so it is bound to this exact host.\n *\n * @param name - Cookie name.\n * @param value - Cookie value.\n * @param maxAgeSeconds - Lifetime.\n * @returns The header value.\n * @internal\n */\nexport function hostCookie(\n name: string,\n value: string,\n maxAgeSeconds: number,\n): string {\n return `${name}=${value}; Path=/; Max-Age=${maxAgeSeconds}; HttpOnly; Secure; SameSite=Lax`\n}\n\n/**\n * A `Set-Cookie` value that deletes a `__Host-` cookie.\n *\n * @param name - Cookie name.\n * @returns The header value.\n * @internal\n */\nexport function clearHostCookie(name: string): string {\n return `${name}=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Lax`\n}\n\n/**\n * Cookie name for one flow: the configured prefix plus a tag derived from\n * the flow id. Separate names let a browser run several authorizations side\n * by side.\n *\n * @param prefix - Configured cookie name prefix.\n * @param flowId - The flow or consent id.\n * @returns The cookie name.\n * @internal\n */\nexport function bindingCookieName(prefix: string, flowId: string): string {\n return prefix + sha256Hex(`binding:${flowId}`).slice(0, 20)\n}\n", "/**\n * Parameters grouped by name, each with all its values in order.\n *\n * @internal\n */\nexport type Params = ReadonlyMap<string, readonly string[]>\n\n/**\n * Group URL-encoded parameters by name, keeping repeated values.\n *\n * @param search - Parsed query or form body.\n * @returns The grouped parameters.\n * @internal\n */\nexport function groupParams(search: URLSearchParams): Params {\n const params = new Map<string, string[]>()\n\n for (const [key, value] of search) {\n const values = params.get(key)\n\n if (values) values.push(value)\n else params.set(key, [value])\n }\n\n return params\n}\n\n/**\n * Names that appear more than once, which RFC 6749 \u00A7 3.1 and \u00A7 3.2 forbid.\n *\n * @param params - Grouped parameters.\n * @returns The repeated names.\n * @internal\n */\nexport function duplicatedNames(params: Params): string[] {\n return [...params].filter(([, values]) => values.length > 1).map(([k]) => k)\n}\n\n/**\n * The value of a parameter; an empty value counts as absent.\n *\n * @param params - Grouped parameters.\n * @param name - Parameter name.\n * @returns The first value, or `undefined`.\n * @internal\n */\nexport function single(params: Params, name: string): string | undefined {\n const value = params.get(name)?.[0]\n\n return value === undefined || value === \"\" ? undefined : value\n}\n\n/**\n * Which of the parameters a protocol defines appear more than once. RFC 6749\n * \u00A7 3.1 and \u00A7 3.2 (OAuth 2.1 \u00A7 3.1) forbid repeating a defined parameter but\n * require unknown parameters to be ignored, even when repeated.\n *\n * @param params - Grouped query or form parameters, unvalidated.\n * @param defined - Names the endpoint defines.\n * @returns The defined names that were sent more than once.\n * @internal\n */\nexport function repeatedDefined(\n params: Params,\n defined: readonly string[],\n): string[] {\n return defined.filter((name) => (params.get(name)?.length ?? 0) > 1)\n}\n", "/**\n * Split an absolute URL into its lower-cased `scheme://authority` and the\n * untouched remainder (path, query).\n *\n * @param value - An absolute URL as written.\n * @returns The two parts, or `undefined` when there is no `://`.\n */\nfunction splitAuthority(\n value: string,\n): { authority: string; rest: string } | undefined {\n const start = value.indexOf(\"://\")\n\n if (start === -1) return undefined\n\n const pathStart = value.indexOf(\"/\", start + 3)\n const end = pathStart === -1 ? value.length : pathStart\n\n return {\n authority: value.slice(0, end).toLowerCase(),\n rest: value.slice(end),\n }\n}\n\n/**\n * Whether a `resource` parameter names the configured resource. Scheme and\n * host are compared case-insensitively (RFC 3986 \u00A7 6.2.2.1); the path and\n * query must match exactly.\n *\n * @param configured - The configured `resource` option.\n * @param given - The raw `resource` parameter, unvalidated.\n * @returns `true` when both name the same resource.\n * @internal\n */\nexport function sameResource(configured: string, given: string): boolean {\n if (given === configured) return true\n\n const a = splitAuthority(configured)\n const b = splitAuthority(given)\n\n return (\n a !== undefined &&\n b !== undefined &&\n a.authority === b.authority &&\n a.rest === b.rest\n )\n}\n", "import { isScopeToken } from \"./text.js\"\n\n/**\n * The scopes a `scope` parameter asks for, each listed once in the order\n * given. An absent parameter yields `fallback`. Returns `undefined` when the\n * value is longer than `maxLength`, empty, or names a scope outside\n * `permitted`.\n *\n * @param requested - The raw `scope` query value; `undefined` when absent.\n * @param permitted - Scopes this client may receive: its registered scopes, or all supported scopes.\n * @param fallback - Scopes granted when `requested` is absent; a subset of `permitted`.\n * @param maxLength - Longest `scope` value accepted, in characters (`limits.scopeLength`).\n * @returns The distinct requested scopes in request order (or `fallback` when none were requested), or `undefined` when the value is too long, empty, or names a scope outside `permitted`.\n * @internal\n */\nexport function resolveScopes(\n requested: string | undefined,\n permitted: readonly string[],\n fallback: readonly string[],\n maxLength: number,\n): readonly string[] | undefined {\n if (requested === undefined) return fallback\n\n if (requested.length > maxLength) return undefined\n\n const asked = [...new Set(requested.split(\" \").filter((s) => s.length > 0))]\n\n if (asked.length === 0) return undefined\n\n const allKnown = asked.every((s) => isScopeToken(s) && permitted.includes(s))\n\n return allKnown ? asked : undefined\n}\n", "import type { ErrorPageReason } from \"../constants/error-pages.js\"\nimport { resolveClient } from \"../core/clients.js\"\nimport { decide } from \"../core/policy.js\"\nimport { bindingCookieName, hostCookie } from \"../helpers/cookies.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\nimport { groupParams, repeatedDefined, single } from \"../helpers/params.js\"\nimport { matchesAnyRedirectUri } from \"../helpers/redirect-uris.js\"\nimport { sameResource } from \"../helpers/resource.js\"\nimport { redirect } from \"../helpers/responses.js\"\nimport { resolveScopes } from \"../helpers/scopes.js\"\nimport {\n isWellFormedChallenge,\n pkceChallenge,\n randomToken,\n sha256Hex,\n} from \"../helpers/tokens.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { PendingAuthorization } from \"../interfaces/records.js\"\n\n/** Parameters this endpoint defines; only these may not be repeated. */\nconst AUTHORIZE_PARAMETERS = [\n \"client_id\",\n \"redirect_uri\",\n \"response_type\",\n \"code_challenge\",\n \"code_challenge_method\",\n \"resource\",\n \"scope\",\n \"state\",\n \"prompt\",\n] as const\n\n/**\n * `GET /authorize`: validate the request and send the browser to the\n * identity provider.\n *\n * Every problem found here is shown as a page in the browser, never sent to\n * the client's redirect URI: the person has not signed in yet, and\n * redirecting an unauthenticated request with an error would make this\n * endpoint an open redirector for any registered (or self-registered) client\n * (RFC 9700 \u00A7 4.11.2, OAuth 2.1 \u00A7 7.13.2). Errors are returned to the client\n * only after sign-in, from the callback and consent steps.\n *\n * On success the request is saved under a fresh flow id, a cookie ties the\n * flow to this browser, and the identity provider receives the flow id as\n * `state` together with this server's own PKCE challenge and nonce.\n *\n * @param context - Context supplying clients, policy hooks, the identity provider, store and cookie prefix.\n * @param request - The `GET /authorize` request opened in the person's browser; its query is unvalidated.\n * @returns A redirect to the identity provider, or an error page.\n * @internal\n */\nexport async function handleAuthorize(\n context: HandlerContext,\n request: Request,\n): Promise<Response> {\n const { options } = context\n const { limits } = options\n\n if (!(await context.allowed(request, \"authorize\"))) {\n return context.errorPage(request, \"rate_limited\")\n }\n\n const params = groupParams(new URL(request.url).searchParams)\n const refuse = async (\n reason: ErrorPageReason,\n clientId?: string,\n extra: { detail?: string; policyReason?: string } = {},\n ) => {\n await context.emit({\n type: \"authorization_refused\",\n reason,\n ...(clientId !== undefined ? { clientId } : {}),\n })\n\n return context.errorPage(request, reason, extra)\n }\n\n // A defined parameter may appear once (OAuth 2.1 \u00A7 3.1). Two different\n // `redirect_uri` or `client_id` values could be read differently by\n // different parsers. Unknown parameters are ignored, even when repeated.\n if (repeatedDefined(params, AUTHORIZE_PARAMETERS).length > 0) {\n return refuse(\"repeated_parameter\")\n }\n\n const clientId = single(params, \"client_id\")\n const client =\n clientId === undefined ? undefined : await resolveClient(context, clientId)\n\n if (clientId === undefined || client === undefined) {\n return refuse(\"client_unknown\")\n }\n\n // OAuth 2.1 \u00A7 4.1.1: `redirect_uri` may be left out when the client has\n // exactly one registered; that one is then used.\n const sent = single(params, \"redirect_uri\")\n const [onlyRegistered] =\n client.redirectUris.length === 1 ? client.redirectUris : []\n const redirectUri = sent ?? onlyRegistered\n\n if (\n redirectUri === undefined ||\n !matchesAnyRedirectUri(client.redirectUris, redirectUri, limits)\n ) {\n return refuse(\"redirect_uri_not_registered\", clientId)\n }\n\n const state = single(params, \"state\")\n\n if (state !== undefined && state.length > limits.stateLength) {\n return refuse(\"invalid_request\", clientId, {\n detail: `The state value exceeds ${limits.stateLength} characters`,\n })\n }\n\n if (single(params, \"response_type\") !== \"code\") {\n return refuse(\"unsupported_response_type\", clientId, {\n detail: \"Only the authorization code flow is offered\",\n })\n }\n\n const codeChallenge = single(params, \"code_challenge\")\n\n if (codeChallenge === undefined || !isWellFormedChallenge(codeChallenge)) {\n return refuse(\"invalid_request\", clientId, {\n detail: \"A 43-character S256 code_challenge must accompany the request\",\n })\n }\n\n if (single(params, \"code_challenge_method\") !== \"S256\") {\n return refuse(\"invalid_request\", clientId, {\n detail: \"The only accepted code_challenge_method is S256\",\n })\n }\n\n const resource = single(params, \"resource\")\n\n if (resource === undefined || !sameResource(options.resource, resource)) {\n return refuse(\"invalid_target\", clientId, {\n detail: `Tokens from this server are only for ${options.resource}`,\n })\n }\n\n // Clients that declared a scope at registration stay inside it.\n const permitted = client.scopes ?? options.scopes\n const fallback = options.defaultScopes.filter((s) => permitted.includes(s))\n const scopes = resolveScopes(\n params.get(\"scope\")?.[0],\n permitted,\n fallback.length > 0 ? fallback : permitted,\n limits.scopeLength,\n )\n\n if (scopes === undefined) {\n return refuse(\"invalid_scope\", clientId)\n }\n\n // The person is always asked, so a request that forbids any interaction\n // (`prompt=none`) can never succeed.\n if (single(params, \"prompt\")?.split(\" \").includes(\"none\")) {\n return refuse(\"consent_required\", clientId)\n }\n\n // The operator's own policy runs last and can only refuse.\n const policy = options.authorizeClient\n\n if (policy !== undefined) {\n const outcome = await decide(() =>\n policy({\n client: frozenCopy(client),\n redirectUri,\n scopes: frozenCopy(scopes),\n request,\n }),\n )\n\n if (!outcome.allowed) {\n return refuse(\n \"access_denied\",\n clientId,\n outcome.reason === undefined ? {} : { policyReason: outcome.reason },\n )\n }\n }\n\n const flowId = randomToken()\n const binding = randomToken()\n const upstreamVerifier = randomToken()\n const nonce = randomToken()\n const pending: PendingAuthorization = {\n id: flowId,\n clientId: client.clientId,\n clientKind: client.kind,\n redirectUri,\n codeChallenge,\n // Always the configured spelling, whatever case the client used.\n resource: options.resource,\n scopes,\n upstreamCodeVerifier: upstreamVerifier,\n upstreamNonce: nonce,\n bindingHash: sha256Hex(binding),\n allowRefresh: client.grantTypes?.includes(\"refresh_token\") ?? true,\n expiresAt: options.now() + options.pendingTtlMs,\n ...(client.clientName !== undefined\n ? { clientName: client.clientName }\n : {}),\n ...(state !== undefined ? { state } : {}),\n }\n\n await options.store.putPendingAuthorization(pending)\n\n const location = await options.identityProvider.authorizationUrl({\n state: flowId,\n codeChallenge: pkceChallenge(upstreamVerifier),\n redirectUri: options.callbackUrl,\n nonce,\n })\n\n await context.emit({\n type: \"authorization_started\",\n clientId: client.clientId,\n scopes,\n })\n\n return redirect(location, [\n hostCookie(\n bindingCookieName(options.cookiePrefix, flowId),\n binding,\n Math.ceil(options.pendingTtlMs / 1000),\n ),\n ])\n}\n", "import { randomUUID } from \"node:crypto\"\n\nimport { UPSTREAM_LIFETIME_CEILING_SECONDS } from \"../constants/defaults.js\"\nimport { consentPageResponse } from \"../core/consent-page.js\"\nimport { revokeUpstreamToken } from \"../core/grants.js\"\nimport { resolvePerson } from \"../core/subject.js\"\nimport {\n bindingCookieName,\n clearHostCookie,\n readCookie,\n} from \"../helpers/cookies.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\nimport { jsonCopy } from \"../helpers/json.js\"\nimport { groupParams, repeatedDefined, single } from \"../helpers/params.js\"\nimport { buildRedirect } from \"../helpers/redirect-uris.js\"\nimport { redirect } from \"../helpers/responses.js\"\nimport { hasExpired } from \"../helpers/time.js\"\nimport { constantTimeEqual, randomToken, sha256Hex } from \"../helpers/tokens.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { UpstreamLogin } from \"../interfaces/identity-provider.js\"\nimport type { ConsentRequest } from \"../interfaces/records.js\"\n\n/** Parameters an authorization response defines (RFC 6749 \u00A7 4.1.2, RFC 9207). */\nconst CALLBACK_PARAMETERS = [\n \"state\",\n \"code\",\n \"error\",\n \"error_description\",\n \"error_uri\",\n \"iss\",\n] as const\n\n/**\n * `GET /callback`: the identity provider returns the browser here.\n *\n * 1. The browser must carry the cookie set when the flow began; without it\n * the request is refused and the flow is left untouched, so a person who\n * only saw the `state` value cannot cancel someone else's sign-in.\n * 2. The flow is then taken from the store, once; a second callback finds\n * nothing.\n * 3. The provider's code is exchanged, the upstream tokens are sealed for\n * the grant id the person may approve, and the consent page is shown with\n * a one-time form token.\n *\n * @param context - Context supplying the store, identity provider, sealer, hooks and pages.\n * @param request - The browser's return from the identity provider, with `state`, `code` or `error` in the query and the binding cookie.\n * @returns The consent page, a redirect to the client, or an error page.\n * @internal\n */\nexport async function handleCallback(\n context: HandlerContext,\n request: Request,\n): Promise<Response> {\n const { options } = context\n\n if (!(await context.allowed(request, \"callback\"))) {\n return context.errorPage(request, \"rate_limited\")\n }\n\n const params = groupParams(new URL(request.url).searchParams)\n\n if (repeatedDefined(params, CALLBACK_PARAMETERS).length > 0) {\n return context.errorPage(request, \"malformed_response\")\n }\n\n const flowId = single(params, \"state\")\n\n if (flowId === undefined) return context.errorPage(request, \"flow_ended\")\n\n const cookieName = bindingCookieName(options.cookiePrefix, flowId)\n const dropCookie = clearHostCookie(cookieName)\n const observed = await options.store.getPendingAuthorization(flowId)\n\n if (observed === undefined || hasExpired(observed.expiresAt, options.now())) {\n return context.errorPage(request, \"flow_ended\")\n }\n\n const binding = readCookie(request, cookieName)\n\n if (\n binding === undefined ||\n !constantTimeEqual(sha256Hex(binding), observed.bindingHash)\n ) {\n await context.emit({ type: \"flow_binding_mismatch\", step: \"callback\" })\n\n return context.errorPage(request, \"browser_mismatch\", {\n setCookies: [dropCookie],\n })\n }\n\n const pending = await options.store.consumePendingAuthorization(flowId)\n\n if (\n pending === undefined ||\n hasExpired(pending.expiresAt, options.now()) ||\n pending.bindingHash !== observed.bindingHash\n ) {\n return context.errorPage(request, \"flow_ended\")\n }\n\n const providerError = single(params, \"error\")\n\n // The person went to the identity provider from this browser (the\n // binding cookie proves it) and came back; telling the client is not an\n // automatic redirect of an unauthenticated request.\n if (providerError !== undefined) {\n const cancelled = providerError === \"access_denied\"\n\n await context.emit({\n type: \"identity_provider_error\",\n clientId: pending.clientId,\n reason: cancelled ? \"person_cancelled\" : \"provider_reported_error\",\n })\n\n return redirect(\n buildRedirect(pending.redirectUri, {\n error: cancelled ? \"access_denied\" : \"server_error\",\n error_description: cancelled\n ? \"The person stopped at the sign-in step\"\n : \"The identity provider could not complete the sign-in\",\n state: pending.state,\n iss: options.issuer,\n }),\n [dropCookie],\n )\n }\n\n const code = single(params, \"code\")\n\n if (code === undefined) {\n return context.errorPage(request, \"malformed_response\", {\n setCookies: [dropCookie],\n })\n }\n\n const iss = single(params, \"iss\")\n let login: UpstreamLogin\n\n try {\n login = await options.identityProvider.exchangeCode({\n code,\n codeVerifier: pending.upstreamCodeVerifier,\n redirectUri: options.callbackUrl,\n nonce: pending.upstreamNonce,\n ...(iss !== undefined ? { issuer: iss } : {}),\n })\n } catch (error) {\n await context.emit({\n type: \"identity_provider_error\",\n clientId: pending.clientId,\n reason: error instanceof Error ? error.name : \"exchange_failed\",\n })\n\n return context.errorPage(request, \"sign_in_not_confirmed\", {\n setCookies: [dropCookie],\n })\n }\n\n const releaseUpstream = async () => {\n if (login.refreshToken !== undefined) {\n await revokeUpstreamToken(context, login.refreshToken)\n }\n }\n\n if (typeof login.subject !== \"string\" || login.subject.length === 0) {\n await releaseUpstream()\n await context.emit({\n type: \"identity_provider_error\",\n clientId: pending.clientId,\n reason: \"subject_missing\",\n })\n\n return context.errorPage(request, \"sign_in_not_confirmed\", {\n setCookies: [dropCookie],\n })\n }\n\n const client = {\n clientId: pending.clientId,\n kind: pending.clientKind,\n ...(pending.clientName !== undefined\n ? { clientName: pending.clientName }\n : {}),\n }\n const resolved = await resolvePerson(context, login, client)\n\n if (!resolved.ok) {\n await releaseUpstream()\n await context.emit({\n type: \"identity_provider_error\",\n clientId: pending.clientId,\n reason: \"subject_not_permitted\",\n })\n\n return context.errorPage(request, \"subject_not_permitted\", {\n setCookies: [dropCookie],\n ...(resolved.policyReason !== undefined\n ? { policyReason: resolved.policyReason }\n : {}),\n })\n }\n\n const { person } = resolved\n let details: unknown\n\n if (options.getConsentDetails !== undefined) {\n try {\n details = jsonCopy(\n await options.getConsentDetails(\n Object.freeze({\n ...frozenCopy({\n subject: person.subject,\n client,\n scopes: [...pending.scopes],\n }),\n request,\n }),\n ),\n options.limits.consentDetailsBytes,\n \"getConsentDetails\",\n )\n } catch {\n await releaseUpstream()\n\n return context.errorPage(request, \"server_error\", {\n setCookies: [dropCookie],\n })\n }\n }\n\n const now = options.now()\n const grantId = randomUUID()\n const formToken = randomToken()\n const { sealer } = options\n const lifetime =\n login.expiresIn !== undefined && Number.isFinite(login.expiresIn)\n ? Math.min(\n Math.max(login.expiresIn, 0),\n UPSTREAM_LIFETIME_CEILING_SECONDS,\n )\n : undefined\n const consent: ConsentRequest = {\n id: randomToken(),\n grantId,\n clientId: pending.clientId,\n clientKind: pending.clientKind,\n redirectUri: pending.redirectUri,\n codeChallenge: pending.codeChallenge,\n resource: pending.resource,\n scopes: pending.scopes,\n subject: person.subject,\n csrfHash: sha256Hex(formToken),\n bindingHash: pending.bindingHash,\n expiresAt: now + options.pendingTtlMs,\n ...(pending.allowRefresh !== undefined\n ? { allowRefresh: pending.allowRefresh }\n : {}),\n ...(pending.clientName !== undefined\n ? { clientName: pending.clientName }\n : {}),\n ...(pending.state !== undefined ? { state: pending.state } : {}),\n ...(person.displayName !== undefined\n ? { displayName: person.displayName }\n : {}),\n ...(details !== undefined ? { details } : {}),\n ...(login.refreshToken !== undefined\n ? {\n sealedUpstreamRefreshToken: await sealer.seal(\n login.refreshToken,\n grantId,\n ),\n }\n : {}),\n ...(login.accessToken !== undefined && lifetime !== undefined\n ? {\n sealedUpstreamAccessToken: await sealer.seal(\n login.accessToken,\n `${grantId}#access`,\n ),\n upstreamAccessTokenExpiresAt: now + lifetime * 1000,\n }\n : {}),\n }\n let response: Response\n\n // Render before storing: if a custom renderer throws, nothing holding the\n // sealed upstream token is left behind, and the token itself is revoked.\n try {\n response = consentPageResponse(context, request, consent, formToken, {\n binding,\n setCookies: [dropCookie],\n })\n } catch (error) {\n await releaseUpstream()\n throw error\n }\n\n await options.store.putConsentRequest(consent)\n\n return response\n}\n", "import { CONSENT_FIELDS } from \"../constants/protocol.js\"\nimport { bindingCookieName, hostCookie } from \"../helpers/cookies.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\nimport { parseLocales } from \"../helpers/locales.js\"\nimport {\n isLoopbackRedirectUri,\n originOf,\n redirectHostOf,\n} from \"../helpers/redirect-uris.js\"\nimport { html } from \"../helpers/responses.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { ConsentRequest } from \"../interfaces/records.js\"\n\n/**\n * Host of a metadata-document URL, shown so people see who vouches for it.\n *\n * @param url - A metadata-document `client_id` (an `https` URL).\n * @returns The host, or an empty string.\n */\nfunction documentHost(url: string): string {\n try {\n return new URL(url).host\n } catch {\n return \"\"\n }\n}\n\n/** Optional parts of a consent page response. */\ninterface ConsentPageExtras {\n /** HTTP status. Default: 200. */\n readonly status?: number\n /** Problems `readConsentFields` reported, by field name. */\n readonly fieldErrors?: Readonly<Record<string, string>>\n /** Extra field values to fill the form with again. */\n readonly submitted?: Readonly<Record<string, string>>\n /** The plaintext binding cookie value, to set the consent-step cookie. */\n readonly binding?: string\n /** Other `Set-Cookie` values, such as clearing the sign-in cookie. */\n readonly setCookies?: readonly string[]\n}\n\n/**\n * Render the consent page for a stored (or about to be stored) consent\n * request and wrap it in a response with the page security headers.\n *\n * The renderer receives a deeply frozen copy of its context. The form may\n * submit (and be redirected) only to this server and the client's redirect\n * origin; inline scripts run only when listed in `scriptHashes`.\n *\n * @param context - Context supplying the renderer, paths, cookie prefix, lifetimes and script hashes.\n * @param request - The request that leads to this page; its `Accept-Language` sets `locales`.\n * @param consent - The consent request the page is for.\n * @param formToken - The plaintext one-time form token, embedded as a hidden field.\n * @param extras - Status, field errors, submitted values and cookies.\n * @returns The HTML response.\n * @throws {Error} Whatever a custom `renderConsentPage` throws.\n * @internal\n */\nexport function consentPageResponse(\n context: HandlerContext,\n request: Request,\n consent: ConsentRequest,\n formToken: string,\n extras: ConsentPageExtras = {},\n): Response {\n const { options } = context\n const page = options.renderConsentPage(\n frozenCopy({\n clientId: consent.clientId,\n clientKind: consent.clientKind,\n redirectUri: consent.redirectUri,\n redirectHost: redirectHostOf(consent.redirectUri),\n redirectIsLoopback: isLoopbackRedirectUri(consent.redirectUri),\n scopes: consent.scopes,\n scopeDescriptions: { ...options.scopeDescriptions },\n resource: consent.resource,\n formAction: options.paths.consent,\n hiddenFields: {\n [CONSENT_FIELDS.id]: consent.id,\n [CONSENT_FIELDS.csrf]: formToken,\n },\n decisionField: CONSENT_FIELDS.decision,\n locales: parseLocales(request.headers.get(\"accept-language\")),\n ...(consent.clientName !== undefined\n ? { clientName: consent.clientName }\n : {}),\n ...(consent.clientKind === \"metadata_document\"\n ? { clientHost: documentHost(consent.clientId) }\n : {}),\n ...(consent.displayName !== undefined\n ? { subjectDisplayName: consent.displayName }\n : {}),\n ...(consent.details !== undefined ? { details: consent.details } : {}),\n ...(extras.fieldErrors !== undefined\n ? { fieldErrors: extras.fieldErrors }\n : {}),\n ...(extras.submitted !== undefined\n ? { submitted: extras.submitted }\n : {}),\n }),\n )\n const target = originOf(consent.redirectUri)\n const cookies = [...(extras.setCookies ?? [])]\n\n if (extras.binding !== undefined) {\n cookies.push(\n hostCookie(\n bindingCookieName(options.cookiePrefix, consent.id),\n extras.binding,\n Math.ceil(options.pendingTtlMs / 1000),\n ),\n )\n }\n\n return html(extras.status ?? 200, page, {\n formActionOrigins: target === undefined ? [] : [target],\n setCookies: cookies,\n scriptHashes: options.scriptHashes,\n })\n}\n", "import { cleanClientName } from \"../helpers/client-fields.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\nimport { hasHiddenCharacters } from \"../helpers/text.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { UpstreamLogin } from \"../interfaces/identity-provider.js\"\nimport type { MappedSubject } from \"../interfaces/policy.js\"\nimport type { ClientKind } from \"../interfaces/records.js\"\nimport { decide } from \"./policy.js\"\n\n/** Longest subject accepted, from the identity provider or `mapSubject`. */\nconst SUBJECT_LENGTH_LIMIT = 512\n\n/**\n * Whether a subject is safe to store and compare: a non-empty string of at\n * most 512 characters, without surrounding whitespace, already in Unicode\n * NFC form, and free of invisible or formatting characters. Two subjects\n * that look the same must be the same string.\n *\n * @param subject - A candidate subject, of unknown type.\n * @returns `true` when it may be stored.\n */\nfunction isUsableSubject(subject: unknown): subject is string {\n return (\n typeof subject === \"string\" &&\n subject !== \"\" &&\n subject.length <= SUBJECT_LENGTH_LIMIT &&\n subject.trim() === subject &&\n subject.normalize(\"NFC\") === subject &&\n !hasHiddenCharacters(subject)\n )\n}\n\n/**\n * Read what `mapSubject` returned. Each field is read exactly once, so an\n * object with getters cannot pass validation with one value and be stored\n * with another.\n *\n * @param value - The hook's result, of unknown shape.\n * @returns The subject and the raw display name, or `undefined` when the\n * result is not an object.\n */\nfunction readMapped(\n value: unknown,\n): { subject: unknown; displayName: unknown } | undefined {\n if (typeof value !== \"object\" || value === null) return undefined\n\n const subject: unknown = Reflect.get(value, \"subject\")\n const displayName: unknown = Reflect.get(value, \"displayName\")\n\n return { subject, displayName }\n}\n\n/**\n * Decide who signed in: apply `mapSubject`, check the subject, clean the\n * display name, then ask `allowSubject`. Every step fails closed: an invalid\n * mapping, an unusable subject, a refusal or a thrown error means nobody is\n * signed in. Hooks receive deeply frozen copies.\n *\n * @param context - Context supplying the `mapSubject` and `allowSubject`\n * hooks and `limits.clientNameLength` for display names.\n * @param login - What the identity provider reported, after its own checks.\n * @param client - The connecting client.\n * @param client.clientId - Its id.\n * @param client.clientName - Its display name.\n * @param client.kind - How it made itself known.\n * @returns The person to record, or a refusal with the reason\n * `allowSubject` gave, if any.\n * @internal\n */\nexport async function resolvePerson(\n context: HandlerContext,\n login: UpstreamLogin,\n client: { clientId: string; clientName?: string; kind: ClientKind },\n): Promise<\n | { readonly ok: true; readonly person: MappedSubject }\n | { readonly ok: false; readonly policyReason?: string }\n> {\n const { mapSubject, allowSubject, limits } = context.options\n const claims = login.claims ?? {}\n let subject: unknown = login.subject\n let rawName: unknown = login.displayName\n\n if (mapSubject !== undefined) {\n let mapped: { subject: unknown; displayName: unknown } | undefined\n\n try {\n mapped = readMapped(\n await mapSubject(\n frozenCopy({\n subject: login.subject,\n claims,\n ...(login.displayName !== undefined\n ? { displayName: login.displayName }\n : {}),\n }),\n ),\n )\n } catch {\n return { ok: false }\n }\n\n if (mapped === undefined) return { ok: false }\n\n subject = mapped.subject\n rawName = mapped.displayName\n }\n\n if (!isUsableSubject(subject)) return { ok: false }\n\n // A display name is cosmetic: an unusable one is dropped, not fatal.\n const displayName =\n rawName === undefined\n ? undefined\n : cleanClientName(rawName, limits.clientNameLength)\n const person: MappedSubject =\n displayName === undefined ? { subject } : { subject, displayName }\n\n if (allowSubject !== undefined) {\n const outcome = await decide(() =>\n allowSubject(frozenCopy({ ...person, claims, client })),\n )\n\n if (!outcome.allowed) {\n return outcome.reason === undefined\n ? { ok: false }\n : { ok: false, policyReason: outcome.reason }\n }\n }\n\n return { ok: true, person }\n}\n", "import type { ResolvedOptions } from \"../interfaces/options.js\"\nimport type { GrantRecord } from \"../interfaces/records.js\"\n\ntype ExpiryOptions = Pick<ResolvedOptions, \"grantTtlMs\" | \"grantIdleMs\">\n\n/**\n * When a new grant ends: `lifetimes.grant` after approval, or sooner when\n * `lifetimes.grantIdle` is shorter.\n *\n * @param options - The resolved grant lifetimes.\n * @param now - The approval time, in ms.\n * @returns The grant's first expiry, in ms.\n * @internal\n */\nexport function initialGrantExpiry(\n options: ExpiryOptions,\n now: number,\n): number {\n const absolute = now + options.grantTtlMs\n\n return options.grantIdleMs > 0\n ? Math.min(absolute, now + options.grantIdleMs)\n : absolute\n}\n\n/**\n * The grant's new expiry after a refresh: with `lifetimes.grantIdle`, the\n * idle window counted from now, never past the absolute limit; without it,\n * the absolute limit itself, so grants created while `grantIdle` was on are\n * not left on a short expiry after it is turned off.\n *\n * @param options - The resolved grant lifetimes.\n * @param grant - The grant being renewed.\n * @param now - The refresh time, in ms.\n * @returns The later expiry, or `undefined` when nothing moves.\n * @internal\n */\nexport function slidGrantExpiry(\n options: ExpiryOptions,\n grant: Pick<GrantRecord, \"createdAt\" | \"expiresAt\">,\n now: number,\n): number | undefined {\n const absolute = grant.createdAt + options.grantTtlMs\n const next =\n options.grantIdleMs > 0\n ? Math.min(absolute, now + options.grantIdleMs)\n : absolute\n\n return next > grant.expiresAt ? next : undefined\n}\n", "/**\n * A request body went past the size limit while it was being read.\n *\n * @internal\n */\nexport class BodyTooLargeError extends Error {\n /** Creates the error with a fixed message. */\n constructor() {\n super(\"Body exceeds the configured size limit\")\n this.name = \"BodyTooLargeError\"\n }\n}\n", "import { BodyTooLargeError } from \"../errors/body-too-large-error.js\"\n\n/**\n * Whether the request's media type is `expected`, ignoring parameters such\n * as `charset`.\n *\n * @param request - The request.\n * @param expected - Lower-case media type.\n * @returns `true` on a match.\n * @internal\n */\nexport function hasContentType(request: Request, expected: string): boolean {\n const value = request.headers.get(\"content-type\")\n\n if (value === null) return false\n\n const [type] = value.split(\";\")\n\n return (type ?? \"\").trim().toLowerCase() === expected\n}\n\n/**\n * Read a request body as UTF-8, stopping as soon as it passes `maxBytes`, so\n * an oversized body is never held in memory.\n *\n * @param request - The request.\n * @param maxBytes - Largest body accepted, in bytes; checked against `Content-Length` and while streaming.\n * @returns The body decoded as UTF-8; an empty string when there is no body.\n * @throws {BodyTooLargeError} When the body is larger than `maxBytes`.\n * @throws {TypeError} When the body stream yields something other than bytes.\n * @internal\n */\nexport async function readBody(\n request: Request,\n maxBytes: number,\n): Promise<string> {\n const declared = Number(request.headers.get(\"content-length\"))\n\n if (Number.isFinite(declared) && declared > maxBytes) {\n throw new BodyTooLargeError()\n }\n\n if (request.body === null) return \"\"\n\n const reader = request.body.getReader()\n const chunks: Uint8Array[] = []\n let total = 0\n\n for (;;) {\n const chunk = await reader.read()\n\n if (chunk.done) break\n\n const bytes: unknown = chunk.value\n\n // Request bodies are byte streams; anything else is not a valid body.\n if (!(bytes instanceof Uint8Array)) throw new TypeError(\"Body is not bytes\")\n\n total += bytes.byteLength\n\n if (total > maxBytes) {\n await reader.cancel()\n throw new BodyTooLargeError()\n }\n\n chunks.push(bytes)\n }\n\n return Buffer.concat(chunks).toString(\"utf8\")\n}\n", "import {\n CONSENT_APPROVE,\n CONSENT_FIELDS,\n FORM_MEDIA_TYPE,\n} from \"../constants/protocol.js\"\nimport { consentPageResponse } from \"../core/consent-page.js\"\nimport { initialGrantExpiry } from \"../core/grant-expiry.js\"\nimport { grantSummary } from \"../core/grant-summary.js\"\nimport { revokeSealedUpstreamToken } from \"../core/grants.js\"\nimport { decide } from \"../core/policy.js\"\nimport { BodyTooLargeError } from \"../errors/body-too-large-error.js\"\nimport { hasContentType, readBody } from \"../helpers/body.js\"\nimport {\n bindingCookieName,\n clearHostCookie,\n readCookie,\n} from \"../helpers/cookies.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\nimport { isRecord } from \"../helpers/guards.js\"\nimport { jsonCopy } from \"../helpers/json.js\"\nimport {\n groupParams,\n repeatedDefined,\n single,\n type Params,\n} from \"../helpers/params.js\"\nimport { buildRedirect } from \"../helpers/redirect-uris.js\"\nimport { redirect } from \"../helpers/responses.js\"\nimport { hasExpired } from \"../helpers/time.js\"\nimport { setTimeout as sleep } from \"node:timers/promises\"\nimport { constantTimeEqual, randomToken, sha256Hex } from \"../helpers/tokens.js\"\nimport type { HookClient } from \"../interfaces/consent.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { ConsentRequest, GrantRecord } from \"../interfaces/records.js\"\n\n/** How often a repeated submission looks for the first one's answer, in ms. */\nconst REPLAY_POLL_MS = 200\n\n/** What a submission left for a repeat: its answer, or that it is still being decided. */\ntype RememberedAnswer =\n { readonly location: string } | { readonly pending: true }\n\n/** The package's own consent form fields; never passed to `readConsentFields`. */\nconst RESERVED_FIELDS: readonly string[] = Object.values(CONSENT_FIELDS)\n\n/** Most field errors `readConsentFields` may report. */\nconst FIELD_ERROR_LIMIT = 50\n\n/** Longest field name or field error message accepted from `readConsentFields`. */\nconst FIELD_TEXT_LIMIT = 200\n\n/**\n * The extra fields of a consent form: the first value of every field that is\n * not one of the package's own.\n *\n * @param form - The parsed consent form.\n * @returns Field name to value.\n */\nfunction extraFields(form: Params): Record<string, string> {\n const fields: [string, string][] = []\n\n for (const [name, values] of form) {\n const [first] = values\n\n if (!RESERVED_FIELDS.includes(name) && first !== undefined) {\n fields.push([name, first])\n }\n }\n\n // Own data properties only: a field named `__proto__` stays a field.\n return Object.fromEntries(fields)\n}\n\n/**\n * Check what `readConsentFields` reported as field errors.\n *\n * @param value - The hook's `fieldErrors`, of unknown shape.\n * @returns 1\u201350 non-empty string messages by field name, or `undefined`\n * when the value is not usable.\n */\nfunction readFieldErrors(\n value: unknown,\n): Readonly<Record<string, string>> | undefined {\n if (!isRecord(value)) return undefined\n\n const entries = Object.entries(value)\n\n if (entries.length === 0 || entries.length > FIELD_ERROR_LIMIT) {\n return undefined\n }\n\n const errors: [string, string][] = []\n\n for (const [name, message] of entries) {\n if (\n typeof message !== \"string\" ||\n message === \"\" ||\n message.length > FIELD_TEXT_LIMIT ||\n name.length > FIELD_TEXT_LIMIT\n ) {\n return undefined\n }\n\n errors.push([name, message])\n }\n\n return Object.fromEntries(errors)\n}\n\n/** What `readConsentFields` decided. */\ntype FieldsVerdict =\n | { readonly kind: \"accepted\"; readonly grantData: unknown }\n | {\n readonly kind: \"correct\"\n readonly fieldErrors: Readonly<Record<string, string>>\n }\n | { readonly kind: \"failed\" }\n\n/**\n * The client of a consent request, as hooks see it.\n *\n * @param consent - The consent request.\n * @returns The client id, kind and name.\n */\nfunction hookClient(consent: ConsentRequest): HookClient {\n return {\n clientId: consent.clientId,\n kind: consent.clientKind,\n ...(consent.clientName !== undefined\n ? { clientName: consent.clientName }\n : {}),\n }\n}\n\n/**\n * Run `readConsentFields`, if set, and validate its result.\n *\n * @param context - Context supplying the hook and `limits.grantDataBytes`.\n * @param consent - The verified consent request.\n * @param fields - The extra form fields.\n * @returns Accepted with grant data, a request to correct fields, or failed\n * (the hook threw or returned something unusable).\n */\nasync function readFields(\n context: HandlerContext,\n consent: ConsentRequest,\n fields: Readonly<Record<string, string>>,\n): Promise<FieldsVerdict> {\n const hook = context.options.readConsentFields\n\n if (hook === undefined) return { kind: \"accepted\", grantData: undefined }\n\n let result: unknown\n\n try {\n result = await hook(\n frozenCopy({\n form: { ...fields },\n details: consent.details,\n subject: consent.subject,\n client: hookClient(consent),\n scopes: [...consent.scopes],\n }),\n )\n } catch {\n return { kind: \"failed\" }\n }\n\n if (!isRecord(result)) return { kind: \"failed\" }\n\n switch (result.ok) {\n case true:\n try {\n return {\n kind: \"accepted\",\n grantData: jsonCopy(\n result.grantData,\n context.options.limits.grantDataBytes,\n \"readConsentFields\",\n ),\n }\n } catch {\n return { kind: \"failed\" }\n }\n case false: {\n const fieldErrors = readFieldErrors(result.fieldErrors)\n\n return fieldErrors === undefined\n ? { kind: \"failed\" }\n : { kind: \"correct\", fieldErrors }\n }\n default:\n return { kind: \"failed\" }\n }\n}\n\n/**\n * `POST /consent`: the person approves or declines.\n *\n * 1. The saved consent request is read, and the one-time form token and the\n * browser cookie are compared in constant time before anything else runs.\n * A mismatch, or an expired request, ends the flow and revokes the\n * upstream token.\n * 2. On approval, `readConsentFields` may ask for corrections: the page is\n * shown again and nothing is used up.\n * 3. The request is then taken from the store, once. A repeat of the same\n * submission from the same browser within `consentReplaySeconds` gets the\n * same redirect again, when the store supports it.\n * 4. `beforeGrant` may refuse; the grant and a one-time code are stored;\n * `afterGrant` runs and, if it throws, the grant is removed again.\n *\n * Every answer to the client is a 303 redirect (OAuth 2.1 \u00A7 7.5.4: a\n * redirect after a form `POST` must not repeat the `POST`) carrying `iss`\n * (RFC 9207). Every outcome without a grant revokes the upstream refresh\n * token obtained at sign-in.\n *\n * @param context - Context supplying the store, sealer, clock, lifetimes, hooks, cookie prefix and pages.\n * @param request - The `POST` from the consent page, with a form body and the binding cookie.\n * @returns A 303 redirect to the client, the consent page again, or an error page.\n * @internal\n */\nexport async function handleConsent(\n context: HandlerContext,\n request: Request,\n): Promise<Response> {\n const { options } = context\n\n if (!(await context.allowed(request, \"consent\"))) {\n return context.errorPage(request, \"rate_limited\")\n }\n\n // An extra check on top of the form token and the SameSite cookie. Pages\n // here use `Referrer-Policy: no-referrer`, under which browsers report\n // `Origin: null` for the form post; any concrete foreign origin means a\n // different site submitted the form.\n const origin = request.headers.get(\"origin\")\n\n if (origin !== null && origin !== \"null\" && origin !== options.issuerOrigin) {\n return context.errorPage(request, \"consent_unavailable\")\n }\n\n if (!hasContentType(request, FORM_MEDIA_TYPE)) {\n return context.errorPage(request, \"unsupported_submission\")\n }\n\n let form: Params\n\n try {\n form = groupParams(\n new URLSearchParams(await readBody(request, options.limits.formBytes)),\n )\n } catch (error) {\n if (error instanceof BodyTooLargeError) {\n return context.errorPage(request, \"consent_unavailable\")\n }\n\n throw error\n }\n\n if (repeatedDefined(form, RESERVED_FIELDS).length > 0) {\n return context.errorPage(request, \"consent_unavailable\")\n }\n\n const interactionId = single(form, CONSENT_FIELDS.id)\n const formToken = single(form, CONSENT_FIELDS.csrf)\n\n if (interactionId === undefined || formToken === undefined) {\n return context.errorPage(request, \"consent_unavailable\")\n }\n\n const { store, sealer } = options\n const cookieName = bindingCookieName(options.cookiePrefix, interactionId)\n const dropCookie = clearHostCookie(cookieName)\n const binding = readCookie(request, cookieName)\n const ended = () =>\n context.errorPage(request, \"consent_unavailable\", {\n setCookies: [dropCookie],\n })\n // Names one submission from one browser: only the same form token sent\n // with the same cookie can see the answer it got.\n const replayKey =\n binding === undefined\n ? undefined\n : sha256Hex(`${sha256Hex(binding)}:${sha256Hex(formToken)}`)\n const remembers =\n replayKey !== undefined &&\n options.consentReplayMs > 0 &&\n store.putConsentOutcome !== undefined &&\n store.getConsentOutcome !== undefined\n // Remember this submission's answer, or that it is still being decided,\n // for a repeat from the same browser. Sealed: the location may carry an\n // authorization code. The expiry travels inside, so a store that keeps it\n // longer changes nothing.\n // Best effort: it only serves a repeat, so a failed write must never fail\n // the submission itself (a repeat then sees the error page).\n const remember = async (answer: RememberedAnswer): Promise<void> => {\n if (!remembers) return\n\n try {\n const expiresAt = options.now() + options.consentReplayMs\n\n await store.putConsentOutcome?.(\n replayKey,\n await sealer.seal(JSON.stringify({ ...answer, expiresAt }), replayKey),\n expiresAt,\n )\n } catch {\n // Nothing to undo; see above.\n }\n }\n // The last value read while polling: an unchanged \"pending\" mark is not\n // opened (decrypted) again.\n let lastSealed: string | undefined\n let lastAnswer: RememberedAnswer | undefined\n let lastExpiresAt = 0\n const recall = async (): Promise<RememberedAnswer | undefined> => {\n if (!remembers) return undefined\n\n const sealed = await store.getConsentOutcome?.(replayKey)\n\n if (sealed === undefined) return undefined\n\n if (sealed === lastSealed && lastAnswer !== undefined) {\n return \"pending\" in lastAnswer &&\n !hasExpired(lastExpiresAt, options.now())\n ? lastAnswer\n : undefined\n }\n\n let outcome: unknown\n\n try {\n outcome = JSON.parse(await sealer.open(sealed, replayKey))\n } catch {\n return undefined\n }\n\n // The window is enforced here, never left to the store's expiry.\n if (\n !isRecord(outcome) ||\n typeof outcome.expiresAt !== \"number\" ||\n hasExpired(outcome.expiresAt, options.now())\n ) {\n return undefined\n }\n\n lastSealed = sealed\n lastExpiresAt = outcome.expiresAt\n lastAnswer =\n outcome.pending === true\n ? { pending: true }\n : typeof outcome.location === \"string\"\n ? { location: outcome.location }\n : undefined\n\n return lastAnswer\n }\n // A repeat of a submission this browser already made. While the first is\n // still being decided, wait for its answer within the replay window. An\n // unverified repeat (`verified: false`) waits only once the first has left\n // its \"pending\" mark, so a forged one ends at once; a repeat whose form\n // token and browser cookie were just verified also waits for a mark the\n // first has not written yet (both took the same request a moment apart).\n const replay = async (verified: boolean): Promise<Response> => {\n if (!remembers) return ended()\n\n const attempts = Math.ceil(options.consentReplayMs / REPLAY_POLL_MS)\n\n for (let attempt = 0; ; attempt += 1) {\n const answer = await recall()\n\n if (answer !== undefined && \"location\" in answer) {\n return redirect(answer.location, [dropCookie], 303)\n }\n\n if (\n (answer === undefined && !verified) ||\n attempt >= attempts ||\n request.signal.aborted\n ) {\n return ended()\n }\n\n await sleep(REPLAY_POLL_MS)\n }\n }\n const consent = await store.getConsentRequest(interactionId)\n\n if (consent === undefined) return replay(false)\n\n const releaseUpstream = () =>\n revokeSealedUpstreamToken(\n context,\n consent.sealedUpstreamRefreshToken,\n consent.grantId,\n )\n const abandon = async (): Promise<void> => {\n await store.consumeConsentRequest(interactionId)\n await releaseUpstream()\n }\n\n if (hasExpired(consent.expiresAt, options.now())) {\n await abandon()\n\n return ended()\n }\n\n const tokenOk = constantTimeEqual(sha256Hex(formToken), consent.csrfHash)\n const browserOk =\n binding !== undefined &&\n constantTimeEqual(sha256Hex(binding), consent.bindingHash)\n\n if (!tokenOk || !browserOk) {\n await context.emit({ type: \"flow_binding_mismatch\", step: \"consent\" })\n await abandon()\n\n return ended()\n }\n\n const approve = single(form, CONSENT_FIELDS.decision) === CONSENT_APPROVE\n let grantData: unknown\n\n if (approve) {\n const fields = extraFields(form)\n const verdict = await readFields(context, consent, fields)\n\n switch (verdict.kind) {\n case \"correct\":\n // Shown again; the request stays usable for the corrected answer.\n return consentPageResponse(context, request, consent, formToken, {\n status: 400,\n fieldErrors: verdict.fieldErrors,\n submitted: { ...fields },\n })\n case \"failed\":\n await abandon()\n\n return context.errorPage(request, \"server_error\", {\n setCookies: [dropCookie],\n })\n case \"accepted\":\n grantData = verdict.grantData\n break\n }\n }\n\n // Taking the request is the one atomic step: of two concurrent\n // submissions only one gets it, and the other is answered like the first.\n const taken = await store.consumeConsentRequest(interactionId)\n\n if (taken?.csrfHash !== consent.csrfHash) return replay(true)\n\n // From here the answer can take a while (`beforeGrant`, `afterGrant`):\n // mark it as being decided, so a double-click waits for it instead of\n // seeing an error page.\n await remember({ pending: true })\n\n const answer = async (\n params: Readonly<Record<string, string | undefined>>,\n ): Promise<Response> => {\n const location = buildRedirect(consent.redirectUri, {\n ...params,\n state: consent.state,\n iss: options.issuer,\n })\n\n await remember({ location })\n\n return redirect(location, [dropCookie], 303)\n }\n\n if (!approve) {\n await releaseUpstream()\n await context.emit({\n type: \"consent_declined\",\n clientId: consent.clientId,\n subject: consent.subject,\n })\n\n return answer({\n error: \"access_denied\",\n error_description: \"The person declined to grant access\",\n })\n }\n\n const before = options.beforeGrant\n\n if (before !== undefined) {\n const outcome = await decide(() =>\n before(\n Object.freeze({\n ...frozenCopy({\n subject: consent.subject,\n client: hookClient(consent),\n scopes: [...consent.scopes],\n grantData,\n }),\n request,\n }),\n ),\n )\n\n if (!outcome.allowed) {\n await releaseUpstream()\n await context.emit({\n type: \"authorization_refused\",\n clientId: consent.clientId,\n reason: outcome.reason ?? \"before_grant\",\n })\n\n return answer({\n error: \"access_denied\",\n error_description: \"Access was not granted\",\n })\n }\n }\n\n const now = options.now()\n const grant: GrantRecord = {\n grantId: consent.grantId,\n clientId: consent.clientId,\n subject: consent.subject,\n scopes: consent.scopes,\n resource: consent.resource,\n createdAt: now,\n expiresAt: initialGrantExpiry(options, now),\n redirectUri: consent.redirectUri,\n ...(grantData !== undefined ? { data: grantData } : {}),\n ...(consent.allowRefresh !== undefined\n ? { allowRefresh: consent.allowRefresh }\n : {}),\n ...(consent.clientName !== undefined\n ? { clientName: consent.clientName }\n : {}),\n ...(consent.displayName !== undefined\n ? { displayName: consent.displayName }\n : {}),\n ...(consent.sealedUpstreamRefreshToken !== undefined\n ? { sealedUpstreamRefreshToken: consent.sealedUpstreamRefreshToken }\n : {}),\n ...(consent.sealedUpstreamAccessToken !== undefined &&\n consent.upstreamAccessTokenExpiresAt !== undefined\n ? {\n sealedUpstreamAccessToken: consent.sealedUpstreamAccessToken,\n upstreamAccessTokenExpiresAt: consent.upstreamAccessTokenExpiresAt,\n }\n : {}),\n }\n\n await store.putGrant(grant)\n\n const code = randomToken()\n\n await store.putAuthorizationCode({\n codeHash: sha256Hex(code),\n grantId: grant.grantId,\n clientId: consent.clientId,\n redirectUri: consent.redirectUri,\n codeChallenge: consent.codeChallenge,\n resource: consent.resource,\n scopes: consent.scopes,\n expiresAt: now + options.codeTtlMs,\n })\n\n const after = options.afterGrant\n\n if (after !== undefined) {\n try {\n await after(Object.freeze({ grant: grantSummary(grant) }))\n } catch (error) {\n // No grant may outlive a failed `afterGrant`: deleting the grant\n // makes its code worthless too.\n await store.deleteGrant(grant.grantId)\n await releaseUpstream()\n await context.emit({\n type: \"unexpected_failure\",\n where: \"afterGrant\",\n errorName: error instanceof Error ? error.name : typeof error,\n })\n\n return answer({\n error: \"server_error\",\n error_description: \"The connection could not be completed\",\n })\n }\n }\n\n await context.emit({\n type: \"consent_approved\",\n grantId: grant.grantId,\n clientId: grant.clientId,\n subject: grant.subject,\n scopes: grant.scopes,\n })\n\n return answer({ code })\n}\n", "import { permits } from \"../core/policy.js\"\nimport { frozenCopy } from \"../helpers/freeze.js\"\nimport { BodyTooLargeError } from \"../errors/body-too-large-error.js\"\nimport { hasContentType, readBody } from \"../helpers/body.js\"\nimport {\n cleanClientName,\n parseClientScopes,\n parseGrantTypes,\n} from \"../helpers/client-fields.js\"\nimport { redirectHostOf, redirectUriProblem } from \"../helpers/redirect-uris.js\"\nimport { json, oauthError } from \"../helpers/responses.js\"\nimport { randomToken } from \"../helpers/tokens.js\"\nimport { isRecord, isStringArray } from \"../helpers/guards.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { ClientRecord } from \"../interfaces/records.js\"\n\nfunction badMetadata(description: string): Response {\n return oauthError(400, \"invalid_client_metadata\", description)\n}\n\nfunction badRedirect(description: string): Response {\n return oauthError(400, \"invalid_redirect_uri\", description)\n}\n\n/**\n * `POST /register`: RFC 7591 dynamic registration of a public client. The\n * response never contains a secret; metadata this server does not use is\n * accepted and discarded.\n *\n * @param context - Context supplying the store, supported scopes, limits and `allowRegistration` hook.\n * @param request - The `POST` to the registration endpoint, with a JSON body of client metadata.\n * @returns The registration response or an OAuth error.\n * @internal\n */\nexport async function handleRegister(\n context: HandlerContext,\n request: Request,\n): Promise<Response> {\n const { options } = context\n const { limits } = options\n\n if (!(await context.allowed(request, \"register\"))) {\n return oauthError(\n 429,\n \"temporarily_unavailable\",\n \"Registration rate exceeded; retry later\",\n )\n }\n\n if (!hasContentType(request, \"application/json\")) {\n return badMetadata(\"Send the client metadata as application/json\")\n }\n\n let body: unknown\n\n try {\n body = JSON.parse(await readBody(request, limits.registrationBytes))\n } catch (error) {\n return badMetadata(\n error instanceof BodyTooLargeError\n ? `The metadata exceeds ${limits.registrationBytes} bytes`\n : \"The body is not a JSON object\",\n )\n }\n\n if (!isRecord(body)) return badMetadata(\"The body is not a JSON object\")\n\n const uris = body.redirect_uris\n\n if (!isStringArray(uris) || uris.length === 0) {\n return badRedirect(\"List at least one redirect URI in redirect_uris\")\n }\n\n if (uris.length > limits.redirectUris) {\n return badRedirect(`Register at most ${limits.redirectUris} redirect URIs`)\n }\n\n for (const uri of uris) {\n const problem = redirectUriProblem(uri, limits)\n\n if (problem !== undefined) {\n return badRedirect(\n `Redirect URI refused (${problem}). Allowed: https URLs, or http on 127.0.0.1, [::1] or localhost`,\n )\n }\n }\n\n const authMethod = body.token_endpoint_auth_method\n\n if (authMethod !== undefined && authMethod !== \"none\") {\n return badMetadata(\n 'This server registers public clients only; token_endpoint_auth_method must be \"none\"',\n )\n }\n\n // Omitted grant_types means authorization_code alone (RFC 7591 \u00A7 2): a\n // client has to ask for refresh tokens explicitly.\n const grantTypes = parseGrantTypes(\n body.grant_types,\n true,\n options.defaultGrantTypes,\n )\n\n if (grantTypes === undefined) {\n return badMetadata(\n 'grant_types must include \"authorization_code\" and may add \"refresh_token\"; nothing else is offered',\n )\n }\n\n const responseTypes = body.response_types ?? [\"code\"]\n\n if (\n !isStringArray(responseTypes) ||\n responseTypes.length !== 1 ||\n responseTypes[0] !== \"code\"\n ) {\n return badMetadata('The only response type offered is \"code\"')\n }\n\n const scopes = parseClientScopes(\n body.scope,\n options.scopes,\n true,\n limits.scopeLength,\n )\n\n if (scopes === undefined) {\n return badMetadata(`Available scopes: ${options.scopes.join(\" \")}`)\n }\n\n let clientName: string | undefined\n\n if (body.client_name !== undefined) {\n clientName = cleanClientName(body.client_name, limits.clientNameLength)\n\n if (clientName === undefined) {\n return badMetadata(\n `client_name needs 1 to ${limits.clientNameLength} visible characters`,\n )\n }\n }\n\n const policy = options.allowRegistration\n\n if (\n policy !== undefined &&\n !(await permits(() => policy({ metadata: frozenCopy(body), request })))\n ) {\n return oauthError(\n 403,\n \"invalid_client_metadata\",\n \"This server does not accept the registration of this client\",\n )\n }\n\n const issuedAt = options.now()\n const redirectUris = [...new Set(uris)]\n const client: ClientRecord = {\n clientId: `client-${randomToken()}`,\n redirectUris,\n kind: \"dynamic\",\n createdAt: issuedAt,\n grantTypes,\n ...(scopes !== null ? { scopes } : {}),\n ...(clientName !== undefined ? { clientName } : {}),\n }\n\n await options.store.putClient(client)\n await context.emit({\n type: \"client_registered\",\n clientId: client.clientId,\n redirectHosts: [...new Set(redirectUris.map(redirectHostOf))],\n ...(clientName !== undefined ? { clientName } : {}),\n })\n\n return json(\n 201,\n {\n client_id: client.clientId,\n client_id_issued_at: Math.floor(issuedAt / 1000),\n redirect_uris: redirectUris,\n grant_types: grantTypes,\n response_types: [\"code\"],\n token_endpoint_auth_method: \"none\",\n ...(scopes !== null ? { scope: scopes.join(\" \") } : {}),\n ...(clientName !== undefined ? { client_name: clientName } : {}),\n },\n { cors: true },\n )\n}\n", "/**\n * A request an OAuth endpoint turns down, converted into an RFC 6749 \u00A7 5.2\n * JSON error in a single place.\n *\n * @internal\n */\nexport class EndpointRefusal extends Error {\n /**\n * @param error - The OAuth error code.\n * @param description - Human-readable `error_description`.\n * @param status - HTTP status. Default: 400.\n * @param challenge - `WWW-Authenticate` value to send with the error.\n * @param detail - Why, for the `token_request_refused` event only; never sent to the caller.\n */\n constructor(\n readonly error: string,\n readonly description: string,\n readonly status: number = 400,\n readonly challenge?: string,\n readonly detail?: string,\n ) {\n super(description)\n this.name = \"EndpointRefusal\"\n }\n}\n", "import { FORM_MEDIA_TYPE } from \"../constants/protocol.js\"\nimport { BodyTooLargeError } from \"../errors/body-too-large-error.js\"\nimport { EndpointRefusal } from \"../errors/endpoint-refusal.js\"\nimport { hasContentType, readBody } from \"../helpers/body.js\"\nimport {\n groupParams,\n type Params,\n repeatedDefined,\n single,\n} from \"../helpers/params.js\"\nimport { sameResource } from \"../helpers/resource.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\n\n/** HTTP auth-scheme syntax (RFC 9110 \u00A7 11.1, `token`). */\nconst AUTH_SCHEME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,32}$/\n\n/** Parameters every client-authenticating form defines. */\nconst CLIENT_PARAMETERS: readonly string[] = [\n \"client_id\",\n \"client_secret\",\n \"client_assertion\",\n \"client_assertion_type\",\n]\n\n/**\n * The scheme of an `Authorization` header, for echoing in a challenge.\n *\n * @param header - The header value.\n * @returns The scheme, or `Basic` when it is not a valid scheme token.\n */\nfunction schemeOf(header: string): string {\n const scheme = header.trim().split(\" \")[0] ?? \"\"\n\n return AUTH_SCHEME.test(scheme) ? scheme : \"Basic\"\n}\n\n/**\n * Parse the form body shared by the token and revocation endpoints and make\n * sure the caller acts as a public client.\n *\n * @param request - A POST to the token or revocation endpoint; its body is consumed.\n * @param maxBytes - Largest body accepted, in bytes (`limits.formBytes`).\n * @param defined - The parameters the endpoint defines; only these may not be repeated.\n * @returns The grouped form and the `client_id`, if sent.\n * @throws {EndpointRefusal} When the media type is wrong, the body is too large, a defined parameter is repeated, or client credentials are present.\n * @internal\n */\nexport async function readPublicClientForm(\n request: Request,\n maxBytes: number,\n defined: readonly string[],\n): Promise<{ form: Params; clientId: string | undefined }> {\n if (!hasContentType(request, FORM_MEDIA_TYPE)) {\n throw new EndpointRefusal(\n \"invalid_request\",\n \"Send the parameters as an application/x-www-form-urlencoded body\",\n )\n }\n\n let form: Params\n\n try {\n form = groupParams(new URLSearchParams(await readBody(request, maxBytes)))\n } catch (error) {\n if (error instanceof BodyTooLargeError) {\n throw new EndpointRefusal(\n \"invalid_request\",\n `The body exceeds ${maxBytes} bytes`,\n )\n }\n\n throw error\n }\n\n // RFC 6749 \u00A7 3.2 forbids repeating a defined parameter; unknown ones are\n // ignored, repeated or not.\n const repeated = repeatedDefined(form, [...CLIENT_PARAMETERS, ...defined])\n\n if (repeated.length > 0) {\n throw new EndpointRefusal(\n \"invalid_request\",\n `Parameter sent more than once: ${repeated.join(\", \")}`,\n )\n }\n\n // Credentials of any kind mean the client thinks it is confidential, which\n // no client of this server is. Saying so beats silently ignoring them.\n // RFC 6749 \u00A7 5.2: 401 with a matching `WWW-Authenticate` when the client\n // used the Authorization header; otherwise a plain 400 `invalid_client`.\n const authorization = request.headers.get(\"authorization\")\n const refusal =\n \"Clients here authenticate with PKCE alone; remove the client credentials\"\n\n if (authorization !== null) {\n throw new EndpointRefusal(\n \"invalid_client\",\n refusal,\n 401,\n schemeOf(authorization),\n )\n }\n\n if (form.has(\"client_secret\") || form.has(\"client_assertion\")) {\n throw new EndpointRefusal(\"invalid_client\", refusal)\n }\n\n return { form, clientId: single(form, \"client_id\") }\n}\n\n/**\n * An explicit `resource` must be this server's (RFC 8707).\n *\n * @param context - Context supplying the configured resource URL.\n * @param form - Parsed token-endpoint form; only its optional `resource` value is read.\n * @throws {EndpointRefusal} `invalid_target` when `resource` names another resource.\n * @internal\n */\nexport function requireOwnResource(\n context: HandlerContext,\n form: Params,\n): void {\n const resource = single(form, \"resource\")\n\n if (\n resource !== undefined &&\n !sameResource(context.options.resource, resource)\n ) {\n throw new EndpointRefusal(\n \"invalid_target\",\n `Tokens from this server are only for ${context.options.resource}`,\n )\n }\n}\n", "import { readPublicClientForm } from \"../core/client-form.js\"\nimport { EndpointRefusal } from \"../errors/endpoint-refusal.js\"\nimport { single } from \"../helpers/params.js\"\nimport { oauthError } from \"../helpers/responses.js\"\nimport { isOpaqueToken, sha256Hex } from \"../helpers/tokens.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { RevokeGrant } from \"../interfaces/routes.js\"\n\nconst DONE_HEADERS: Readonly<Record<string, string>> = {\n \"Cache-Control\": \"no-store\",\n Pragma: \"no-cache\",\n \"Access-Control-Allow-Origin\": \"*\",\n}\n\n/** Parameters the revocation endpoint defines (RFC 7009 \u00A7 2.1). */\nconst REVOKE_PARAMETERS: readonly string[] = [\"token\", \"token_type_hint\"]\n\n/**\n * `POST /revoke` (RFC 7009). The answer is `200` whether or not the token was\n * known, so the endpoint cannot be used to probe for tokens (\u00A7 2.2).\n *\n * A refresh token ends its whole grant. An access token is deleted on its\n * own. If the caller names a `client_id`, only that client's tokens are\n * affected.\n *\n * @param context - Context supplying the store and size limits.\n * @param request - The `POST` to the revocation endpoint, with a form body.\n * @param revoke - Ends a grant; called when a refresh token is revoked.\n * @returns A 200 response or an OAuth error.\n * @internal\n */\nexport async function handleRevoke(\n context: HandlerContext,\n request: Request,\n revoke: RevokeGrant,\n): Promise<Response> {\n const { store, limits } = context.options\n const done = () => new Response(null, { status: 200, headers: DONE_HEADERS })\n\n try {\n if (!(await context.allowed(request, \"revoke\"))) {\n return oauthError(\n 429,\n \"temporarily_unavailable\",\n \"Request rate exceeded; retry later\",\n )\n }\n\n const { form, clientId } = await readPublicClientForm(\n request,\n limits.formBytes,\n REVOKE_PARAMETERS,\n )\n const token = single(form, \"token\")\n\n if (token === undefined) {\n throw new EndpointRefusal(\"invalid_request\", \"Missing parameter: token\")\n }\n\n if (!isOpaqueToken(token)) return done()\n\n const hash = sha256Hex(token)\n const callerMayAct = (owner: string) =>\n clientId === undefined || clientId === owner\n const asRefreshToken = async () => {\n const found = await store.getRefreshToken(hash)\n\n if (found === undefined || !callerMayAct(found.clientId)) return false\n\n await revoke(found.grantId, \"revocation_request\")\n\n return true\n }\n const asAccessToken = async () => {\n const found = await store.getAccessToken(hash)\n\n if (found === undefined || !callerMayAct(found.clientId)) return false\n\n await store.deleteAccessToken(hash)\n\n return true\n }\n const order =\n single(form, \"token_type_hint\") === \"access_token\"\n ? [asAccessToken, asRefreshToken]\n : [asRefreshToken, asAccessToken]\n\n for (const attempt of order) {\n if (await attempt()) break\n }\n\n return done()\n } catch (error) {\n if (!(error instanceof EndpointRefusal)) throw error\n\n return oauthError(\n error.status,\n error.error,\n error.description,\n error.challenge,\n )\n }\n}\n", "import { EndpointRefusal } from \"../errors/endpoint-refusal.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { GrantRecord } from \"../interfaces/records.js\"\nimport { grantSummary } from \"./grant-summary.js\"\nimport { judge } from \"./policy.js\"\n\n/**\n * Run `beforeTokenIssue`, if set. It runs before a code or refresh token is\n * used up, so a refusal never strands the client.\n *\n * @param context - Context supplying the hook.\n * @param grant - The live grant tokens would be issued under.\n * @param grantType - Which token request this is.\n * @throws {EndpointRefusal} `invalid_grant` when the hook refuses, or\n * `temporarily_unavailable` (503) when it throws.\n * @internal\n */\nexport async function requireTokenIssue(\n context: HandlerContext,\n grant: GrantRecord,\n grantType: \"authorization_code\" | \"refresh_token\",\n): Promise<void> {\n const hook = context.options.beforeTokenIssue\n\n if (hook === undefined) return\n\n let verdict: unknown\n\n try {\n verdict = await hook(\n Object.freeze({ grant: grantSummary(grant), grantType }),\n )\n } catch {\n // A failing check is not a decision: the client may retry later with\n // the same, still unused, code or refresh token.\n throw new EndpointRefusal(\n \"temporarily_unavailable\",\n \"Tokens cannot be issued right now; retry later\",\n 503,\n )\n }\n\n if (!judge(verdict).allowed) {\n throw new EndpointRefusal(\n \"invalid_grant\",\n \"Tokens are not issued for this grant\",\n )\n }\n}\n", "import { EndpointRefusal } from \"../errors/endpoint-refusal.js\"\nimport { single, type Params } from \"../helpers/params.js\"\nimport { sameResource } from \"../helpers/resource.js\"\nimport { hasExpired } from \"../helpers/time.js\"\nimport { isOpaqueToken, pkceMatches, sha256Hex } from \"../helpers/tokens.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { RevokeGrant } from \"../interfaces/routes.js\"\nimport type { TokenResponse } from \"../interfaces/token-response.js\"\nimport { requireOwnResource } from \"./client-form.js\"\nimport { issueTokenPair, liveGrant } from \"./grants.js\"\nimport { requireTokenIssue } from \"./token-policy.js\"\n\n/**\n * Exchange an authorization code (OAuth 2.1 \u00A7 4.1.3).\n *\n * The code is first READ, and the client, `redirect_uri` (when sent),\n * `resource` (when sent) and PKCE verifier are compared with it. A mismatch\n * is refused without using the code up or touching the grant: whoever holds\n * a stolen code can neither spend it nor end the person's session by\n * guessing verifiers, and guessing one (256 bits for a 43-character\n * verifier, under the token endpoint's rate limit) is hopeless. Only a\n * fully matching request consumes the code, atomically: the first gets\n * tokens; a later, fully valid one is a replay and ends the grant\n * (OAuth 2.1 \u00A7 4.1.3, RFC 9700 \u00A7 4.5.3.1).\n *\n * @param context - Context supplying the store, clock, hooks and token lifetimes.\n * @param form - Parsed token-endpoint form, already checked for repeated parameters and client credentials.\n * @param clientId - The `client_id` sent with the request; for stored clients it was confirmed to exist.\n * @param revoke - Ends a grant; used when a code is replayed or presented after it expired.\n * @returns The RFC 6749 \u00A7 5.1 token response for the code's grant.\n * @throws {EndpointRefusal} When a parameter is missing, the code is unknown, replayed or expired, a value differs from the authorization, `beforeTokenIssue` refuses, or the grant is gone.\n * @internal\n */\nexport async function exchangeCode(\n context: HandlerContext,\n form: Params,\n clientId: string,\n revoke: RevokeGrant,\n): Promise<TokenResponse> {\n const { options } = context\n const code = single(form, \"code\")\n const verifier = single(form, \"code_verifier\")\n // Optional: OAuth 2.1 binds the code to the client and PKCE instead.\n const redirectUri = single(form, \"redirect_uri\")\n const missing = [\n code === undefined ? \"code\" : undefined,\n verifier === undefined ? \"code_verifier\" : undefined,\n ].filter((name) => name !== undefined)\n\n if (code === undefined || verifier === undefined) {\n throw new EndpointRefusal(\n \"invalid_request\",\n `Missing parameter: ${missing.join(\", \")}`,\n )\n }\n\n requireOwnResource(context, form)\n\n // One answer for an unknown, expired, replayed or mismatched code, so\n // the response says nothing about which check failed; the event does.\n const unusable = (detail: string) =>\n new EndpointRefusal(\n \"invalid_grant\",\n \"This authorization code cannot be exchanged\",\n 400,\n undefined,\n detail,\n )\n\n if (!isOpaqueToken(code)) throw unusable(\"code_malformed\")\n\n const codeHash = sha256Hex(code)\n const stored = await options.store.getAuthorizationCode(codeHash)\n\n if (stored === undefined) throw unusable(\"code_unknown\")\n\n const issued = stored.record\n const resource = single(form, \"resource\")\n // Checked in this order; the first that differs is reported.\n const comparisons: readonly (readonly [string, boolean])[] = [\n [\"client_id\", issued.clientId !== clientId],\n [\n \"redirect_uri\",\n redirectUri !== undefined && issued.redirectUri !== redirectUri,\n ],\n [\n \"resource\",\n resource !== undefined && !sameResource(issued.resource, resource),\n ],\n [\"code_verifier\", !pkceMatches(verifier, issued.codeChallenge)],\n ]\n const differs = comparisons.find(([, mismatch]) => mismatch)?.[0]\n\n if (differs !== undefined) {\n throw unusable(`${differs}_mismatch`)\n }\n\n const replayed = async () => {\n // A second fully valid redemption means someone else holds the code\n // and its verifier too: undo everything it led to.\n await context.emit({\n type: \"authorization_code_replayed\",\n grantId: issued.grantId,\n clientId: issued.clientId,\n })\n await revoke(issued.grantId, \"authorization_code_replay\")\n\n return unusable(\"code_replayed\")\n }\n\n if (stored.consumed) throw await replayed()\n\n if (hasExpired(issued.expiresAt, options.now())) {\n await revoke(issued.grantId, \"authorization_code_expired\")\n\n throw unusable(\"code_expired\")\n }\n\n const grant = await liveGrant(context, issued.grantId, clientId)\n\n if (grant === undefined) {\n throw new EndpointRefusal(\"invalid_grant\", \"Access has been withdrawn\")\n }\n\n await requireTokenIssue(context, grant, \"authorization_code\")\n\n const redeemed = await options.store.consumeAuthorizationCode(codeHash)\n\n if (redeemed === undefined) throw unusable(\"code_unknown\")\n\n if (!redeemed.firstUse) throw await replayed()\n\n const tokens = await issueTokenPair(context, grant, issued.scopes)\n\n await context.emit({\n type: \"token_issued\",\n grantId: grant.grantId,\n clientId,\n subject: grant.subject,\n grantType: \"authorization_code\",\n })\n\n return tokens\n}\n", "import { EndpointRefusal } from \"../errors/endpoint-refusal.js\"\nimport { isRecord } from \"../helpers/guards.js\"\nimport { single, type Params } from \"../helpers/params.js\"\nimport { isOpaqueToken, sha256Hex } from \"../helpers/tokens.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { RefreshTokenRecord } from \"../interfaces/records.js\"\nimport type { RevokeGrant } from \"../interfaces/routes.js\"\nimport type { TokenResponse } from \"../interfaces/token-response.js\"\nimport { requireOwnResource } from \"./client-form.js\"\nimport { issueTokenPair, liveGrant } from \"./grants.js\"\nimport { requireTokenIssue } from \"./token-policy.js\"\nimport { hasExpired } from \"../helpers/time.js\"\nimport { slidGrantExpiry } from \"./grant-expiry.js\"\n\n/**\n * Exchange a refresh token for a new access token and a rotated refresh\n * token (OAuth 2.1 \u00A7 4.3).\n *\n * @param context - Context supplying the store, clock, retry window, hooks and token lifetimes.\n * @param form - Parsed token-endpoint form, already checked for repeated parameters and client credentials.\n * @param clientId - The `client_id` sent with the request; the refresh token must have been issued to it.\n * @param revoke - Ends a grant; used when a spent refresh token is presented again outside the retry window, or after the pair it was exchanged for has been used.\n * @returns The RFC 6749 \u00A7 5.1 token response with a rotated refresh token; for a retry inside the window, the pair the first exchange issued (or, with a store that keeps no replacements, an access token only).\n * @throws {EndpointRefusal} When the token is missing, unknown, expired, another client's, replayed, asks for wider scopes, or its grant is gone.\n * @internal\n */\nexport async function exchangeRefreshToken(\n context: HandlerContext,\n form: Params,\n clientId: string,\n revoke: RevokeGrant,\n): Promise<TokenResponse> {\n const { options } = context\n const presented = single(form, \"refresh_token\")\n\n if (presented === undefined) {\n throw new EndpointRefusal(\n \"invalid_request\",\n \"Missing parameter: refresh_token\",\n )\n }\n\n requireOwnResource(context, form)\n\n const unusable = () =>\n new EndpointRefusal(\"invalid_grant\", \"This refresh token cannot be used\")\n\n if (!isOpaqueToken(presented)) throw unusable()\n\n const hash = sha256Hex(presented)\n const now = options.now()\n // Look before marking: a different client presenting the token must not\n // be able to use it up.\n const known = await options.store.getRefreshToken(hash)\n\n if (\n known === undefined ||\n known.clientId !== clientId ||\n hasExpired(known.expiresAt, now)\n ) {\n throw unusable()\n }\n\n let scopes = known.scopes\n const narrowed = single(form, \"scope\")\n\n if (narrowed !== undefined) {\n const asked = [...new Set(narrowed.split(\" \").filter((s) => s.length > 0))]\n\n if (asked.length === 0 || asked.some((s) => !known.scopes.includes(s))) {\n throw new EndpointRefusal(\n \"invalid_scope\",\n \"A refreshed token can only carry scopes already granted\",\n )\n }\n\n scopes = asked\n }\n\n // Checked before the token is marked used, so a refusal or a failing\n // check leaves it usable.\n const current = await liveGrant(context, known.grantId, clientId)\n\n if (current === undefined || current.allowRefresh === false) {\n throw new EndpointRefusal(\"invalid_grant\", \"Access has been withdrawn\")\n }\n\n await requireTokenIssue(context, current, \"refresh_token\")\n\n const marked = await options.store.useRefreshToken(hash, now)\n\n if (marked === undefined) throw unusable()\n\n const earlier = marked.previousUsedAt\n\n if (earlier !== undefined) {\n const withinRetryWindow =\n options.refreshReuseGraceMs > 0 &&\n now - earlier <= options.refreshReuseGraceMs\n\n if (!withinRetryWindow) {\n // Presented again after it was spent, outside the retry window: assume\n // it was copied and withdraw everything issued under it (OAuth 2.1 \u00A7\n // 4.3.1, RFC 9700 \u00A7 4.14).\n await endReplayedGrant(context, marked.record.grantId, clientId, revoke)\n\n throw unusable()\n }\n\n return answerRetry(context, marked.record, scopes, clientId, revoke)\n }\n\n const grant = await liveGrant(context, marked.record.grantId, clientId)\n\n if (grant === undefined || grant.allowRefresh === false) {\n throw new EndpointRefusal(\"invalid_grant\", \"Access has been withdrawn\")\n }\n\n // Under `lifetimes.grantIdle` a renewal extends the grant, never past its\n // absolute limit. The tokens below follow the new expiry only when the\n // store accepted the update; every later check reads the stored grant, so\n // a store that ignored `expiresAt` still ends the grant on time.\n const slid = slidGrantExpiry(options, grant, now)\n const renewed =\n slid !== undefined &&\n (await options.store.updateGrant(grant.grantId, { expiresAt: slid }))\n ? { ...grant, expiresAt: slid }\n : grant\n\n // A narrowed request narrows only this access token; the new refresh\n // token keeps the scopes it replaces (OAuth 2.1 \u00A7 4.3.1, RFC 6749 \u00A7 6).\n const tokens = await issueTokenPair(context, renewed, scopes, known.scopes)\n\n await keepReplacement(context, marked.record, tokens, now)\n\n await context.emit({\n type: \"token_issued\",\n grantId: grant.grantId,\n clientId,\n subject: grant.subject,\n grantType: \"refresh_token\",\n })\n\n return tokens\n}\n\n/**\n * The pair a refresh exchange issued, kept encrypted on the spent refresh\n * token so a retry inside the window receives exactly the same pair.\n */\ninterface IssuedReplacement {\n readonly accessToken: string\n readonly refreshToken: string\n readonly scope: string\n readonly accessExpiresAt: number\n}\n\n/**\n * Additional authenticated data binding a sealed replacement to the spent\n * refresh token it was issued for, so it cannot be moved to another record.\n *\n * @param spent - The spent refresh token record.\n * @returns The `aad` string for the sealer.\n */\nfunction replacementAad(spent: RefreshTokenRecord): string {\n return `${spent.grantId}#refresh-replacement#${spent.tokenHash}`\n}\n\n/**\n * Read a decrypted replacement, refusing anything that is not one.\n *\n * @param plaintext - The opened value.\n * @returns The replacement, or `undefined` when the value is malformed.\n */\nfunction parseReplacement(plaintext: string): IssuedReplacement | undefined {\n let value: unknown\n\n try {\n value = JSON.parse(plaintext)\n } catch {\n return undefined\n }\n\n if (\n !isRecord(value) ||\n typeof value.accessToken !== \"string\" ||\n typeof value.refreshToken !== \"string\" ||\n typeof value.scope !== \"string\" ||\n typeof value.accessExpiresAt !== \"number\"\n ) {\n return undefined\n }\n\n return {\n accessToken: value.accessToken,\n refreshToken: value.refreshToken,\n scope: value.scope,\n accessExpiresAt: value.accessExpiresAt,\n }\n}\n\n/**\n * Keep the pair just issued for a refresh token, encrypted on the spent\n * token's record, when the store implements `putRefreshTokenReplacement`.\n *\n * @param context - Context supplying the store and sealer.\n * @param spent - The refresh token this exchange used up.\n * @param tokens - The response being returned.\n * @param now - The time the exchange started.\n */\nasync function keepReplacement(\n context: HandlerContext,\n spent: RefreshTokenRecord,\n tokens: TokenResponse,\n now: number,\n): Promise<void> {\n const { store, sealer } = context.options\n\n if (\n store.putRefreshTokenReplacement === undefined ||\n tokens.refresh_token === undefined\n ) {\n return\n }\n\n const replacement: IssuedReplacement = {\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n scope: tokens.scope,\n accessExpiresAt: now + tokens.expires_in * 1000,\n }\n\n await store.putRefreshTokenReplacement(\n spent.tokenHash,\n await sealer.seal(JSON.stringify(replacement), replacementAad(spent)),\n )\n}\n\n/**\n * Report a replayed refresh token and end its grant.\n *\n * @param context - Context supplying the event hook.\n * @param grantId - The grant the replayed token belongs to.\n * @param clientId - The client presenting it.\n * @param revoke - Ends the grant.\n */\nasync function endReplayedGrant(\n context: HandlerContext,\n grantId: string,\n clientId: string,\n revoke: RevokeGrant,\n): Promise<void> {\n await context.emit({ type: \"refresh_token_replayed\", grantId, clientId })\n await revoke(grantId, \"refresh_token_replay\")\n}\n\n/**\n * Answer a spent refresh token presented again inside the retry window\n * without ever starting a second token chain.\n *\n * With a store that keeps replacements, the retry receives the very pair the\n * first exchange issued, so whoever else holds the token shares that one\n * chain: once either holder rotates it, the other's next use is a replay and\n * the grant ends. A retry after the replacement was itself used is already\n * that replay. Without `putRefreshTokenReplacement`, the retry receives an\n * access token only.\n *\n * @param context - Context supplying the store, sealer and token lifetimes.\n * @param spent - The spent refresh token record.\n * @param scopes - Scopes for a new access token (store without replacements only).\n * @param clientId - The client presenting the token.\n * @param revoke - Ends the grant when the retry turns out to be a replay.\n * @returns The original pair, or a new access token without a refresh token.\n * @throws {EndpointRefusal} `temporarily_unavailable` while the first exchange is still storing its pair; `invalid_grant` when the pair cannot be replayed.\n */\nasync function answerRetry(\n context: HandlerContext,\n spent: RefreshTokenRecord,\n scopes: readonly string[],\n clientId: string,\n revoke: RevokeGrant,\n): Promise<TokenResponse> {\n const { options } = context\n const unusable = () =>\n new EndpointRefusal(\"invalid_grant\", \"This refresh token cannot be used\")\n\n if (options.store.putRefreshTokenReplacement === undefined) {\n const grant = await liveGrant(context, spent.grantId, clientId)\n\n if (grant === undefined) {\n throw new EndpointRefusal(\"invalid_grant\", \"Access has been withdrawn\")\n }\n\n // No refresh token: a second refresh token here would be an independent\n // chain that replay detection could never connect to the first.\n const tokens = await issueTokenPair(\n context,\n { ...grant, allowRefresh: false },\n scopes,\n )\n\n await context.emit({\n type: \"token_issued\",\n grantId: grant.grantId,\n clientId,\n subject: grant.subject,\n grantType: \"refresh_token\",\n })\n\n return tokens\n }\n\n if (spent.sealedReplacement === undefined) {\n // The first exchange has marked the token but not stored its pair yet.\n throw new EndpointRefusal(\n \"temporarily_unavailable\",\n \"This refresh token is being exchanged; retry shortly\",\n 503,\n )\n }\n\n const replacement = parseReplacement(\n await options.sealer.open(spent.sealedReplacement, replacementAad(spent)),\n )\n\n if (replacement === undefined) throw unusable()\n\n const next = await options.store.getRefreshToken(\n sha256Hex(replacement.refreshToken),\n )\n\n if (next?.usedAt !== undefined) {\n // The pair already moved on: someone else holds this token too.\n await endReplayedGrant(context, spent.grantId, clientId, revoke)\n\n throw unusable()\n }\n\n if (next === undefined) throw unusable()\n\n return {\n access_token: replacement.accessToken,\n token_type: \"Bearer\",\n expires_in: Math.max(\n 0,\n Math.floor((replacement.accessExpiresAt - options.now()) / 1000),\n ),\n scope: replacement.scope,\n refresh_token: replacement.refreshToken,\n }\n}\n", "import { readPublicClientForm } from \"../core/client-form.js\"\nimport { resolveClient } from \"../core/clients.js\"\nimport { exchangeCode } from \"../core/code-exchange.js\"\nimport { exchangeRefreshToken } from \"../core/refresh-exchange.js\"\nimport { EndpointRefusal } from \"../errors/endpoint-refusal.js\"\nimport { isMetadataDocumentClientId } from \"../helpers/client-fields.js\"\nimport { single } from \"../helpers/params.js\"\nimport { json, oauthError } from \"../helpers/responses.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { RevokeGrant } from \"../interfaces/routes.js\"\nimport type { TokenResponse } from \"../interfaces/token-response.js\"\n\n/** Parameters the token endpoint defines (besides client authentication). */\nconst TOKEN_PARAMETERS: readonly string[] = [\n \"grant_type\",\n \"code\",\n \"redirect_uri\",\n \"code_verifier\",\n \"refresh_token\",\n \"scope\",\n \"resource\",\n]\n\n/**\n * `POST /token`. Callers identify themselves by `client_id` alone and prove\n * possession with PKCE; codes and refresh tokens are exchanged here. Errors use\n * the RFC 6749 \u00A7 5.2 format. `invalid_client` is a 400, except a 401 with a\n * `WWW-Authenticate` challenge in the scheme the client used when it sent an\n * `Authorization` header (RFC 6749 \u00A7 5.2).\n *\n * @param context - Context supplying the store, clients, clock and token settings.\n * @param request - The `POST` to the token endpoint, with a form body.\n * @param revoke - Ends a grant; used for code and refresh-token replay and mismatches.\n * @returns The token response or an OAuth error.\n * @internal\n */\nexport async function handleToken(\n context: HandlerContext,\n request: Request,\n revoke: RevokeGrant,\n): Promise<Response> {\n let clientId: string | undefined\n\n try {\n if (!(await context.allowed(request, \"token\"))) {\n return oauthError(\n 429,\n \"temporarily_unavailable\",\n \"Request rate exceeded; retry later\",\n )\n }\n\n const parsed = await readPublicClientForm(\n request,\n context.options.limits.formBytes,\n TOKEN_PARAMETERS,\n )\n\n clientId = parsed.clientId\n\n if (clientId === undefined) {\n throw new EndpointRefusal(\n \"invalid_client\",\n \"Missing parameter: client_id\",\n )\n }\n\n // Registered clients must still exist. A metadata-document client was\n // checked at /authorize, and its code and tokens are tied to it.\n const byUrl = isMetadataDocumentClientId(clientId)\n const client = byUrl ? undefined : await resolveClient(context, clientId)\n\n if (!byUrl && client === undefined) {\n throw new EndpointRefusal(\n \"invalid_client\",\n \"No client is registered under this client_id\",\n )\n }\n\n const grantType = single(parsed.form, \"grant_type\")\n\n if (\n grantType === \"refresh_token\" &&\n client?.grantTypes !== undefined &&\n !client.grantTypes.includes(\"refresh_token\")\n ) {\n throw new EndpointRefusal(\n \"unauthorized_client\",\n \"refresh_token is not among this client's registered grant_types\",\n )\n }\n\n let tokens: TokenResponse\n\n switch (grantType) {\n case \"authorization_code\":\n tokens = await exchangeCode(context, parsed.form, clientId, revoke)\n break\n case \"refresh_token\":\n tokens = await exchangeRefreshToken(\n context,\n parsed.form,\n clientId,\n revoke,\n )\n break\n case undefined:\n throw new EndpointRefusal(\n \"invalid_request\",\n \"Missing parameter: grant_type\",\n )\n default:\n throw new EndpointRefusal(\n \"unsupported_grant_type\",\n \"This server only exchanges authorization codes and refresh tokens\",\n )\n }\n\n return json(200, tokens, { cors: true })\n } catch (error) {\n if (!(error instanceof EndpointRefusal)) throw error\n\n await context.emit({\n type: \"token_request_refused\",\n error: error.error,\n reason: error.detail ?? error.description,\n ...(clientId !== undefined ? { clientId } : {}),\n })\n\n return oauthError(\n error.status,\n error.error,\n error.description,\n error.challenge,\n )\n }\n}\n", "import type { ResolvedOptions } from \"../interfaces/options.js\"\n\n/**\n * RFC 8414 authorization server metadata.\n *\n * @param options - Checked options; supplies the issuer, endpoint paths, scopes and enabled features.\n * @returns The RFC 8414 document, serialized as JSON by the caller.\n * @internal\n */\nexport function authorizationServerMetadata(\n options: ResolvedOptions,\n): Record<string, unknown> {\n const at = (path: string) => `${options.issuerOrigin}${path}`\n\n return {\n issuer: options.issuer,\n authorization_endpoint: at(options.paths.authorize),\n token_endpoint: at(options.paths.token),\n ...(options.dynamicClientRegistration\n ? { registration_endpoint: at(options.paths.register) }\n : {}),\n revocation_endpoint: at(options.paths.revoke),\n scopes_supported: options.scopes,\n response_types_supported: [\"code\"],\n response_modes_supported: [\"query\"],\n grant_types_supported: [\"authorization_code\", \"refresh_token\"],\n code_challenge_methods_supported: [\"S256\"],\n token_endpoint_auth_methods_supported: [\"none\"],\n revocation_endpoint_auth_methods_supported: [\"none\"],\n client_id_metadata_document_supported: options.clientIdMetadataDocuments,\n authorization_response_iss_parameter_supported: true,\n }\n}\n\n/**\n * RFC 9728 protected resource metadata.\n *\n * @param options - Checked options; supplies the resource, issuer, scopes and resource name.\n * @returns The RFC 9728 document, serialized as JSON by the caller.\n * @internal\n */\nexport function protectedResourceMetadata(\n options: ResolvedOptions,\n): Record<string, unknown> {\n return {\n resource: options.resource,\n authorization_servers: [options.issuer],\n scopes_supported: options.scopes,\n bearer_methods_supported: [\"header\"],\n ...(options.resourceName !== undefined\n ? { resource_name: options.resourceName }\n : {}),\n }\n}\n", "import { handleAuthorize } from \"../handlers/authorize.js\"\nimport { handleCallback } from \"../handlers/callback.js\"\nimport { handleConsent } from \"../handlers/consent.js\"\nimport { handleRegister } from \"../handlers/register.js\"\nimport { handleRevoke } from \"../handlers/revoke.js\"\nimport { handleToken } from \"../handlers/token.js\"\nimport { json, oauthError } from \"../helpers/responses.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { RevokeGrant, RouteEntry } from \"../interfaces/routes.js\"\nimport {\n authorizationServerMetadata,\n protectedResourceMetadata,\n} from \"./metadata.js\"\n\n/**\n * Run a handler, turning any unexpected error into a generic response and\n * an `unexpected_failure` event, so no internal detail reaches the client.\n *\n * @param context - Context used to emit `unexpected_failure` when the handler throws.\n * @param where - Handler name for the event.\n * @param run - The handler.\n * @param fail - Builds the generic response.\n * @returns The handler's response, or the generic one.\n */\nasync function guarded(\n context: HandlerContext,\n where: string,\n run: () => Promise<Response>,\n fail: () => Response,\n): Promise<Response> {\n try {\n return await run()\n } catch (error) {\n await context.emit({\n type: \"unexpected_failure\",\n where,\n errorName: error instanceof Error ? error.name : \"unknown\",\n })\n\n return fail()\n }\n}\n\n/**\n * Build the route table: metadata documents, registration (when enabled),\n * authorization, callback, consent, token and revocation. When two paths\n * coincide, the first entry wins.\n *\n * @param context - Context with the checked options; paths, enabled endpoints and metadata come from it.\n * @param revoke - Ends a grant with a reason; passed to the token and revocation handlers for replay and revocation.\n * @returns Routes by exact path.\n * @internal\n */\nexport function buildRoutes(\n context: HandlerContext,\n revoke: RevokeGrant,\n): ReadonlyMap<string, RouteEntry> {\n const { options } = context\n const serverError = () =>\n oauthError(\n 500,\n \"server_error\",\n \"An unexpected condition stopped the request\",\n )\n const pageError = (request: Request) =>\n context.errorPage(request, \"server_error\")\n const routes = new Map<string, RouteEntry>()\n const add = (path: string, entry: RouteEntry) => {\n if (!routes.has(path)) routes.set(path, entry)\n }\n const document = (body: Record<string, unknown>): RouteEntry => ({\n methods: [\"GET\"],\n cors: true,\n run: () =>\n Promise.resolve(json(200, body, { cors: true, cacheable: true })),\n })\n\n for (const path of options.resourceMetadataPaths) {\n add(path, document(protectedResourceMetadata(options)))\n }\n\n for (const path of options.authorizationServerMetadataPaths) {\n add(path, document(authorizationServerMetadata(options)))\n }\n\n if (options.dynamicClientRegistration) {\n add(options.paths.register, {\n methods: [\"POST\"],\n cors: true,\n run: (request) =>\n guarded(\n context,\n \"register\",\n () => handleRegister(context, request),\n serverError,\n ),\n })\n }\n\n add(options.paths.authorize, {\n methods: [\"GET\"],\n cors: false,\n run: (request) =>\n guarded(\n context,\n \"authorize\",\n () => handleAuthorize(context, request),\n () => pageError(request),\n ),\n })\n add(options.paths.callback, {\n methods: [\"GET\"],\n cors: false,\n run: (request) =>\n guarded(\n context,\n \"callback\",\n () => handleCallback(context, request),\n () => pageError(request),\n ),\n })\n add(options.paths.consent, {\n methods: [\"POST\"],\n cors: false,\n run: (request) =>\n guarded(\n context,\n \"consent\",\n () => handleConsent(context, request),\n () => pageError(request),\n ),\n })\n add(options.paths.token, {\n methods: [\"POST\"],\n cors: true,\n run: (request) =>\n guarded(\n context,\n \"token\",\n () => handleToken(context, request, revoke),\n serverError,\n ),\n })\n add(options.paths.revoke, {\n methods: [\"POST\"],\n cors: true,\n run: (request) =>\n guarded(\n context,\n \"revoke\",\n () => handleRevoke(context, request, revoke),\n serverError,\n ),\n })\n\n return routes\n}\n", "/**\n * The grant behind a token has been removed or has expired, so no upstream\n * token can be produced for it.\n *\n * @example\n * ```ts\n * import { type AuthSuccess, type McpAuth, GrantInactiveError } from \"@gydr-ai/mcp-oauth\"\n *\n * export async function upstreamOrChallenge(\n * auth: McpAuth,\n * request: Request,\n * result: AuthSuccess,\n * ): Promise<string | Response> {\n * try {\n * return await result.getUpstreamAccessToken()\n * } catch (error) {\n * if (error instanceof GrantInactiveError) return auth.unauthorized(request)\n * throw error\n * }\n * }\n * ```\n */\nexport class GrantInactiveError extends Error {\n /** Creates the error with a fixed message. */\n constructor() {\n super(\"The grant this token belonged to has been removed\")\n this.name = \"GrantInactiveError\"\n }\n}\n", "/**\n * No upstream access token could be obtained for a grant. The message never\n * contains a token.\n *\n * @example\n * ```ts\n * import { type AuthSuccess, UpstreamTokenError } from \"@gydr-ai/mcp-oauth\"\n *\n * export async function upstreamToken(result: AuthSuccess): Promise<string | undefined> {\n * try {\n * return await result.getUpstreamAccessToken()\n * } catch (error) {\n * if (error instanceof UpstreamTokenError) return undefined\n * throw error\n * }\n * }\n * ```\n */\nexport class UpstreamTokenError extends Error {\n /**\n * @param message - What failed.\n * @param options - Standard error options, such as `cause`.\n * @param options.cause - The error that caused the failure, such as the provider's rejection.\n */\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options)\n this.name = \"UpstreamTokenError\"\n }\n}\n", "import {\n UPSTREAM_CACHE_GRANTS,\n UPSTREAM_EXPIRY_SKEW_MS,\n UPSTREAM_LIFETIME_CEILING_SECONDS,\n} from \"../constants/defaults.js\"\nimport { GrantInactiveError } from \"../errors/grant-inactive-error.js\"\nimport { UpstreamTokenError } from \"../errors/upstream-token-error.js\"\nimport type { HandlerContext } from \"../interfaces/context.js\"\nimport type { GrantRecord } from \"../interfaces/records.js\"\nimport { hasExpired } from \"../helpers/time.js\"\n\n/** A load of the upstream token for one grant that is under way. */\ninterface Flight {\n readonly promise: Promise<string>\n /** Whether it renews at the identity provider whatever is cached. */\n readonly forced: boolean\n}\n\n/** An upstream access token held in memory. */\ninterface CachedToken {\n readonly token: string\n readonly expiresAt: number\n}\n\n/**\n * Supplies an upstream access token per grant, trying in turn: this\n * process's memory, the sealed copy kept on the grant, and finally a renewal\n * at the identity provider. While a renewal for a grant is under way, other\n * callers for that grant wait for it instead of starting their own, so an\n * identity provider that rotates refresh tokens never sees one used twice\n * from here.\n *\n * @internal\n */\nexport class UpstreamTokenCache {\n readonly #context: HandlerContext\n readonly #cache = new Map<string, CachedToken>()\n readonly #inflight = new Map<string, Flight>()\n /** Grants being revoked in this process, oldest first (bounded). */\n readonly #revoking = new Set<string>()\n\n /**\n *\n * @param context - Context of the owning `createMcpAuth` instance; supplies the store, sealer, identity provider and clock.\n */\n constructor(context: HandlerContext) {\n this.#context = context\n }\n\n /**\n * Forget a grant that is being revoked. Its cached upstream access token\n * is dropped, and a renewal for it that is under way revokes the token\n * it obtains instead of storing it.\n *\n * @param grantId - Id of the grant being revoked; unknown ids are ignored.\n */\n forget(grantId: string): void {\n this.#cache.delete(grantId)\n\n if (this.#revoking.size >= UPSTREAM_CACHE_GRANTS) {\n const oldest = this.#revoking.values().next()\n\n if (oldest.done !== true) this.#revoking.delete(oldest.value)\n }\n\n this.#revoking.add(grantId)\n }\n\n /**\n * A valid upstream access token for the grant.\n *\n * @param grantId - Id of a grant already confirmed live by `authenticate`; it is looked up again before any renewal.\n * @param forceRefresh - Skip the memory and stored copies and renew at the identity provider. Joins a renewal already under way rather than starting a second.\n * @returns The upstream access token, valid for at least another minute.\n */\n get(grantId: string, forceRefresh = false): Promise<string> {\n const running = this.#inflight.get(grantId)\n\n if (!forceRefresh) {\n const now = this.#context.options.now()\n const cached = this.#cache.get(grantId)\n\n if (\n cached !== undefined &&\n !hasExpired(cached.expiresAt - UPSTREAM_EXPIRY_SKEW_MS, now)\n ) {\n return Promise.resolve(cached.token)\n }\n\n if (running !== undefined) return running.promise\n\n return this.#start(grantId, false, this.#load(grantId))\n }\n\n if (running?.forced === true) return running.promise\n\n // A plain load under way may hand back the very token the caller wants\n // replaced: let it settle (so one refresh token is never used twice at\n // once), then renew.\n const after =\n running === undefined\n ? this.#renew(grantId)\n : running.promise.then(\n () => this.#renew(grantId),\n () => this.#renew(grantId),\n )\n\n return this.#start(grantId, true, after)\n }\n\n #start(\n grantId: string,\n forced: boolean,\n work: Promise<string>,\n ): Promise<string> {\n const promise = work.finally(() => {\n if (this.#inflight.get(grantId)?.promise === promise) {\n this.#inflight.delete(grantId)\n }\n })\n\n this.#inflight.set(grantId, { promise, forced })\n\n return promise\n }\n\n /**\n * Drop every cached copy of the grant's upstream access token, then renew\n * it at the identity provider.\n *\n * @param grantId - The grant.\n * @returns The new upstream access token.\n */\n async #renew(grantId: string): Promise<string> {\n const { options } = this.#context\n\n this.#cache.delete(grantId)\n\n const grant = await options.store.getGrant(grantId)\n\n if (grant === undefined || hasExpired(grant.expiresAt, options.now())) {\n throw new GrantInactiveError()\n }\n\n // An expiry of 0 makes the stored copy unusable even if the renewal\n // below fails.\n if (grant.sealedUpstreamAccessToken !== undefined) {\n await options.store.updateGrant(grantId, {\n upstreamAccessTokenExpiresAt: 0,\n })\n }\n\n return this.#refresh(grant)\n }\n\n #remember(grantId: string, token: CachedToken): void {\n if (\n this.#cache.size >= UPSTREAM_CACHE_GRANTS &&\n !this.#cache.has(grantId)\n ) {\n const oldest = this.#cache.keys().next()\n\n if (oldest.done !== true) this.#cache.delete(oldest.value)\n }\n\n this.#cache.delete(grantId)\n this.#cache.set(grantId, token)\n }\n\n async #load(grantId: string): Promise<string> {\n const { options } = this.#context\n const grant = await options.store.getGrant(grantId)\n\n if (grant === undefined || hasExpired(grant.expiresAt, options.now())) {\n throw new GrantInactiveError()\n }\n\n const stored = await this.#fromGrant(grant)\n\n if (stored !== undefined) return stored\n\n return this.#refresh(grant)\n }\n\n async #fromGrant(grant: GrantRecord): Promise<string | undefined> {\n const { options } = this.#context\n\n if (\n grant.sealedUpstreamAccessToken === undefined ||\n grant.upstreamAccessTokenExpiresAt === undefined ||\n hasExpired(\n grant.upstreamAccessTokenExpiresAt - UPSTREAM_EXPIRY_SKEW_MS,\n options.now(),\n )\n ) {\n return undefined\n }\n\n try {\n const token = await options.sealer.open(\n grant.sealedUpstreamAccessToken,\n `${grant.grantId}#access`,\n )\n\n this.#remember(grant.grantId, {\n token,\n expiresAt: grant.upstreamAccessTokenExpiresAt,\n })\n\n return token\n } catch {\n // Cannot be opened (say, its key was retired): renew instead.\n return undefined\n }\n }\n\n async #refresh(grant: GrantRecord): Promise<string> {\n const { options } = this.#context\n const grantId = grant.grantId\n\n if (grant.sealedUpstreamRefreshToken === undefined) {\n throw new UpstreamTokenError(\n \"This grant has no upstream refresh token to renew with\",\n )\n }\n\n let refreshed: Awaited<ReturnType<typeof options.identityProvider.refresh>>\n\n try {\n const refreshToken = await options.sealer.open(\n grant.sealedUpstreamRefreshToken,\n grantId,\n )\n\n refreshed = await options.identityProvider.refresh(\n refreshToken,\n Object.freeze({ grantId, subject: grant.subject }),\n )\n } catch (error) {\n await this.#context.emit({\n type: \"upstream_token_refresh_failed\",\n grantId,\n reason: error instanceof Error ? error.name : \"unknown\",\n })\n\n throw new UpstreamTokenError(\n \"The identity provider would not renew the upstream token\",\n {\n cause: error,\n },\n )\n }\n\n const now = options.now()\n const expiresIn = Math.max(\n 0,\n Math.min(\n Number.isFinite(refreshed.expiresIn) ? refreshed.expiresIn : 0,\n UPSTREAM_LIFETIME_CEILING_SECONDS,\n ),\n )\n const expiresAt = now + expiresIn * 1000\n const updated = await options.store.updateGrant(grantId, {\n sealedUpstreamAccessToken: await options.sealer.seal(\n refreshed.accessToken,\n `${grantId}#access`,\n ),\n upstreamAccessTokenExpiresAt: expiresAt,\n ...(refreshed.refreshToken !== undefined\n ? {\n sealedUpstreamRefreshToken: await options.sealer.seal(\n refreshed.refreshToken,\n grantId,\n ),\n }\n : {}),\n })\n\n // The grant ended while the identity provider answered: whoever ended\n // it could only revoke the refresh token stored at that moment, so the\n // one just handed out is revoked here.\n if (!updated || this.#revoking.has(grantId)) {\n this.#cache.delete(grantId)\n\n if (\n refreshed.refreshToken !== undefined &&\n options.identityProvider.revoke !== undefined\n ) {\n try {\n await options.identityProvider.revoke(refreshed.refreshToken)\n } catch {\n // Best effort.\n }\n }\n\n throw new GrantInactiveError()\n }\n\n this.#remember(grantId, { token: refreshed.accessToken, expiresAt })\n\n return refreshed.accessToken\n }\n}\n", "import { McpAuthConfigError } from \"../errors/mcp-auth-config-error.js\"\nimport { jsonCopy } from \"../helpers/json.js\"\nimport {\n methodNotAllowed,\n preflight,\n withTransportSecurity,\n} from \"../helpers/responses.js\"\nimport type { McpAuth } from \"../interfaces/auth.js\"\nimport type { McpAuthOptions } from \"../interfaces/options.js\"\nimport type { RevokeGrant } from \"../interfaces/routes.js\"\nimport { authenticateRequest, bearerChallenge } from \"./authenticate.js\"\nimport { createContext } from \"./context.js\"\nimport { revokeGrantById } from \"./grants.js\"\nimport { defaultConsentPage, defaultErrorPage } from \"./pages.js\"\nimport { resolveOptions } from \"./resolve-options.js\"\nimport { buildRoutes } from \"./router.js\"\nimport { UpstreamTokenCache } from \"./upstream-tokens.js\"\n\n/**\n * Create the authorization server and resource-server guard for one MCP\n * endpoint.\n *\n * Every option that can be customised is checked here, once; extensions can\n * only add checks, enrich data or observe events, never relax a protocol or\n * security rule.\n *\n * @param options - Configuration; see {@link McpAuthOptions}.\n * @returns The request handler and guard.\n * @throws {McpAuthConfigError} When an option is invalid.\n * @example\n * ```ts\n * import { randomBytes } from \"node:crypto\"\n * import {\n * createAesGcmSealer,\n * createMcpAuth,\n * createMemoryStore,\n * createOidcProvider,\n * } from \"@gydr-ai/mcp-oauth\"\n *\n * const auth = createMcpAuth({\n * issuer: \"https://mcp.example.com\",\n * resource: \"https://mcp.example.com/mcp\",\n * identityProvider: createOidcProvider({\n * issuer: \"https://login.example.com\",\n * clientId: \"mcp-gateway\",\n * scopes: [\"openid\", \"offline_access\"],\n * redirectUri: \"https://mcp.example.com/callback\",\n * }),\n * store: createMemoryStore(),\n * sealer: createAesGcmSealer({ keys: { k1: randomBytes(32) }, currentKid: \"k1\" }),\n * })\n *\n * export async function serve(request: Request): Promise<Response> {\n * const oauth = await auth.handle(request)\n *\n * if (oauth) return oauth\n *\n * const result = await auth.authenticate(request)\n *\n * if (!result.ok) return result.response\n *\n * return Response.json({ hello: result.subject })\n * }\n * ```\n */\nexport function createMcpAuth(options: McpAuthOptions): McpAuth {\n const resolved = resolveOptions(options, {\n consent: defaultConsentPage,\n error: defaultErrorPage,\n })\n const context = createContext(resolved)\n const upstream = new UpstreamTokenCache(context)\n const revoke: RevokeGrant = (grantId, reason) =>\n revokeGrantById(context, grantId, reason, (id) => {\n upstream.forget(id)\n })\n const routes = buildRoutes(context, revoke)\n const secured = (response: Response) =>\n withTransportSecurity(response, resolved.strictTransportSecurity)\n const route = async (request: Request) => {\n const entry = routes.get(new URL(request.url).pathname)\n\n if (entry === undefined) return undefined\n\n if (request.method === \"OPTIONS\" && entry.cors) {\n return preflight([...entry.methods, \"OPTIONS\"].join(\", \"))\n }\n\n if (!entry.methods.includes(request.method)) {\n return methodNotAllowed(entry.methods.join(\", \"))\n }\n\n return entry.run(request)\n }\n\n return {\n async handle(request) {\n const response = await route(request)\n\n return response === undefined ? undefined : secured(response)\n },\n async authenticate(request) {\n const result = await authenticateRequest(\n context,\n request,\n (grantId, forceRefresh) => upstream.get(grantId, forceRefresh),\n )\n\n if (!result.ok) secured(result.response)\n\n return result\n },\n unauthorized(_request, error) {\n return secured(bearerChallenge(context, error))\n },\n revokeGrant(grantId) {\n return revoke(grantId, \"revoke_grant_call\")\n },\n renewUpstream(grantId) {\n return upstream.get(grantId, true)\n },\n async updateGrantData(grantId, data) {\n const copy = jsonCopy(\n data,\n resolved.limits.grantDataBytes,\n \"updateGrantData\",\n )\n\n return resolved.store.updateGrant(grantId, { data: copy })\n },\n async revokeGrantsForSubject(subject) {\n const { store } = resolved\n\n if (store.listGrantsBySubject === undefined) {\n throw new McpAuthConfigError(\n \"revokeGrantsForSubject needs a store with listGrantsBySubject\",\n )\n }\n\n const grants = await store.listGrantsBySubject(subject)\n let ended = 0\n\n for (const grant of grants) {\n // Never trust the store's filter to end someone else's grant.\n if (grant.subject !== subject) continue\n\n await revoke(grant.grantId, \"revoke_subject_call\")\n ended += 1\n }\n\n return ended\n },\n }\n}\n", "import { createCipheriv, createDecipheriv, randomBytes } from \"node:crypto\"\n\nimport { SealerError } from \"../errors/sealer-error.js\"\nimport type { AesGcmSealerOptions, SecretSealer } from \"../interfaces/sealer.js\"\n\n/** Length of the random initialisation vector (96 bits, as GCM recommends). */\nconst IV_BYTES = 12\n/** Length of the authentication tag. */\nconst TAG_BYTES = 16\n/** Allowed key ids. */\nconst KID_PATTERN = /^[A-Za-z0-9_-]{1,32}$/\n/** Version tag at the start of every sealed value. */\nconst FORMAT = \"v1\"\n\n/**\n * Run synchronous crypto and report its outcome as a promise.\n *\n * @param work - Synchronous encryption or decryption; anything it throws becomes the rejection.\n * @returns Resolves with `work`'s result, or rejects with what it threw (non-errors become a `SealerError`).\n */\nfunction attempt<T>(work: () => T): Promise<T> {\n try {\n return Promise.resolve(work())\n } catch (error) {\n return Promise.reject(\n error instanceof Error ? error : new SealerError(\"Sealing failed\"),\n )\n }\n}\n\n/**\n * The additional data actually authenticated: format, key id, caller AAD.\n *\n * @param kid - Key id the value is (or will be) encrypted with, already checked against the allowed pattern.\n * @param aad - Caller's owner label (a grant id, optionally suffixed with `#access`); any string.\n * @returns The bytes to authenticate.\n */\nfunction additionalData(kid: string, aad: string): Buffer {\n return Buffer.from(`${FORMAT}\\u0000${kid}\\u0000${aad}`, \"utf8\")\n}\n\n/**\n * AES-256-GCM {@link SecretSealer} with key rotation.\n *\n * Output: `v1.<kid>.<iv>.<ciphertext>.<tag>` (base64url parts). A random\n * 96-bit IV per value; the caller's AAD (the grant id) and the key id are\n * authenticated, so a sealed value moved to another grant, or relabelled with\n * another key id, fails to open.\n *\n * @param options - Keys by key id and the key id to encrypt with.\n * @returns A `SecretSealer` whose values look like `v1.<kid>.<iv>.<ciphertext>.<tag>` (base64url parts).\n * @throws {TypeError} When a key is not 32 bytes, a key id is invalid, or\n * `currentKid` is not among the keys.\n * @example\n * ```ts\n * import { createAesGcmSealer } from \"@gydr-ai/mcp-oauth\"\n *\n * const sealer = createAesGcmSealer({\n * keys: { k2026a: Buffer.from(process.env.SEALING_KEY ?? \"\", \"base64\") },\n * currentKid: \"k2026a\",\n * })\n * const sealed = await sealer.seal(\"upstream-refresh-token\", \"grant-123\")\n *\n * console.log(await sealer.open(sealed, \"grant-123\"))\n * ```\n */\nexport function createAesGcmSealer(options: AesGcmSealerOptions): SecretSealer {\n const keys = new Map<string, Buffer>()\n\n for (const [kid, key] of Object.entries(options.keys)) {\n if (!KID_PATTERN.test(kid)) {\n throw new TypeError(\n `Key id ${JSON.stringify(kid)} has characters outside A-Z a-z 0-9 _ -, or is too long`,\n )\n }\n\n if (!(key instanceof Uint8Array) || key.byteLength !== 32) {\n throw new TypeError(`Key ${kid} is not 32 bytes long`)\n }\n\n // Copy: a caller mutating its buffer must not change our key.\n keys.set(kid, Buffer.from(key))\n }\n\n const current = keys.get(options.currentKid)\n\n if (current === undefined) {\n throw new TypeError(\"currentKid has no matching entry in keys\")\n }\n\n const currentKid = options.currentKid\n\n return {\n seal(plaintext, aad) {\n return attempt(() => {\n const iv = randomBytes(IV_BYTES)\n const cipher = createCipheriv(\"aes-256-gcm\", current, iv, {\n authTagLength: TAG_BYTES,\n })\n\n cipher.setAAD(additionalData(currentKid, aad))\n\n const body = Buffer.concat([\n cipher.update(plaintext, \"utf8\"),\n cipher.final(),\n ])\n\n return [\n FORMAT,\n currentKid,\n iv.toString(\"base64url\"),\n body.toString(\"base64url\"),\n cipher.getAuthTag().toString(\"base64url\"),\n ].join(\".\")\n })\n },\n open(sealed, aad) {\n return attempt(() => {\n const parts = sealed.split(\".\")\n\n if (parts.length !== 5 || parts[0] !== FORMAT) {\n throw new SealerError(\"Value is not in sealed format\")\n }\n\n const [, kid = \"\", ivText = \"\", bodyText = \"\", tagText = \"\"] = parts\n const key = keys.get(kid)\n\n if (key === undefined)\n throw new SealerError(\n \"Value was sealed with a key that is not configured\",\n )\n\n const iv = Buffer.from(ivText, \"base64url\")\n const tag = Buffer.from(tagText, \"base64url\")\n\n if (iv.byteLength !== IV_BYTES || tag.byteLength !== TAG_BYTES) {\n throw new SealerError(\"Value is not in sealed format\")\n }\n\n try {\n const decipher = createDecipheriv(\"aes-256-gcm\", key, iv, {\n authTagLength: TAG_BYTES,\n })\n\n decipher.setAAD(additionalData(kid, aad))\n decipher.setAuthTag(tag)\n\n return Buffer.concat([\n decipher.update(Buffer.from(bodyText, \"base64url\")),\n decipher.final(),\n ]).toString(\"utf8\")\n } catch {\n throw new SealerError(\n \"Value was altered or belongs to a different owner\",\n )\n }\n })\n },\n }\n}\n", "/**\n * A sealed value could not be opened: it is malformed, was altered, belongs\n * to another owner, or was encrypted with a key that is not configured.\n *\n * @example\n * ```ts\n * import { type SecretSealer, SealerError } from \"@gydr-ai/mcp-oauth\"\n *\n * export async function openOrNothing(sealer: SecretSealer, value: string, grantId: string) {\n * try {\n * return await sealer.open(value, grantId)\n * } catch (error) {\n * if (error instanceof SealerError) return undefined\n * throw error\n * }\n * }\n * ```\n */\nexport class SealerError extends Error {\n /**\n * @param message - Why the value could not be opened.\n */\n constructor(message: string) {\n super(message)\n this.name = \"SealerError\"\n }\n}\n", "/**\n * A call to the identity provider failed. The message never contains a token.\n *\n * @example\n * ```ts\n * import { type IdentityProvider, IdentityProviderError } from \"@gydr-ai/mcp-oauth\"\n *\n * export async function renew(provider: IdentityProvider, refreshToken: string) {\n * try {\n * return await provider.refresh(refreshToken)\n * } catch (error) {\n * if (error instanceof IdentityProviderError) console.warn(error.code)\n * throw error\n * }\n * }\n * ```\n */\nexport class IdentityProviderError extends Error {\n /** The provider's OAuth `error` code, when it sent one. */\n readonly code: string | undefined\n\n /**\n * @param message - What failed.\n * @param code - The provider's OAuth `error` code, if any.\n * @param options - Standard error options, such as `cause`.\n * @param options.cause - The underlying network or verification error.\n */\n constructor(message: string, code?: string, options?: { cause?: unknown }) {\n super(message, options)\n this.name = \"IdentityProviderError\"\n this.code = code\n }\n}\n", "import { trimTrailing } from \"../helpers/text.js\"\nimport {\n createRemoteJWKSet,\n customFetch,\n type JWTPayload,\n jwtVerify,\n} from \"jose\"\n\nimport { DEFAULT_ALGORITHMS, RESERVED_PARAMS } from \"../constants/oidc.js\"\nimport { IdentityProviderError } from \"../errors/identity-provider-error.js\"\nimport { isRecord } from \"../helpers/guards.js\"\nimport type {\n IdentityProvider,\n UpstreamLogin,\n UpstreamTokens,\n} from \"../interfaces/identity-provider.js\"\nimport type {\n Discovery,\n OidcProviderOptions,\n TokenEndpointResponse,\n} from \"../interfaces/oidc.js\"\nimport {\n checkEndpoint,\n isDiscovery,\n isTokenResponse,\n} from \"./oidc-documents.js\"\n\n/**\n * An {@link IdentityProvider} for any OpenID Connect provider: discovery,\n * authorization code + PKCE S256 + nonce, and ID token verification (`iss`,\n * `aud`, `azp`, `exp`, `nonce`, signature against the IdP's JWKS with an\n * asymmetric-only algorithm allow-list).\n *\n * IdP requests use plain `fetch` with a timeout: the issuer is configured\n * by the operator and may legitimately sit on a private network.\n *\n * @param options - The provider's issuer, this server's client credentials,\n * scopes and callback URL.\n * @returns An `IdentityProvider`; discovery runs lazily on first use and is retried after a failure.\n * @throws {TypeError} When `scopes` lacks `openid`, an algorithm is not\n * asymmetric, a reserved parameter is overridden, or the issuer is not an\n * `https` URL.\n * @example\n * ```ts\n * import { createOidcProvider } from \"@gydr-ai/mcp-oauth\"\n *\n * export const identityProvider = createOidcProvider({\n * issuer: \"https://login.example.com\",\n * clientId: \"mcp-gateway\",\n * clientSecret: process.env.OIDC_CLIENT_SECRET ?? \"\",\n * scopes: [\"openid\", \"email\", \"offline_access\"],\n * redirectUri: \"https://mcp.example.com/callback\",\n * })\n * ```\n */\nexport function createOidcProvider(\n options: OidcProviderOptions,\n): IdentityProvider {\n const insecure = options.allowInsecureLocalhost === true\n const timeoutMs = options.timeoutMs ?? 10_000\n const doFetch = options.fetch ?? fetch\n const algorithms = [...(options.algorithms ?? DEFAULT_ALGORITHMS)]\n\n if (!options.scopes.includes(\"openid\")) {\n throw new TypeError(\"OIDC scopes need to contain openid\")\n }\n\n if (\n algorithms.length === 0 ||\n algorithms.some(\n (alg) =>\n alg.toLowerCase() === \"none\" || alg.toUpperCase().startsWith(\"HS\"),\n )\n ) {\n throw new TypeError(\n \"Only asymmetric signature algorithms are allowed (no none, no HS*)\",\n )\n }\n\n for (const key of Object.keys(options.extraAuthorizationParams ?? {})) {\n if (RESERVED_PARAMS.has(key)) {\n throw new TypeError(`extraAuthorizationParams may not override ${key}`)\n }\n }\n\n try {\n checkEndpoint(\"issuer\", options.issuer, insecure)\n } catch (error) {\n throw new TypeError(\n error instanceof Error\n ? error.message\n : \"Identity provider issuer is not usable\",\n )\n }\n\n const timedFetch = (url: string | URL, init: RequestInit = {}) =>\n doFetch(url, {\n ...init,\n redirect: \"error\",\n signal: AbortSignal.timeout(timeoutMs),\n })\n\n let discovery: Promise<{\n readonly document: Discovery\n readonly jwks: ReturnType<typeof createRemoteJWKSet>\n }> | null = null\n\n const discover = () => {\n if (discovery !== null) return discovery\n\n const running = (async () => {\n const base = trimTrailing(options.issuer, \"/\")\n let response: Response\n\n try {\n response = await timedFetch(\n `${base}/.well-known/openid-configuration`,\n {\n headers: { Accept: \"application/json\" },\n },\n )\n } catch (error) {\n throw new IdentityProviderError(\n \"Could not reach the identity provider's discovery document\",\n undefined,\n {\n cause: error,\n },\n )\n }\n\n if (!response.ok) {\n throw new IdentityProviderError(\n `Discovery document request returned HTTP ${response.status}`,\n )\n }\n\n const document: unknown = await response.json().catch(() => undefined)\n\n if (!isDiscovery(document)) {\n throw new IdentityProviderError(\n \"Discovery document lacks required fields\",\n )\n }\n\n // OIDC Discovery \u00A7 4.3: the document must name the configured issuer.\n if (document.issuer !== options.issuer) {\n throw new IdentityProviderError(\n \"Discovery document names a different issuer than configured\",\n )\n }\n\n if (\n document.code_challenge_methods_supported !== undefined &&\n !document.code_challenge_methods_supported.includes(\"S256\")\n ) {\n throw new IdentityProviderError(\n \"Identity provider does not offer S256 code challenges\",\n )\n }\n\n checkEndpoint(\n \"authorization_endpoint\",\n document.authorization_endpoint,\n insecure,\n )\n checkEndpoint(\"token_endpoint\", document.token_endpoint, insecure)\n\n const jwksUrl = checkEndpoint(\"jwks_uri\", document.jwks_uri, insecure)\n\n if (document.revocation_endpoint !== undefined) {\n checkEndpoint(\n \"revocation_endpoint\",\n document.revocation_endpoint,\n insecure,\n )\n }\n\n const jwks = createRemoteJWKSet(jwksUrl, {\n timeoutDuration: timeoutMs,\n [customFetch]: (url: string, init: RequestInit) =>\n doFetch(url, { ...init, redirect: \"error\" }),\n })\n\n return { document, jwks }\n })()\n\n discovery = running\n // A failed discovery is retried on the next call.\n running.catch(() => {\n if (discovery === running) discovery = null\n })\n\n return running\n }\n\n /**\n * Client authentication for the token and revocation endpoints.\n *\n * @param document - The provider's discovery document; its `token_endpoint_auth_methods_supported` picks the method.\n * @param body - Form body of the outgoing request; `client_id` (and, for `client_secret_post`, the secret) are added to it.\n * @returns Headers to add (empty when credentials go in the body).\n */\n const clientAuth = (\n document: Discovery,\n body: URLSearchParams,\n ): Record<string, string> => {\n body.set(\"client_id\", options.clientId)\n\n if (options.clientSecret === undefined) return {}\n\n const methods = document.token_endpoint_auth_methods_supported ?? [\n \"client_secret_basic\",\n ]\n\n if (methods.includes(\"client_secret_basic\")) {\n // RFC 6749 \u00A7 2.3.1: form-encode each part before base64.\n const user = encodeURIComponent(options.clientId)\n const pass = encodeURIComponent(options.clientSecret)\n\n return {\n Authorization: `Basic ${Buffer.from(`${user}:${pass}`).toString(\"base64\")}`,\n }\n }\n\n if (methods.includes(\"client_secret_post\")) {\n body.set(\"client_secret\", options.clientSecret)\n\n return {}\n }\n\n throw new IdentityProviderError(\n \"Identity provider accepts no client-secret method this library implements\",\n )\n }\n\n const postToken = async (\n document: Discovery,\n body: URLSearchParams,\n ): Promise<TokenEndpointResponse> => {\n const headers = {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n ...clientAuth(document, body),\n }\n let response: Response\n\n try {\n response = await timedFetch(document.token_endpoint, {\n method: \"POST\",\n headers,\n body: body.toString(),\n })\n } catch (error) {\n throw new IdentityProviderError(\n \"Could not reach the identity provider's token endpoint\",\n undefined,\n {\n cause: error,\n },\n )\n }\n\n const parsed: unknown = await response.json().catch(() => undefined)\n\n if (!response.ok) {\n const code =\n isRecord(parsed) && typeof parsed.error === \"string\"\n ? parsed.error.slice(0, 64)\n : undefined\n\n throw new IdentityProviderError(\n `Token endpoint returned HTTP ${response.status}`,\n code,\n )\n }\n\n if (!isTokenResponse(parsed)) {\n throw new IdentityProviderError(\n \"Token endpoint reply lacks an access_token\",\n )\n }\n\n return parsed\n }\n\n const verifyIdToken = async (\n document: Discovery,\n jwks: ReturnType<typeof createRemoteJWKSet>,\n idToken: string,\n nonce: string,\n ): Promise<JWTPayload> => {\n let payload: JWTPayload\n\n try {\n ;({ payload } = await jwtVerify(idToken, jwks, {\n issuer: document.issuer,\n audience: options.clientId,\n algorithms,\n clockTolerance: options.clockToleranceSeconds ?? 60,\n requiredClaims: [\"sub\", \"exp\", \"iat\"],\n }))\n } catch (error) {\n throw new IdentityProviderError(\n \"ID token signature or claims did not verify\",\n undefined,\n { cause: error },\n )\n }\n\n if (payload.nonce !== nonce) {\n throw new IdentityProviderError(\n \"ID token nonce differs from the one sent\",\n )\n }\n\n const audiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud]\n\n if (\n (audiences.length > 1 || payload.azp !== undefined) &&\n payload.azp !== options.clientId\n ) {\n throw new IdentityProviderError(\"ID token azp names some other client\")\n }\n\n return payload\n }\n\n return {\n async authorizationUrl({ state, codeChallenge, redirectUri, nonce }) {\n if (redirectUri !== options.redirectUri) {\n throw new IdentityProviderError(\n \"Callback URL differs from the redirectUri this provider was configured with\",\n )\n }\n\n const { document } = await discover()\n const url = new URL(document.authorization_endpoint)\n\n for (const [key, value] of Object.entries(\n options.extraAuthorizationParams ?? {},\n )) {\n url.searchParams.set(key, value)\n }\n\n url.searchParams.set(\"response_type\", \"code\")\n url.searchParams.set(\"client_id\", options.clientId)\n url.searchParams.set(\"redirect_uri\", redirectUri)\n url.searchParams.set(\"scope\", options.scopes.join(\" \"))\n url.searchParams.set(\"state\", state)\n url.searchParams.set(\"nonce\", nonce)\n url.searchParams.set(\"code_challenge\", codeChallenge)\n url.searchParams.set(\"code_challenge_method\", \"S256\")\n\n return url.toString()\n },\n\n async exchangeCode({ code, codeVerifier, redirectUri, nonce, issuer }) {\n const { document, jwks } = await discover()\n\n // RFC 9207: when the IdP sends `iss` it must be the expected issuer;\n // when it promises to send it, it must be there (mix-up defence).\n if (issuer !== undefined && issuer !== document.issuer) {\n throw new IdentityProviderError(\n \"Authorization response iss names another issuer\",\n )\n }\n\n if (\n issuer === undefined &&\n document.authorization_response_iss_parameter_supported === true\n ) {\n throw new IdentityProviderError(\n \"Authorization response omits the promised iss\",\n )\n }\n\n const tokens = await postToken(\n document,\n new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n code_verifier: codeVerifier,\n }),\n )\n\n if (tokens.id_token === undefined) {\n throw new IdentityProviderError(\"Token endpoint reply has no id_token\")\n }\n\n const claims = await verifyIdToken(document, jwks, tokens.id_token, nonce)\n const subject = claims.sub\n\n if (typeof subject !== \"string\" || subject === \"\") {\n throw new IdentityProviderError(\"ID token carries no sub claim\")\n }\n\n const displayName = [\n claims.name,\n claims.email,\n claims.preferred_username,\n ].find(\n (value): value is string => typeof value === \"string\" && value !== \"\",\n )\n const login: UpstreamLogin = {\n subject,\n claims: { ...claims },\n accessToken: tokens.access_token,\n expiresIn: tokens.expires_in ?? 300,\n ...(displayName !== undefined ? { displayName } : {}),\n ...(tokens.refresh_token !== undefined\n ? { refreshToken: tokens.refresh_token }\n : {}),\n }\n\n return login\n },\n\n async refresh(refreshToken): Promise<UpstreamTokens> {\n const { document } = await discover()\n const tokens = await postToken(\n document,\n new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n }),\n )\n\n return {\n accessToken: tokens.access_token,\n expiresIn: tokens.expires_in ?? 300,\n ...(tokens.refresh_token !== undefined\n ? { refreshToken: tokens.refresh_token }\n : {}),\n }\n },\n\n async revoke(refreshToken) {\n const { document } = await discover()\n\n if (document.revocation_endpoint === undefined) return\n\n const body = new URLSearchParams({\n token: refreshToken,\n token_type_hint: \"refresh_token\",\n })\n const headers = {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n ...clientAuth(document, body),\n }\n\n try {\n await timedFetch(document.revocation_endpoint, {\n method: \"POST\",\n headers,\n body: body.toString(),\n })\n } catch (error) {\n throw new IdentityProviderError(\n \"Could not reach the identity provider's revocation endpoint\",\n undefined,\n {\n cause: error,\n },\n )\n }\n },\n }\n}\n", "/**\n * ID token signature algorithms accepted by default: asymmetric only.\n *\n * @internal\n */\nexport const DEFAULT_ALGORITHMS = [\n \"RS256\",\n \"RS384\",\n \"RS512\",\n \"PS256\",\n \"PS384\",\n \"PS512\",\n \"ES256\",\n \"ES384\",\n \"ES512\",\n \"EdDSA\",\n]\n\n/**\n * Authorization parameters this library sets itself and never lets callers override.\n *\n * @internal\n */\nexport const RESERVED_PARAMS: ReadonlySet<string> = new Set([\n \"state\",\n \"nonce\",\n \"code_challenge\",\n \"code_challenge_method\",\n \"client_id\",\n \"redirect_uri\",\n \"response_type\",\n \"scope\",\n])\n", "import { LOOPBACK_HOSTS } from \"../constants/protocol.js\"\nimport { IdentityProviderError } from \"../errors/identity-provider-error.js\"\nimport { isRecord, isStringArray } from \"../helpers/guards.js\"\nimport type { Discovery, TokenEndpointResponse } from \"../interfaces/oidc.js\"\n\nfunction optionalString(value: unknown): value is string | undefined {\n return value === undefined || typeof value === \"string\"\n}\n\n/**\n *\n * @param value - Parsed JSON from the provider's `/.well-known/openid-configuration`.\n * @returns `true` for a usable discovery document.\n * @internal\n */\nexport function isDiscovery(value: unknown): value is Discovery {\n return (\n isRecord(value) &&\n typeof value.issuer === \"string\" &&\n typeof value.authorization_endpoint === \"string\" &&\n typeof value.token_endpoint === \"string\" &&\n typeof value.jwks_uri === \"string\" &&\n optionalString(value.revocation_endpoint) &&\n (value.token_endpoint_auth_methods_supported === undefined ||\n isStringArray(value.token_endpoint_auth_methods_supported)) &&\n (value.authorization_response_iss_parameter_supported === undefined ||\n typeof value.authorization_response_iss_parameter_supported ===\n \"boolean\") &&\n (value.code_challenge_methods_supported === undefined ||\n isStringArray(value.code_challenge_methods_supported))\n )\n}\n\n/**\n *\n * @param value - Parsed JSON from the provider's token endpoint.\n * @returns `true` for a usable token response.\n * @internal\n */\nexport function isTokenResponse(\n value: unknown,\n): value is TokenEndpointResponse {\n return (\n isRecord(value) &&\n typeof value.access_token === \"string\" &&\n value.access_token !== \"\" &&\n optionalString(value.id_token) &&\n optionalString(value.refresh_token) &&\n (value.expires_in === undefined ||\n (typeof value.expires_in === \"number\" &&\n Number.isFinite(value.expires_in)))\n )\n}\n\n/**\n *\n * @param name - Discovery field name, used in the error message (for example `token_endpoint`).\n * @param value - The endpoint URL as published by the provider.\n * @param insecure - Whether `http` is allowed for loopback hosts (`allowInsecureLocalhost`).\n * @returns The parsed URL, known to be `https` (or loopback `http` when allowed).\n * @throws {IdentityProviderError} When the value is not a URL or is not `https` (loopback `http` only when allowed).\n * @internal\n */\nexport function checkEndpoint(\n name: string,\n value: string,\n insecure: boolean,\n): URL {\n let url: URL\n\n try {\n url = new URL(value)\n } catch {\n throw new IdentityProviderError(\n `Identity provider ${name} cannot be parsed as a URL`,\n )\n }\n\n const local = url.protocol === \"http:\" && LOOPBACK_HOSTS.has(url.hostname)\n\n if (url.protocol !== \"https:\" && !(local && insecure)) {\n throw new IdentityProviderError(\n `Identity provider ${name} is not an https URL`,\n )\n }\n\n return url\n}\n", "import type { ClientRecord } from \"../interfaces/records.js\"\nimport { hasExpired } from \"../helpers/time.js\"\n\n/**\n * Clients, split so a registration flood can never push out a client people\n * are connected through:\n *\n * - `#held`: clients with at least one grant in the store, and static\n * clients. Never evicted; bounded indirectly by the grant collection.\n * - `#idle`: dynamic clients with no grant, in least-recently-used order.\n * Bounded by `max` (the least recently used is evicted, O(1)) and by the\n * idle lifetime.\n *\n * @internal\n */\nexport class ClientCollection {\n readonly #held = new Map<string, ClientRecord>()\n readonly #idle = new Map<string, { client: ClientRecord; usedAt: number }>()\n readonly #grantCounts = new Map<string, number>()\n readonly #max: number\n readonly #idleMs: number\n readonly #now: () => number\n\n /**\n *\n * @param max - Most grant-less dynamic clients held; the least recently used is evicted beyond it.\n * @param idleMs - Milliseconds a grant-less dynamic client may go unused before it is dropped.\n * @param now - Clock in milliseconds since the epoch.\n */\n constructor(max: number, idleMs: number, now: () => number) {\n this.#max = max\n this.#idleMs = idleMs\n this.#now = now\n }\n\n #toIdle(client: ClientRecord): void {\n this.#idle.delete(client.clientId)\n\n while (this.#idle.size >= this.#max) {\n const oldest = this.#idle.keys().next()\n\n if (oldest.done === true) break\n\n this.#idle.delete(oldest.value)\n }\n\n this.#idle.set(client.clientId, { client, usedAt: this.#now() })\n }\n\n /**\n *\n * @param client - Client to store; static clients and clients with grants are never evicted.\n */\n put(client: ClientRecord): void {\n this.#held.delete(client.clientId)\n this.#idle.delete(client.clientId)\n\n if (\n client.kind === \"static\" ||\n (this.#grantCounts.get(client.clientId) ?? 0) > 0\n ) {\n this.#held.set(client.clientId, client)\n } else {\n this.#toIdle(client)\n }\n }\n\n /**\n *\n * @param clientId - The `client_id` to look up; a hit counts as use of a grant-less client.\n * @returns The client, or `undefined` when unknown or idle for longer than the idle lifetime.\n */\n get(clientId: string): ClientRecord | undefined {\n const held = this.#held.get(clientId)\n\n if (held !== undefined) return held\n\n const idle = this.#idle.get(clientId)\n\n if (idle === undefined) return undefined\n\n this.#idle.delete(clientId)\n\n if (hasExpired(idle.usedAt + this.#idleMs, this.#now())) return undefined\n\n // A lookup is a use: re-insert at the most-recently-used end.\n this.#idle.set(clientId, { client: idle.client, usedAt: this.#now() })\n\n return idle.client\n }\n\n /**\n *\n * @param clientId - Client whose grant count increases; it becomes non-evictable.\n */\n grantAdded(clientId: string): void {\n this.#grantCounts.set(clientId, (this.#grantCounts.get(clientId) ?? 0) + 1)\n\n const idle = this.#idle.get(clientId)\n\n if (idle !== undefined) {\n this.#idle.delete(clientId)\n this.#held.set(clientId, idle.client)\n }\n }\n\n /**\n *\n * @param clientId - Client whose grant count decreases; at zero a dynamic client becomes evictable again.\n */\n grantRemoved(clientId: string): void {\n const count = (this.#grantCounts.get(clientId) ?? 0) - 1\n\n if (count > 0) {\n this.#grantCounts.set(clientId, count)\n\n return\n }\n\n this.#grantCounts.delete(clientId)\n\n const held = this.#held.get(clientId)\n\n if (held !== undefined && held.kind !== \"static\") {\n this.#held.delete(clientId)\n this.#toIdle(held)\n }\n }\n}\n", "import { hasExpired } from \"../helpers/time.js\"\n\n/**\n * A Map with expiry-on-read and oldest-first eviction. Eviction pops from\n * the head of the Map's insertion order: O(1) amortised.\n *\n * @internal\n */\nexport class Collection<V> {\n readonly #items = new Map<string, V>()\n readonly #max: number\n readonly #expiresAt: (value: V) => number\n readonly #now: () => number\n readonly #onRemove: (value: V) => void\n\n /**\n *\n * @param max - Most entries held; inserting beyond it evicts the oldest.\n * @param now - Clock in milliseconds since the epoch, compared with each entry's expiry.\n * @param expiresAt - Returns an entry's expiry in milliseconds since the epoch.\n * @param onRemove - Called for every entry that leaves the collection (replaced, expired, evicted or deleted).\n */\n constructor(\n max: number,\n now: () => number,\n expiresAt: (value: V) => number,\n onRemove: (value: V) => void = () => {},\n ) {\n this.#max = max\n this.#now = now\n this.#expiresAt = expiresAt\n this.#onRemove = onRemove\n }\n\n #remove(key: string, value: V): void {\n this.#items.delete(key)\n this.#onRemove(value)\n }\n\n /**\n *\n * @param key - Entry key, such as a token hash or record id.\n * @returns The entry, or `undefined` when absent or expired (an expired entry is removed).\n */\n get(key: string): V | undefined {\n const value = this.#items.get(key)\n\n if (value === undefined) return undefined\n\n if (hasExpired(this.#expiresAt(value), this.#now())) {\n this.#remove(key, value)\n\n return undefined\n }\n\n return value\n }\n\n /**\n *\n * @param key - Entry key; an existing entry under it is replaced.\n * @param value - The record to store.\n */\n set(key: string, value: V): void {\n const previous = this.#items.get(key)\n\n if (previous !== undefined) this.#remove(key, previous)\n\n while (this.#items.size >= this.#max) {\n const oldest = this.#items.entries().next()\n\n if (oldest.done === true) break\n\n this.#remove(oldest.value[0], oldest.value[1])\n }\n\n this.#items.set(key, value)\n }\n\n /**\n * Read an entry even if it has expired, without removing it.\n *\n * @param key - Entry key.\n * @returns The entry, or `undefined` when absent.\n */\n peek(key: string): V | undefined {\n return this.#items.get(key)\n }\n\n /**\n * Get and delete in one synchronous step.\n *\n * @param key - Entry key.\n * @param includeExpired - When `true`, an entry past its expiry is still returned and removed.\n * @returns The removed entry, or `undefined` when there was none to take.\n */\n take(key: string, includeExpired = false): V | undefined {\n const value = includeExpired ? this.#items.get(key) : this.get(key)\n\n if (value !== undefined) this.#remove(key, value)\n\n return value\n }\n\n /**\n *\n * @param key - Entry key; a missing key is ignored.\n */\n delete(key: string): void {\n const value = this.#items.get(key)\n\n if (value !== undefined) this.#remove(key, value)\n }\n\n /**\n * Every live (unexpired) entry.\n *\n * @returns The entries in insertion order.\n */\n values(): V[] {\n const now = this.#now()\n\n return [...this.#items.values()].filter(\n (value) => !hasExpired(this.#expiresAt(value), now),\n )\n }\n\n /**\n * Remove every entry that matches.\n *\n * @param test - Returns `true` for entries to remove.\n */\n deleteWhere(test: (value: V) => boolean): void {\n for (const [key, value] of this.#items) {\n if (test(value)) this.#remove(key, value)\n }\n }\n}\n", "import {\n DEFAULT_DYNAMIC_CLIENT_IDLE_SECONDS,\n DEFAULT_MEMORY_STORE_ENTRIES,\n} from \"../constants/defaults.js\"\nimport type { MemoryStoreOptions } from \"../interfaces/memory-store.js\"\nimport type {\n AccessTokenRecord,\n AuthorizationCodeRecord,\n ClientRecord,\n ConsentRequest,\n ConsumedAuthorizationCode,\n GrantRecord,\n GrantUpstreamUpdate,\n PendingAuthorization,\n RefreshTokenRecord,\n UsedRefreshToken,\n} from \"../interfaces/records.js\"\nimport type { OAuthStore } from \"../interfaces/store.js\"\nimport { ClientCollection } from \"./client-collection.js\"\nimport { Collection } from \"./collection.js\"\nimport { checkedClock } from \"../helpers/time.js\"\n\nconst expiry = (value: { readonly expiresAt: number }) => value.expiresAt\n\n/**\n * Run synchronous store logic and report its outcome as a promise.\n *\n * @param work - Synchronous store operation; anything it throws becomes the rejection.\n * @returns Resolves with `work`'s result, or rejects with what it threw.\n */\nfunction settle<T>(work: () => T): Promise<T> {\n try {\n return Promise.resolve(work())\n } catch (error) {\n return Promise.reject(\n error instanceof Error ? error : new Error(\"Memory store failure\"),\n )\n }\n}\n\n/**\n * An in-memory {@link OAuthStore}.\n *\n * **Single process, development and tests only.** Everything is lost on\n * restart (every client must register again and every person must\n * reconnect), and two processes never see each other's state. Its atomic\n * operations are correct only because Node.js runs them on one thread with no\n * `await` between the read and the write.\n *\n * @param options - Capacity, dynamic-client idle lifetime and clock.\n * @returns An `OAuthStore` held in this process's memory.\n * @example\n * ```ts\n * import { createMemoryStore } from \"@gydr-ai/mcp-oauth\"\n *\n * // Development and tests only: state is lost on restart.\n * export const store = createMemoryStore({ maxEntriesPerCollection: 10_000 })\n * ```\n */\nexport function createMemoryStore(\n options: MemoryStoreOptions = {},\n): OAuthStore {\n const max = options.maxEntriesPerCollection ?? DEFAULT_MEMORY_STORE_ENTRIES\n const now = checkedClock(options.now ?? Date.now)\n const clients = new ClientCollection(\n max,\n (options.dynamicClientIdleSeconds ?? DEFAULT_DYNAMIC_CLIENT_IDLE_SECONDS) *\n 1000,\n now,\n )\n const metadata = new Collection<{\n client: ClientRecord\n expiresAt: number\n }>(max, now, expiry)\n const pending = new Collection<PendingAuthorization>(max, now, expiry)\n const consents = new Collection<ConsentRequest>(max, now, expiry)\n const outcomes = new Collection<{\n sealedLocation: string\n expiresAt: number\n }>(max, now, expiry)\n const codes = new Collection<{\n record: AuthorizationCodeRecord\n consumed: boolean\n expiresAt: number\n }>(max, now, expiry)\n const grants = new Collection<GrantRecord>(max, now, expiry, (grant) => {\n clients.grantRemoved(grant.clientId)\n })\n const access = new Collection<AccessTokenRecord>(max, now, expiry)\n const refresh = new Collection<RefreshTokenRecord>(max, now, expiry)\n\n return {\n getClient(clientId) {\n return settle(() => {\n return clients.get(clientId)\n })\n },\n putClient(client) {\n return settle(() => {\n clients.put(client)\n })\n },\n getCachedClientMetadata(url) {\n return settle(() => {\n return metadata.get(url)?.client\n })\n },\n putCachedClientMetadata(url, client, expiresAt) {\n return settle(() => {\n metadata.set(url, { client, expiresAt })\n })\n },\n putPendingAuthorization(record) {\n return settle(() => {\n pending.set(record.id, record)\n })\n },\n getPendingAuthorization(id) {\n return settle(() => {\n return pending.get(id)\n })\n },\n consumePendingAuthorization(id) {\n return settle(() => {\n return pending.take(id)\n })\n },\n putConsentRequest(record) {\n return settle(() => {\n consents.set(record.id, record)\n })\n },\n getConsentRequest(id) {\n return settle(() => consents.peek(id))\n },\n putConsentOutcome(key, sealedLocation, expiresAt) {\n return settle(() => {\n outcomes.set(key, { sealedLocation, expiresAt })\n })\n },\n getConsentOutcome(key) {\n return settle(() => outcomes.get(key)?.sealedLocation)\n },\n listGrantsBySubject(subject) {\n return settle(() => grants.values().filter((g) => g.subject === subject))\n },\n consumeConsentRequest(id) {\n return settle(() => {\n // Returned even when expired, so its upstream token can be revoked.\n return consents.take(id, true)\n })\n },\n putAuthorizationCode(record) {\n return settle(() => {\n codes.set(record.codeHash, {\n record,\n consumed: false,\n expiresAt: record.expiresAt,\n })\n })\n },\n getAuthorizationCode(codeHash) {\n return settle(() => {\n const entry = codes.get(codeHash)\n\n return entry === undefined\n ? undefined\n : { record: entry.record, consumed: entry.consumed }\n })\n },\n consumeAuthorizationCode(codeHash) {\n return settle(() => {\n // The consumed record stays until it expires, so a replay is seen.\n const entry = codes.get(codeHash)\n\n if (entry === undefined) return undefined\n\n const result: ConsumedAuthorizationCode = {\n record: entry.record,\n firstUse: !entry.consumed,\n }\n\n codes.set(codeHash, { ...entry, consumed: true })\n\n return result\n })\n },\n putGrant(grant) {\n return settle(() => {\n // Count first: replacing or evicting a grant reports its removal\n // through the collection callback.\n clients.grantAdded(grant.clientId)\n grants.set(grant.grantId, grant)\n })\n },\n getGrant(grantId) {\n return settle(() => {\n return grants.get(grantId)\n })\n },\n updateGrant(grantId, update: GrantUpstreamUpdate) {\n return settle(() => {\n const grant = grants.get(grantId)\n\n if (grant === undefined) return false\n\n clients.grantAdded(grant.clientId)\n grants.set(grantId, { ...grant, ...update })\n\n return true\n })\n },\n deleteGrant(grantId) {\n return settle(() => {\n const removed = grants.take(grantId, true)\n\n access.deleteWhere((token) => token.grantId === grantId)\n refresh.deleteWhere((token) => token.grantId === grantId)\n codes.deleteWhere((code) => code.record.grantId === grantId)\n\n return removed\n })\n },\n putAccessToken(token) {\n return settle(() => {\n access.set(token.tokenHash, token)\n })\n },\n getAccessToken(tokenHash) {\n return settle(() => {\n return access.get(tokenHash)\n })\n },\n deleteAccessToken(tokenHash) {\n return settle(() => {\n access.delete(tokenHash)\n })\n },\n putRefreshToken(token) {\n return settle(() => {\n refresh.set(token.tokenHash, token)\n })\n },\n getRefreshToken(tokenHash) {\n return settle(() => {\n return refresh.get(tokenHash)\n })\n },\n useRefreshToken(tokenHash, usedAt): Promise<UsedRefreshToken | undefined> {\n return settle(() => {\n const token = refresh.get(tokenHash)\n\n if (token === undefined) return undefined\n\n if (token.usedAt !== undefined) {\n return { record: token, previousUsedAt: token.usedAt }\n }\n\n const marked = { ...token, usedAt }\n\n refresh.set(tokenHash, marked)\n\n return { record: marked }\n })\n },\n putRefreshTokenReplacement(tokenHash, sealedReplacement) {\n return settle(() => {\n const token = refresh.get(tokenHash)\n\n if (token !== undefined) {\n refresh.set(tokenHash, { ...token, sealedReplacement })\n }\n })\n },\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["McpAuthConfigError", "__name", "message", "jsonCopy", "value", "limit", "label", "text", "__name", "DEFAULT_LIFETIMES", "LIFETIME_BOUNDS", "DEFAULT_LIMITS", "DEFAULT_SCOPES", "DEFAULT_REFRESH_REUSE_GRACE_SECONDS", "MAX_REFRESH_REUSE_GRACE_SECONDS", "DEFAULT_COOKIE_PREFIX", "DEFAULT_MEMORY_STORE_ENTRIES", "DEFAULT_DYNAMIC_CLIENT_IDLE_SECONDS", "UPSTREAM_EXPIRY_SKEW_MS", "UPSTREAM_CACHE_GRANTS", "UPSTREAM_LIFETIME_CEILING_SECONDS", "CLIENT_ID_LENGTH_LIMIT", "METADATA_MAX_AGE_SECONDS", "DEFAULT_CONSENT_REPLAY_SECONDS", "MAX_CONSENT_REPLAY_SECONDS", "PROTECTED_RESOURCE_METADATA_PATH", "AUTHORIZATION_SERVER_METADATA_PATH", "LOOPBACK_HOSTS", "CONSENT_FIELDS", "CONSENT_APPROVE", "FORM_MEDIA_TYPE", "JSON_MEDIA_TYPE", "NO_STORE", "STRICT_TRANSPORT_SECURITY", "CORS", "json", "status", "body", "options", "headers", "JSON_MEDIA_TYPE", "METADATA_MAX_AGE_SECONDS", "__name", "oauthError", "error", "description", "challenge", "preflight", "methods", "methodNotAllowed", "allow", "pageHeaders", "formActionOrigins", "scriptHashes", "formAction", "h", "html", "cookie", "redirect", "location", "setCookies", "withTransportSecurity", "response", "enabled", "createHash", "randomBytes", "timingSafeEqual", "SECRET_BYTES", "ENCODED_32_BYTES", "randomToken", "randomBytes", "__name", "sha256Hex", "value", "createHash", "pkceChallenge", "verifier", "constantTimeEqual", "a", "b", "left", "right", "timingSafeEqual", "BASE64URL_EXTRA", "VERIFIER_EXTRA", "charsetCheck", "extra", "text", "i", "c", "onlyBase64Url", "onlyVerifierChars", "isWellFormedVerifier", "isWellFormedChallenge", "challenge", "isOpaqueToken", "pkceMatches", "deepFreeze", "value", "key", "__name", "frozenCopy", "freezeDeep", "grantContextFor", "context", "grant", "hook", "result", "frozenCopy", "jsonCopy", "__name", "isRecord", "value", "__name", "isStringArray", "item", "grantSummary", "grant", "frozenCopy", "__name", "hasExpired", "expiresAt", "now", "__name", "checkedClock", "clock", "value", "issueTokenPair", "context", "grant", "scopes", "refreshScopes", "options", "now", "accessToken", "randomToken", "accessExpiresAt", "sha256Hex", "response", "refreshToken", "__name", "liveGrant", "grantId", "clientId", "hasExpired", "revokeUpstreamToken", "provider", "revokeSealedUpstreamToken", "sealed", "plaintext", "revokeGrantById", "reason", "onRevoking", "grantSummary", "latest", "deleted", "isRecord", "quoted", "value", "__name", "challengeStatus", "error", "bearerChallenge", "context", "options", "params", "scope", "status", "serverFailure", "bearerToken", "request", "header", "trimmed", "space", "token", "authenticateRequest", "upstream", "invalid", "description", "isOpaqueToken", "row", "sha256Hex", "hasExpired", "grant", "liveGrant", "grantContext", "grantContextFor", "ERROR_PAGES", "clientIp", "options", "request", "custom", "hops", "hop", "__name", "TAG_PATTERN", "parseLocales", "header", "ranked", "order", "part", "rawTag", "params", "tag", "qParam", "p", "q", "a", "b", "entry", "__name", "REASON_PATTERN", "decide", "check", "verdict", "judge", "__name", "isRecord", "reason", "permits", "createContext", "options", "emit", "__name", "event", "request", "scope", "limiter", "ok", "permits", "clientIp", "reason", "extra", "text", "ERROR_PAGES", "page", "frozenCopy", "parseLocales", "html", "DISALLOWED", "hasHiddenCharacters", "value", "__name", "HTML_ESCAPES", "escapeHtml", "char", "isScopeToken", "i", "code", "trimTrailing", "text", "end", "document", "title", "body", "escapeHtml", "__name", "provenance", "context", "unchecked", "escapeHtml", "__name", "correctionFields", "errors", "submitted", "names", "name", "index", "id", "message", "value", "note", "described", "defaultConsentPage", "title", "delivery", "account", "scopeItems", "scope", "description", "detail", "fields", "decision", "corrections", "document", "defaultErrorPage", "isMetadataDocumentClientId", "clientId", "CLIENT_ID_LENGTH_LIMIT", "parsed", "__name", "cleanClientName", "value", "maxLength", "name", "hasHiddenCharacters", "isSupportedGrantType", "parseGrantTypes", "strict", "omitted", "isStringArray", "known", "parseClientScopes", "supported", "requested", "s", "usable", "DEFAULT_REDIRECT_URI_LENGTH", "RESERVED_REDIRECT_PARAMETERS", "tryParse", "text", "__name", "isLoopbackHttp", "url", "LOOPBACK_HOSTS", "isLoopbackRedirectUri", "uri", "redirectUriProblem", "limits", "name", "isAcceptableRedirectUri", "isDigit", "code", "portless", "head", "tail", "i", "redirectUriMatches", "registered", "requested", "a", "b", "matchesAnyRedirectUri", "redirectHostOf", "originOf", "buildRedirect", "redirectUri", "params", "target", "value", "resolveScopeDescriptions", "scopes", "descriptions", "scope", "text", "McpAuthConfigError", "hasHiddenCharacters", "own", "__name", "resolveStaticClients", "clients", "limits", "now", "byId", "client", "id", "label", "isMetadataDocumentClientId", "uri", "problem", "redirectUriProblem", "clientName", "cleanClientName", "grantTypes", "parseGrantTypes", "freezeDeep", "parseServerUrl", "name", "value", "allowInsecureLocalhost", "url", "McpAuthConfigError", "local", "LOOPBACK_HOSTS", "__name", "trimPath", "pathname", "trimTrailing", "seconds", "fallback", "min", "max", "chosen", "positive", "resolveLimits", "limits", "pick", "key", "DEFAULT_LIMITS", "checkPath", "path", "segments", "segment", "resolvePaths", "issuerPath", "paths", "resolved", "all", "SCRIPT_HASH", "COOKIE_PREFIX", "resolveOptions", "options", "renderers", "insecure", "issuerUrl", "parseServerUrl", "issuerPath", "trimPath", "issuer", "McpAuthConfigError", "resourceUrl", "resourcePath", "scopes", "DEFAULT_SCOPES", "isScopeToken", "defaultScopes", "scope", "graceSeconds", "DEFAULT_REFRESH_REUSE_GRACE_SECONDS", "MAX_REFRESH_REUSE_GRACE_SECONDS", "lifetimes", "lifetime", "__name", "key", "seconds", "DEFAULT_LIFETIMES", "LIFETIME_BOUNDS", "grantIdleSeconds", "defaultGrantTypes", "parseGrantTypes", "paths", "resolvePaths", "cookiePrefix", "DEFAULT_COOKIE_PREFIX", "limits", "resolveLimits", "now", "checkedClock", "scriptHashes", "hash", "replaySeconds", "DEFAULT_CONSENT_REPLAY_SECONDS", "MAX_CONSENT_REPLAY_SECONDS", "prm", "PROTECTED_RESOURCE_METADATA_PATH", "asm", "AUTHORIZATION_SERVER_METADATA_PATH", "resourceMetadataPaths", "authorizationServerMetadataPaths", "reserved", "name", "path", "resolveScopeDescriptions", "resolveStaticClients", "safeFetch", "ClientMetadataError", "__name", "reason", "validateClientMetadata", "url", "document", "now", "options", "isRecord", "ClientMetadataError", "methods", "isStringArray", "grantTypes", "parseGrantTypes", "scopes", "parseClientScopes", "redirectUris", "uri", "isAcceptableRedirectUri", "clientName", "cleanClientName", "__name", "bundledFetcher", "init", "safeFetch", "beforeAbort", "work", "signal", "resolve", "reject", "onAbort", "value", "error", "failureReason", "readLimited", "response", "limit", "declared", "reader", "parts", "size", "chunk", "bytes", "cacheControlSeconds", "header", "seconds", "part", "rawName", "rawValue", "fetchClientMetadata", "fetcher", "text", "maxAgeSeconds", "hostOf", "url", "__name", "serverIdentity", "value", "host", "trimTrailing", "path", "isIssuerUrl", "issuer", "given", "resolveClient", "context", "clientId", "options", "CLIENT_ID_LENGTH_LIMIT", "isMetadataDocumentClientId", "listed", "stored", "allow", "permits", "cached", "failures", "inflight", "retryAfter", "pending", "download", "downloadAndRemember", "startedAt", "fetched", "fetchClientMetadata", "error", "rememberFailure", "failureReason", "stated", "cacheMs", "oldest", "readCookie", "request", "name", "header", "part", "index", "__name", "hostCookie", "value", "maxAgeSeconds", "clearHostCookie", "bindingCookieName", "prefix", "flowId", "sha256Hex", "groupParams", "search", "params", "key", "value", "values", "__name", "single", "params", "name", "value", "__name", "repeatedDefined", "defined", "splitAuthority", "value", "start", "pathStart", "end", "__name", "sameResource", "configured", "given", "a", "b", "resolveScopes", "requested", "permitted", "fallback", "maxLength", "asked", "s", "isScopeToken", "__name", "AUTHORIZE_PARAMETERS", "handleAuthorize", "context", "request", "options", "limits", "params", "groupParams", "refuse", "__name", "reason", "clientId", "extra", "repeatedDefined", "single", "client", "resolveClient", "sent", "onlyRegistered", "redirectUri", "matchesAnyRedirectUri", "state", "codeChallenge", "isWellFormedChallenge", "resource", "sameResource", "permitted", "fallback", "s", "scopes", "resolveScopes", "policy", "outcome", "decide", "frozenCopy", "flowId", "randomToken", "binding", "upstreamVerifier", "nonce", "pending", "sha256Hex", "location", "pkceChallenge", "redirect", "hostCookie", "bindingCookieName", "randomUUID", "documentHost", "url", "__name", "consentPageResponse", "context", "request", "consent", "formToken", "extras", "options", "page", "frozenCopy", "redirectHostOf", "isLoopbackRedirectUri", "CONSENT_FIELDS", "parseLocales", "target", "originOf", "cookies", "hostCookie", "bindingCookieName", "html", "SUBJECT_LENGTH_LIMIT", "isUsableSubject", "subject", "hasHiddenCharacters", "__name", "readMapped", "value", "displayName", "resolvePerson", "context", "login", "client", "mapSubject", "allowSubject", "limits", "claims", "rawName", "mapped", "frozenCopy", "cleanClientName", "person", "outcome", "decide", "CALLBACK_PARAMETERS", "handleCallback", "context", "request", "options", "params", "groupParams", "repeatedDefined", "flowId", "single", "cookieName", "bindingCookieName", "dropCookie", "clearHostCookie", "observed", "hasExpired", "binding", "readCookie", "constantTimeEqual", "sha256Hex", "pending", "providerError", "cancelled", "redirect", "buildRedirect", "code", "iss", "login", "error", "releaseUpstream", "__name", "revokeUpstreamToken", "client", "resolved", "resolvePerson", "person", "details", "jsonCopy", "frozenCopy", "now", "grantId", "randomUUID", "formToken", "randomToken", "sealer", "lifetime", "UPSTREAM_LIFETIME_CEILING_SECONDS", "consent", "response", "consentPageResponse", "initialGrantExpiry", "options", "now", "absolute", "__name", "slidGrantExpiry", "grant", "next", "BodyTooLargeError", "__name", "hasContentType", "request", "expected", "value", "type", "__name", "readBody", "maxBytes", "declared", "BodyTooLargeError", "reader", "chunks", "total", "chunk", "bytes", "sleep", "REPLAY_POLL_MS", "RESERVED_FIELDS", "CONSENT_FIELDS", "FIELD_ERROR_LIMIT", "FIELD_TEXT_LIMIT", "extraFields", "form", "fields", "name", "values", "first", "__name", "readFieldErrors", "value", "isRecord", "entries", "errors", "message", "hookClient", "consent", "readFields", "context", "hook", "result", "frozenCopy", "jsonCopy", "fieldErrors", "handleConsent", "request", "options", "origin", "hasContentType", "FORM_MEDIA_TYPE", "groupParams", "readBody", "error", "BodyTooLargeError", "repeatedDefined", "interactionId", "single", "formToken", "store", "sealer", "cookieName", "bindingCookieName", "dropCookie", "clearHostCookie", "binding", "readCookie", "ended", "replayKey", "sha256Hex", "remembers", "remember", "answer", "expiresAt", "lastSealed", "lastAnswer", "lastExpiresAt", "recall", "sealed", "hasExpired", "outcome", "replay", "verified", "attempts", "attempt", "redirect", "sleep", "releaseUpstream", "revokeSealedUpstreamToken", "abandon", "tokenOk", "constantTimeEqual", "browserOk", "approve", "CONSENT_APPROVE", "grantData", "verdict", "consentPageResponse", "params", "location", "buildRedirect", "before", "decide", "now", "grant", "initialGrantExpiry", "code", "randomToken", "after", "grantSummary", "badMetadata", "description", "oauthError", "__name", "badRedirect", "handleRegister", "context", "request", "options", "limits", "hasContentType", "body", "readBody", "error", "BodyTooLargeError", "isRecord", "uris", "isStringArray", "uri", "problem", "redirectUriProblem", "authMethod", "grantTypes", "parseGrantTypes", "responseTypes", "scopes", "parseClientScopes", "clientName", "cleanClientName", "policy", "permits", "frozenCopy", "issuedAt", "redirectUris", "client", "randomToken", "redirectHostOf", "json", "EndpointRefusal", "error", "description", "status", "challenge", "detail", "__name", "AUTH_SCHEME", "CLIENT_PARAMETERS", "schemeOf", "header", "scheme", "__name", "readPublicClientForm", "request", "maxBytes", "defined", "hasContentType", "FORM_MEDIA_TYPE", "EndpointRefusal", "form", "groupParams", "readBody", "error", "BodyTooLargeError", "repeated", "repeatedDefined", "authorization", "refusal", "single", "requireOwnResource", "context", "resource", "sameResource", "DONE_HEADERS", "REVOKE_PARAMETERS", "handleRevoke", "context", "request", "revoke", "store", "limits", "done", "__name", "oauthError", "form", "clientId", "readPublicClientForm", "token", "single", "EndpointRefusal", "isOpaqueToken", "hash", "sha256Hex", "callerMayAct", "owner", "asRefreshToken", "found", "asAccessToken", "order", "attempt", "error", "requireTokenIssue", "context", "grant", "grantType", "hook", "verdict", "grantSummary", "EndpointRefusal", "judge", "__name", "exchangeCode", "context", "form", "clientId", "revoke", "options", "code", "single", "verifier", "redirectUri", "missing", "name", "EndpointRefusal", "requireOwnResource", "unusable", "__name", "detail", "isOpaqueToken", "codeHash", "sha256Hex", "stored", "issued", "resource", "differs", "sameResource", "pkceMatches", "mismatch", "replayed", "hasExpired", "grant", "liveGrant", "requireTokenIssue", "redeemed", "tokens", "issueTokenPair", "exchangeRefreshToken", "context", "form", "clientId", "revoke", "options", "presented", "single", "EndpointRefusal", "requireOwnResource", "unusable", "__name", "isOpaqueToken", "hash", "sha256Hex", "now", "known", "hasExpired", "scopes", "narrowed", "asked", "s", "current", "liveGrant", "requireTokenIssue", "marked", "earlier", "endReplayedGrant", "answerRetry", "grant", "slid", "slidGrantExpiry", "renewed", "tokens", "issueTokenPair", "keepReplacement", "replacementAad", "spent", "parseReplacement", "plaintext", "value", "isRecord", "store", "sealer", "replacement", "grantId", "next", "TOKEN_PARAMETERS", "handleToken", "context", "request", "revoke", "clientId", "oauthError", "parsed", "readPublicClientForm", "EndpointRefusal", "byUrl", "isMetadataDocumentClientId", "client", "resolveClient", "grantType", "single", "tokens", "exchangeCode", "exchangeRefreshToken", "json", "error", "__name", "authorizationServerMetadata", "options", "at", "__name", "path", "protectedResourceMetadata", "guarded", "context", "where", "run", "fail", "error", "__name", "buildRoutes", "revoke", "options", "serverError", "oauthError", "pageError", "request", "routes", "add", "path", "entry", "document", "body", "json", "protectedResourceMetadata", "authorizationServerMetadata", "handleRegister", "handleAuthorize", "handleCallback", "handleConsent", "handleToken", "handleRevoke", "GrantInactiveError", "__name", "UpstreamTokenError", "__name", "message", "options", "UpstreamTokenCache", "__name", "#context", "#cache", "#inflight", "#revoking", "context", "grantId", "UPSTREAM_CACHE_GRANTS", "oldest", "forceRefresh", "running", "now", "cached", "hasExpired", "UPSTREAM_EXPIRY_SKEW_MS", "#start", "#load", "after", "#renew", "forced", "work", "promise", "options", "grant", "GrantInactiveError", "#refresh", "#remember", "token", "stored", "#fromGrant", "UpstreamTokenError", "refreshed", "refreshToken", "error", "expiresIn", "UPSTREAM_LIFETIME_CEILING_SECONDS", "expiresAt", "createMcpAuth", "options", "resolved", "resolveOptions", "defaultConsentPage", "defaultErrorPage", "context", "createContext", "upstream", "UpstreamTokenCache", "revoke", "__name", "grantId", "reason", "revokeGrantById", "id", "routes", "buildRoutes", "secured", "response", "withTransportSecurity", "route", "request", "entry", "preflight", "methodNotAllowed", "result", "authenticateRequest", "forceRefresh", "_request", "error", "bearerChallenge", "data", "copy", "jsonCopy", "subject", "store", "McpAuthConfigError", "grants", "ended", "grant", "createCipheriv", "createDecipheriv", "randomBytes", "SealerError", "__name", "message", "IV_BYTES", "TAG_BYTES", "KID_PATTERN", "FORMAT", "attempt", "work", "error", "SealerError", "__name", "additionalData", "kid", "aad", "createAesGcmSealer", "options", "keys", "key", "current", "currentKid", "plaintext", "iv", "randomBytes", "cipher", "createCipheriv", "body", "sealed", "parts", "ivText", "bodyText", "tagText", "tag", "decipher", "createDecipheriv", "IdentityProviderError", "__name", "message", "code", "options", "createRemoteJWKSet", "customFetch", "jwtVerify", "DEFAULT_ALGORITHMS", "RESERVED_PARAMS", "optionalString", "value", "__name", "isDiscovery", "isRecord", "isStringArray", "isTokenResponse", "checkEndpoint", "name", "insecure", "url", "IdentityProviderError", "local", "LOOPBACK_HOSTS", "createOidcProvider", "options", "insecure", "timeoutMs", "doFetch", "algorithms", "DEFAULT_ALGORITHMS", "alg", "key", "RESERVED_PARAMS", "checkEndpoint", "error", "timedFetch", "__name", "url", "init", "discovery", "discover", "running", "base", "trimTrailing", "response", "IdentityProviderError", "document", "isDiscovery", "jwksUrl", "jwks", "createRemoteJWKSet", "customFetch", "clientAuth", "body", "methods", "user", "pass", "postToken", "headers", "parsed", "code", "isRecord", "isTokenResponse", "verifyIdToken", "idToken", "nonce", "payload", "jwtVerify", "state", "codeChallenge", "redirectUri", "value", "codeVerifier", "issuer", "tokens", "claims", "subject", "displayName", "refreshToken", "ClientCollection", "__name", "#held", "#idle", "#grantCounts", "#max", "#idleMs", "#now", "max", "idleMs", "now", "#toIdle", "client", "oldest", "clientId", "held", "idle", "hasExpired", "count", "Collection", "__name", "#items", "#max", "#expiresAt", "#now", "#onRemove", "max", "now", "expiresAt", "onRemove", "#remove", "key", "value", "hasExpired", "previous", "oldest", "includeExpired", "test", "expiry", "__name", "value", "settle", "work", "error", "createMemoryStore", "options", "max", "DEFAULT_MEMORY_STORE_ENTRIES", "now", "checkedClock", "clients", "ClientCollection", "DEFAULT_DYNAMIC_CLIENT_IDLE_SECONDS", "metadata", "Collection", "pending", "consents", "outcomes", "codes", "grants", "grant", "access", "refresh", "clientId", "client", "url", "expiresAt", "record", "id", "key", "sealedLocation", "subject", "g", "codeHash", "entry", "result", "grantId", "update", "removed", "token", "code", "tokenHash", "usedAt", "marked", "sealedReplacement"]
7
7
  }