/** * @typedef {Object} IdTokenSignerConfig * @property {import('node:crypto').KeyObject | string | Uint8Array} signingKey private (asymmetric) signing key * @property {string} alg JWS alg, e.g. `'RS256'` / `'ES256'` / `'EdDSA'` (never `none`/HS*) * @property {string} [kid] `kid` header so a client picks the right JWKS key * @property {string | number} [expiresIn] id_token lifetime (default `'10m'`) */ /** * @param {IdTokenSignerConfig} config * @returns {{ alg: string, sign: (input: IdTokenInput) => Promise }} * * @typedef {Object} IdTokenInput * @property {string} subject the authenticated resource owner → `sub` * @property {string} clientId → `aud` * @property {string} issuer the AS issuer identifier → `iss` * @property {string} [nonce] the authorization-request `nonce` (replay guard) * @property {number} [authTime] unix seconds of the authentication event → `auth_time` * @property {string} [accessToken] present → bind it via `at_hash` */ declare function createIdTokenSigner(config: IdTokenSignerConfig): { alg: string; sign: (input: IdTokenInput) => Promise; }; type IdTokenSignerConfig = { /** * private (asymmetric) signing key */ signingKey: any | string | Uint8Array; /** * JWS alg, e.g. `'RS256'` / `'ES256'` / `'EdDSA'` (never `none`/HS*) */ alg: string; /** * `kid` header so a client picks the right JWKS key */ kid?: string | undefined; /** * id_token lifetime (default `'10m'`) */ expiresIn?: string | number | undefined; }; type IdTokenInput = { /** * the authenticated resource owner → `sub` */ subject: string; /** * → `aud` */ clientId: string; /** * the AS issuer identifier → `iss` */ issuer: string; /** * the authorization-request `nonce` (replay guard) */ nonce?: string | undefined; /** * unix seconds of the authentication event → `auth_time` */ authTime?: number | undefined; /** * present → bind it via `at_hash` */ accessToken?: string | undefined; }; /** * Validate + freeze a client descriptor. * * @param {ClientConfig} config * @returns {Client} */ declare function defineClient(config: ClientConfig): Client; /** * @typedef {Object} ClientRegistry * @property {(clientId: string) => (Client | undefined) | Promise} getClient */ /** * Build an in-memory registry from a static client list. * * @param {ClientConfig[]} clients * @returns {ClientRegistry} */ declare function createClientRegistry(clients: ClientConfig[]): ClientRegistry; /** * Token-endpoint client-authentication methods this server understands * (RFC 6749 §2.3, RFC 7523, RFC 8705). `none` is a public client * (RFC 6749 §2.1) — allowed only because PKCE is mandatory, so a public * client is still protected against code interception. */ declare const AUTH_METHODS: readonly string[]; type ClientRegistry = { getClient: (clientId: string) => (Client | undefined) | Promise; }; type ClientConfig = { clientId: string; /** * confidential clients only */ clientSecret?: string | undefined; /** * exact-match allowlist (RFC 6749 §3.1.2) */ redirectUris: string[]; /** * default `['authorization_code','refresh_token']` */ grantTypes?: string[] | undefined; /** * default `['code']` */ responseTypes?: string[] | undefined; /** * one of {@link AUTH_METHODS} */ tokenEndpointAuthMethod?: string | undefined; /** * scopes the client may request (omit = server default) */ scope?: string[] | undefined; /** * for `private_key_jwt` / signed request objects */ jwksUri?: string | undefined; /** * inline JWKS alternative to `jwksUri` */ jwks?: object | undefined; /** * require DPoP for this client (RFC 9449 §5.2) */ dpopBoundAccessTokens?: boolean | undefined; /** * require PAR (RFC 9126 §2) */ requirePushedAuthorizationRequests?: boolean | undefined; /** * expected cert subject DN for `tls_client_auth` (RFC 8705 §2.1) */ tlsClientAuthSubjectDn?: string | undefined; /** * base64url `x5t#S256` for `self_signed_tls_client_auth` (RFC 8705 §2.2) */ certificateThumbprint?: string | undefined; }; type Client = Readonly; /** * @typedef {Object} AuthCodeRecord * @property {string} clientId * @property {string} redirectUri * @property {string} codeChallenge * @property {string[]} scope * @property {string} [nonce] * @property {string} [subject] * @property {number} [authTime] unix seconds of the authentication event → OIDC `auth_time` * @property {string} [resource] * @property {unknown} [authorizationDetails] * @property {string} [dpopJkt] DPoP `jkt` bound at the authorize step (RFC 9449 §10) * * @typedef {Object} AuthCodeStore * @property {(code: string, record: AuthCodeRecord, ttlMs: number) => void | Promise} save * @property {(code: string) => (AuthCodeRecord | undefined) | Promise} consume */ /** @returns {AuthCodeStore} */ declare function createAuthCodeStore(): AuthCodeStore; /** * @typedef {Object} RefreshRecord * @property {string} token the opaque refresh token (primary key) * @property {string} familyId shared across every rotation of one grant * @property {string} clientId * @property {string[]} scope * @property {string} [subject] * @property {string} [resource] * @property {string} [dpopJkt] * @property {number} [expiresAt] epoch ms after which the token is expired * @property {boolean} [used] set when rotated away — a later presentation is reuse * @property {boolean} [revoked] set when the whole family was burned (reuse detected) * * @typedef {Object} RefreshStore * @property {(record: RefreshRecord) => Promise} save * @property {(token: string) => Promise} get * @property {(oldToken: string, next: RefreshRecord) => Promise} rotate * @property {(familyId: string) => Promise} revokeFamily */ /** @returns {RefreshStore} */ declare function createRefreshStore(): RefreshStore; /** * @typedef {Object} ParStore * @property {(requestUri: string, params: Record, ttlMs: number) => void | Promise} save * @property {(requestUri: string) => (Record | undefined) | Promise | undefined>} consume */ /** @returns {ParStore} */ declare function createParStore(): ParStore; /** * @typedef {Object} DeviceRecord * @property {string} deviceCode * @property {string} userCode * @property {string} clientId * @property {string[]} scope * @property {'pending' | 'approved' | 'denied' | 'redeemed'} status * @property {string} [subject] set on approval * @property {number} [lastPolledAt] for slow_down enforcement * * @typedef {Object} DeviceStore * @property {(record: DeviceRecord, ttlMs: number) => void | Promise} save * @property {(deviceCode: string) => (DeviceRecord | undefined) | Promise} getByDeviceCode * @property {(userCode: string) => (DeviceRecord | undefined) | Promise} getByUserCode * @property {(deviceCode: string, patch: Partial) => void | Promise} update */ /** @returns {DeviceStore} */ declare function createDeviceStore(): DeviceStore; type AuthCodeRecord = { clientId: string; redirectUri: string; codeChallenge: string; scope: string[]; nonce?: string | undefined; subject?: string | undefined; /** * unix seconds of the authentication event → OIDC `auth_time` */ authTime?: number | undefined; resource?: string | undefined; authorizationDetails?: unknown; /** * DPoP `jkt` bound at the authorize step (RFC 9449 §10) */ dpopJkt?: string | undefined; }; type AuthCodeStore = { save: (code: string, record: AuthCodeRecord, ttlMs: number) => void | Promise; consume: (code: string) => (AuthCodeRecord | undefined) | Promise; }; type RefreshRecord = { /** * the opaque refresh token (primary key) */ token: string; /** * shared across every rotation of one grant */ familyId: string; clientId: string; scope: string[]; subject?: string | undefined; resource?: string | undefined; dpopJkt?: string | undefined; /** * epoch ms after which the token is expired */ expiresAt?: number | undefined; /** * set when rotated away — a later presentation is reuse */ used?: boolean | undefined; /** * set when the whole family was burned (reuse detected) */ revoked?: boolean | undefined; }; type RefreshStore = { save: (record: RefreshRecord) => Promise; get: (token: string) => Promise; rotate: (oldToken: string, next: RefreshRecord) => Promise; revokeFamily: (familyId: string) => Promise; }; type ParStore = { save: (requestUri: string, params: Record, ttlMs: number) => void | Promise; consume: (requestUri: string) => (Record | undefined) | Promise | undefined>; }; type DeviceRecord = { deviceCode: string; userCode: string; clientId: string; scope: string[]; status: "pending" | "approved" | "denied" | "redeemed"; /** * set on approval */ subject?: string | undefined; /** * for slow_down enforcement */ lastPolledAt?: number | undefined; }; type DeviceStore = { save: (record: DeviceRecord, ttlMs: number) => void | Promise; getByDeviceCode: (deviceCode: string) => (DeviceRecord | undefined) | Promise; getByUserCode: (userCode: string) => (DeviceRecord | undefined) | Promise; update: (deviceCode: string, patch: Partial) => void | Promise; }; type RawRequest = { method?: string | undefined; /** * full URL or path (`?query` parsed when `query` is absent) */ url?: string | undefined; headers?: Record | undefined; /** * pre-parsed query, else derived from `url` */ query?: Record | undefined; /** * raw string or a framework-parsed object */ body?: string | Record | undefined; /** * mTLS client cert (RFC 8705) */ clientCertificate?: any | Record; }; type ServerRequest = { method: string; /** * lower-cased names */ headers: Record; query: Record; /** * parsed POST body params */ form: Record; header: (name: string) => string | undefined; /** * query ∪ form (form wins) */ param: (name: string) => string | undefined; clientCertificate: object | undefined; }; /** * Shared base error class — the single error structure behind every * `@exortek/*` package's `errors.js`. * * Every package keeps its own class identity with a one-liner subclass; * codes stay per-package frozen maps, status mapping is declared as a * static field: * * import { BaseError } from '@exortek/shared/errors'; * * export const ErrorCode = Object.freeze({ * INVALID_ARGUMENT: 'INVALID_ARGUMENT', * INVALID_TOKEN: 'INVALID_TOKEN', * }); * * export class JwtError extends BaseError { * static statuses = { INVALID_ARGUMENT: 400, INVALID_TOKEN: 401 }; * static defaultStatus = 500; * } * * Instances carry a stable machine-readable `code` (branch on this, * never on the message), an optional HTTP `status`, an optional * `details` object, and the standard `cause` chain. */ declare class BaseError extends Error { /** * Optional `code → HTTP status` map declared on the subclass. When * absent the instance carries no `status` at all — for HTTP-agnostic * packages like `@exortek/crypto`. * * @type {Record | undefined} */ static statuses: Record | undefined; /** * Fallback status for codes missing from `statuses`. * * @type {number} */ static defaultStatus: number; /** * @param {string} code Stable machine-readable code; branch on this. * @param {string} message Human-readable diagnostic. Free-form; may * change across versions. * @param {{ cause?: unknown, status?: number, details?: Record }} [options] */ constructor(code: string, message: string, options?: { cause?: unknown; status?: number; details?: Record; }); /** @type {string} */ code: string; /** @type {number | undefined} */ status: number | undefined; /** @type {Record | undefined} */ details: Record | undefined; } /** * Every OAuth 2.0 protocol `error` value this server can emit. Each is * thrown by the handler noted alongside it (no dead codes). */ declare const ProtocolError: Readonly<{ INVALID_REQUEST: "invalid_request"; INVALID_CLIENT: "invalid_client"; INVALID_GRANT: "invalid_grant"; UNAUTHORIZED_CLIENT: "unauthorized_client"; UNSUPPORTED_GRANT_TYPE: "unsupported_grant_type"; UNSUPPORTED_RESPONSE_TYPE: "unsupported_response_type"; INVALID_SCOPE: "invalid_scope"; ACCESS_DENIED: "access_denied"; SERVER_ERROR: "server_error"; TEMPORARILY_UNAVAILABLE: "temporarily_unavailable"; UNSUPPORTED_TOKEN_TYPE: "unsupported_token_type"; INVALID_TARGET: "invalid_target"; INVALID_AUTHORIZATION_DETAILS: "invalid_authorization_details"; INVALID_DPOP_PROOF: "invalid_dpop_proof"; USE_DPOP_NONCE: "use_dpop_nonce"; AUTHORIZATION_PENDING: "authorization_pending"; SLOW_DOWN: "slow_down"; EXPIRED_TOKEN: "expired_token"; INVALID_CLIENT_METADATA: "invalid_client_metadata"; INVALID_REDIRECT_URI: "invalid_redirect_uri"; }>; /** * A protocol-level failure destined for the client over the wire. The * `code` is the OAuth `error` value; the `message` becomes * `error_description`. `redirectable` marks errors that belong in an * authorization-endpoint redirect (so the caller echoes `state`), as * opposed to a direct token/introspection/revocation JSON response. * * `invalid_client` is `401` (the client failed to authenticate); every * other protocol error is `400`. The authorization-endpoint redirect * errors never surface as an HTTP status — they ride the `Location` * query — so their `400` only applies when there is no usable * `redirect_uri` to bounce back to (see `response.js`). */ declare class ServerError extends BaseError { static statuses: { invalid_client: number; }; /** * @param {string} code a {@link ProtocolError} value * @param {string} message human-readable `error_description` * @param {{ cause?: unknown, status?: number, redirectable?: boolean, errorUri?: string, state?: string, headers?: Record }} [options] */ constructor(code: string, message: string, options?: { cause?: unknown; status?: number; redirectable?: boolean; errorUri?: string; state?: string; headers?: Record; }); /** @type {boolean} the error belongs in an authorize redirect, not a JSON body */ redirectable: boolean; /** @type {string | undefined} RFC 6749 `error_uri` */ errorUri: string | undefined; /** @type {string | undefined} `state` echoed on a redirect error */ state: string | undefined; /** @type {Record | undefined} extra response headers (e.g. `DPoP-Nonce`, `WWW-Authenticate`) */ headers: Record | undefined; } type ServerResponse = { status: number; headers: Record; body: string; }; /** * @typedef {Object} JwtIssuerConfig * @property {import('node:crypto').KeyObject | string | Uint8Array} signingKey private (asymmetric) or shared (HMAC) key * @property {import('node:crypto').KeyObject | string | Uint8Array} [verificationKey] public key for introspection; defaults to `signingKey` (HMAC) * @property {string} alg JWS alg, e.g. `'RS256'` / `'ES256'` / `'EdDSA'` * @property {string | number} [expiresIn] access-token lifetime (default `'10m'`) * @property {string} [kid] `kid` header, so a resource server can pick the key * * @typedef {Object} GrantContext * @property {string} issuer the AS issuer identifier (→ `iss`) * * @typedef {Object} AccessGrant * @property {string} subject resource owner (or client id for client_credentials) → `sub` * @property {string} clientId → `client_id` * @property {string[]} [scope] → space-delimited `scope` * @property {string | string[]} [audience] resource indicator(s) (RFC 8707) → `aud`; falls back to `clientId` * @property {string} [dpopJkt] DPoP key thumbprint → `cnf.jkt` (RFC 9449) * @property {Record} [extra] additional claims (e.g. `authorization_details`) */ /** * @param {JwtIssuerConfig} config */ declare function jwtIssuer(config: JwtIssuerConfig): { /** * Mint an RFC 9068 JWT access token. * * @param {AccessGrant} grant * @param {GrantContext} ctx * @returns {Promise<{ accessToken: string, tokenType: string, expiresIn: number, jti: string }>} */ issue(grant: AccessGrant, ctx: GrantContext): Promise<{ accessToken: string; tokenType: string; expiresIn: number; jti: string; }>; /** * Verify a JWT access token for introspection (RFC 7662). A bad * signature / expired token is simply `{ active: false }` — never a * thrown error, since introspection reports inactivity, not failure. * * @param {string} token * @param {{ issuer: string }} ctx * @returns {Promise<{ active: boolean, claims?: Record }>} */ introspect(token: string, ctx: { issuer: string; }): Promise<{ active: boolean; claims?: Record; }>; }; type JwtIssuerConfig = { /** * private (asymmetric) or shared (HMAC) key */ signingKey: any | string | Uint8Array; /** * public key for introspection; defaults to `signingKey` (HMAC) */ verificationKey?: any | string | Uint8Array; /** * JWS alg, e.g. `'RS256'` / `'ES256'` / `'EdDSA'` */ alg: string; /** * access-token lifetime (default `'10m'`) */ expiresIn?: string | number | undefined; /** * `kid` header, so a resource server can pick the key */ kid?: string | undefined; }; type GrantContext = { /** * the AS issuer identifier (→ `iss`) */ issuer: string; }; type AccessGrant = { /** * resource owner (or client id for client_credentials) → `sub` */ subject: string; /** * → `client_id` */ clientId: string; /** * → space-delimited `scope` */ scope?: string[] | undefined; /** * resource indicator(s) (RFC 8707) → `aud`; falls back to `clientId` */ audience?: string | string[] | undefined; /** * DPoP key thumbprint → `cnf.jkt` (RFC 9449) */ dpopJkt?: string | undefined; /** * additional claims (e.g. `authorization_details`) */ extra?: Record | undefined; }; /** * @typedef {Object} PasetoIssuerConfig * @property {import('node:crypto').KeyObject | Uint8Array} secretKey Ed25519 secret (signing) * @property {import('node:crypto').KeyObject | Uint8Array} publicKey Ed25519 public (introspection) * @property {string | number} [expiresIn] access-token lifetime (default `'10m'`) */ /** * @param {PasetoIssuerConfig} config */ declare function pasetoIssuer(config: PasetoIssuerConfig): { /** * @param {import('./jwt.js').AccessGrant} grant * @param {import('./jwt.js').GrantContext} ctx * @returns {Promise<{ accessToken: string, tokenType: string, expiresIn: number, jti: string }>} */ issue(grant: AccessGrant, ctx: GrantContext): Promise<{ accessToken: string; tokenType: string; expiresIn: number; jti: string; }>; /** * @param {string} token * @param {{ issuer: string }} ctx * @returns {Promise<{ active: boolean, claims?: Record }>} */ introspect(token: string, ctx: { issuer: string; }): Promise<{ active: boolean; claims?: Record; }>; }; type PasetoIssuerConfig = { /** * Ed25519 secret (signing) */ secretKey: any | Uint8Array; /** * Ed25519 public (introspection) */ publicKey: any | Uint8Array; /** * access-token lifetime (default `'10m'`) */ expiresIn?: string | number | undefined; }; /** * Resource-server DPoP check (RFC 9449 §7). A protected resource calls * this with the `DPoP` proof header, the presented access token, and the * token's `cnf.jkt` (from the JWT claim or introspection). It verifies the * proof, that its `ath` binds to this exact token, and that the proving * key is the one the token was issued to — the missing high-level helper * for the resource-server half of DPoP. * * @param {string} proof the `DPoP` request header * @param {{ htm: string, htu: string, accessToken: string, cnfJkt: string }} binding * @returns {Promise<{ jkt: string }>} */ declare function verifyDpopForResource(proof: string, binding: { htm: string; htu: string; accessToken: string; cnfJkt: string; }): Promise<{ jkt: string; }>; /** * @param {any} client * @param {{ keyPrefix?: string }} [options] * @returns {import('../stores.js').AuthCodeStore} */ declare function createRedisAuthCodeStore(client: any, options?: { keyPrefix?: string; }): AuthCodeStore; /** * @param {any} client * @param {{ keyPrefix?: string }} [options] * @returns {import('../stores.js').ParStore} */ declare function createRedisParStore(client: any, options?: { keyPrefix?: string; }): ParStore; /** * Refresh family with rotation + reuse detection over the shared * record-store idiom. A revocation is a tombstone (never a plain update) * so a concurrent rotation on another node cannot silently un-revoke the * family. Records carry `expiresAt`, mirrored to a Redis TTL. * * @param {any} client * @param {{ keyPrefix?: string }} [options] * @returns {import('../stores.js').RefreshStore} */ declare function createRedisRefreshStore(client: any, options?: { keyPrefix?: string; }): RefreshStore; /** * Device flow (RFC 8628): the `device_code` record plus a `user_code` → * `device_code` reverse pointer, each on its own Redis TTL. * * @param {any} client * @param {{ keyPrefix?: string }} [options] * @returns {import('../stores.js').DeviceStore} */ declare function createRedisDeviceStore(client: any, options?: { keyPrefix?: string; }): DeviceStore; /** * PKCE S256 is always required on the code grant (OAuth 2.1) — there is no * off-switch, so `requirePkce` is intentionally not a knob here. * * @typedef {Object} ServerSecurity * @property {string|number} [authorizationCodeTtl='1m'] * @property {string|number} [refreshTokenTtl='30d'] lifetime of an issued refresh token * @property {{ required?: boolean, algs?: string[], nonce?: boolean }} [dpop] RFC 9449 * @property {{ required?: boolean, ttl?: string|number }} [par] RFC 9126 * @property {boolean} [fapi] FAPI 2.0 profile (bundles PAR + PKCE + DPoP/mTLS + iss) * @property {(hostname: string, url: URL) => boolean} [allowJwksHost] gate the hosts the server will fetch a client's `jwks_uri` from; return false to refuse. Recommended wherever dynamic client registration is enabled, since the URI is then chosen by the registrant. * * @typedef {Object} ServerConfig * @property {string} issuer AS issuer identifier (https, no trailing slash) * @property {import('./clients.js').ClientConfig[] | import('./clients.js').ClientRegistry} clients * @property {{ issue: Function, introspect: Function }} tokens an issuer strategy (jwtIssuer / pasetoIssuer) * @property {string} [jwksUri] where the AS publishes its signing JWKS (advertised only) * @property {(req: import('./request.js').ServerRequest) => (UserAuthn | null) | Promise} authenticateUser * @property {string[]} [grants] allowed grant types (default code + refresh + client_credentials) * @property {string[]} [scopes] supported scopes (advertised; also the escalation ceiling) * @property {Partial} [endpoints] override the endpoint URLs advertised in metadata * @property {Partial} [stores] swap in Redis-backed stores * @property {ServerSecurity} [security] * @property {OidcConfig} [oidc] make this an OpenID Provider (issue id_token on the `openid` scope) * @property {RegistrationConfig} [registration] enable RFC 7591 dynamic client registration (opt-in) * * @typedef {Object} RegistrationConfig * @property {string} [initialAccessToken] bearer required to register (secure default; omit only with `open`) * @property {boolean} [open] allow unauthenticated registration (NOT recommended) * @property {(req: import('./request.js').ServerRequest) => boolean | Promise} [authorize] custom gate * @property {Partial} [defaults] defaults layered under each registration * * @typedef {Object} OidcConfig * @property {import('./issuers/id-token.js').IdTokenSignerConfig} idToken id_token JWS signer (signingKey + alg) * @property {string} [userinfoEndpoint] advertised in metadata as `userinfo_endpoint` * @property {string[]} [claimsSupported] advertised as `claims_supported` * @property {string[]} [subjectTypes] advertised as `subject_types_supported` (default `['public']`) * * @typedef {Object} UserAuthn * @property {string} subject resource-owner identifier → `sub` * @property {number} [authTime] unix seconds of the authentication event (`auth_time`) * * @typedef {Object} Endpoints * @property {string} authorization * @property {string} token * @property {string} [introspection] * @property {string} [revocation] * @property {string} [par] * @property {string} [deviceAuthorization] * * @typedef {Object} ServerStores * @property {ReturnType} authCode * @property {ReturnType} refresh * @property {ReturnType} par * @property {ReturnType} device * * @typedef {Object} ResolvedServerConfig * @property {string} issuer * @property {Endpoints} endpoints * @property {string} [jwksUri] * @property {string[]} grantTypes * @property {string[]} [scopes] * @property {string[]} authMethods * @property {string[]} [dpopAlgs] * @property {boolean} requirePar */ /** * @param {ServerConfig} config */ declare function createServer(config: ServerConfig): { /** RFC 8414 authorization-server metadata document. */ metadata: () => ServerResponse; /** Authorization endpoint (RFC 6749 §4.1.1) — code grant, PKCE-required. */ authorize: (raw: RawRequest) => Promise; /** Token endpoint (RFC 6749 §3.2) — code / refresh / client_credentials grants. */ token: (raw: RawRequest) => Promise; /** Token revocation endpoint (RFC 7009). */ revoke: (raw: RawRequest) => Promise; /** Token introspection endpoint (RFC 7662). */ introspect: (raw: RawRequest) => Promise; /** Pushed authorization request endpoint (RFC 9126). */ par: (raw: RawRequest) => Promise; /** Device authorization endpoint (RFC 8628). */ deviceAuthorization: (raw: RawRequest) => Promise; /** Dynamic client registration endpoint (RFC 7591) — opt-in. */ register: (raw: RawRequest) => Promise; /** * Device-approval API for the host's verification page — look up a * pending request by `user_code`, then `approve({ subject })` or * `deny()` it (RFC 8628 §3.3). */ device: { getByUserCode(userCode: string): Promise; approve(userCode: string, approval: { subject: string; }): Promise; deny(userCode: string): Promise; }; /** @internal exposed for tests and the framework adapters */ _config: ResolvedServerConfig & { registry: any; tokens: any; stores: any; security: any; requirePkce: any; requirePar: any; dpopRequired: any; authenticateUser: any; authorizationCodeTtlMs: any; parTtlMs: any; fapi: any; }; }; /** * PKCE S256 is always required on the code grant (OAuth 2.1) — there is no * off-switch, so `requirePkce` is intentionally not a knob here. */ type ServerSecurity = { authorizationCodeTtl?: string | number | undefined; /** * lifetime of an issued refresh token */ refreshTokenTtl?: string | number | undefined; /** * RFC 9449 */ dpop?: { required?: boolean; algs?: string[]; nonce?: boolean; } | undefined; /** * RFC 9126 */ par?: { required?: boolean; ttl?: string | number; } | undefined; /** * FAPI 2.0 profile (bundles PAR + PKCE + DPoP/mTLS + iss) */ fapi?: boolean | undefined; /** * gate the hosts the server will fetch a client's `jwks_uri` from; return false to refuse. Recommended wherever dynamic client registration is enabled, since the URI is then chosen by the registrant. */ allowJwksHost?: ((hostname: string, url: URL) => boolean) | undefined; }; /** * PKCE S256 is always required on the code grant (OAuth 2.1) — there is no * off-switch, so `requirePkce` is intentionally not a knob here. */ type ServerConfig = { /** * AS issuer identifier (https, no trailing slash) */ issuer: string; clients: ClientConfig[] | ClientRegistry; /** * an issuer strategy (jwtIssuer / pasetoIssuer) */ tokens: { issue: Function; introspect: Function; }; /** * where the AS publishes its signing JWKS (advertised only) */ jwksUri?: string | undefined; authenticateUser: (req: ServerRequest) => (UserAuthn | null) | Promise; /** * allowed grant types (default code + refresh + client_credentials) */ grants?: string[] | undefined; /** * supported scopes (advertised; also the escalation ceiling) */ scopes?: string[] | undefined; /** * override the endpoint URLs advertised in metadata */ endpoints?: Partial | undefined; /** * swap in Redis-backed stores */ stores?: Partial | undefined; security?: ServerSecurity | undefined; /** * make this an OpenID Provider (issue id_token on the `openid` scope) */ oidc?: OidcConfig | undefined; /** * enable RFC 7591 dynamic client registration (opt-in) */ registration?: RegistrationConfig | undefined; }; /** * PKCE S256 is always required on the code grant (OAuth 2.1) — there is no * off-switch, so `requirePkce` is intentionally not a knob here. */ type RegistrationConfig = { /** * bearer required to register (secure default; omit only with `open`) */ initialAccessToken?: string | undefined; /** * allow unauthenticated registration (NOT recommended) */ open?: boolean | undefined; /** * custom gate */ authorize?: ((req: ServerRequest) => boolean | Promise) | undefined; /** * defaults layered under each registration */ defaults?: Partial | undefined; }; /** * PKCE S256 is always required on the code grant (OAuth 2.1) — there is no * off-switch, so `requirePkce` is intentionally not a knob here. */ type OidcConfig = { /** * id_token JWS signer (signingKey + alg) */ idToken: IdTokenSignerConfig; /** * advertised in metadata as `userinfo_endpoint` */ userinfoEndpoint?: string | undefined; /** * advertised as `claims_supported` */ claimsSupported?: string[] | undefined; /** * advertised as `subject_types_supported` (default `['public']`) */ subjectTypes?: string[] | undefined; }; /** * PKCE S256 is always required on the code grant (OAuth 2.1) — there is no * off-switch, so `requirePkce` is intentionally not a knob here. */ type UserAuthn = { /** * resource-owner identifier → `sub` */ subject: string; /** * unix seconds of the authentication event (`auth_time`) */ authTime?: number | undefined; }; /** * PKCE S256 is always required on the code grant (OAuth 2.1) — there is no * off-switch, so `requirePkce` is intentionally not a knob here. */ type Endpoints = { authorization: string; token: string; introspection?: string | undefined; revocation?: string | undefined; par?: string | undefined; deviceAuthorization?: string | undefined; }; /** * PKCE S256 is always required on the code grant (OAuth 2.1) — there is no * off-switch, so `requirePkce` is intentionally not a knob here. */ type ServerStores = { authCode: ReturnType; refresh: ReturnType; par: ReturnType; device: ReturnType; }; /** * PKCE S256 is always required on the code grant (OAuth 2.1) — there is no * off-switch, so `requirePkce` is intentionally not a knob here. */ type ResolvedServerConfig = { issuer: string; endpoints: Endpoints; jwksUri?: string | undefined; grantTypes: string[]; scopes?: string[] | undefined; authMethods: string[]; dpopAlgs?: string[] | undefined; requirePar: boolean; }; export { AUTH_METHODS, ProtocolError, ServerError, createClientRegistry, createIdTokenSigner, createRedisAuthCodeStore, createRedisDeviceStore, createRedisParStore, createRedisRefreshStore, createServer, defineClient, jwtIssuer, pasetoIssuer, verifyDpopForResource }; export type { Endpoints, OidcConfig, RegistrationConfig, ResolvedServerConfig, ServerConfig, ServerSecurity, ServerStores, UserAuthn };