@web-ts-toolkit/express-oidc-vault 0.42.2 → 0.47.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (6) hide show
  1. package/README.md +261 -51
  2. package/index.d.mts +300 -9
  3. package/index.d.ts +300 -9
  4. package/index.js +619 -194
  5. package/index.mjs +620 -195
  6. package/package.json +2 -2
package/index.d.ts CHANGED
@@ -10,6 +10,17 @@ interface OidcVaultUserProfile {
10
10
  preferredUsername?: string;
11
11
  [key: string]: unknown;
12
12
  }
13
+ /**
14
+ * Stored session identity. Exchange, refresh, and live-session logout compare
15
+ * each defined field verbatim against resolved middleware config before use.
16
+ * Known mismatches fail with 401 `OIDC_VAULT_INVALID_SESSION` without changing
17
+ * the session or cookie. Issuer trailing-slash variants are distinct.
18
+ *
19
+ * Omitted fields remain legacy-compatible and are not backfilled by refresh.
20
+ * Complete isolation requires separate session/alias, exchange-code, and
21
+ * transaction namespaces: exchange spends its code before this check, and
22
+ * logout's stale-alias path has no live identity to check.
23
+ */
13
24
  interface OidcVaultProviderMetadata {
14
25
  issuer?: string;
15
26
  clientId?: string;
@@ -35,11 +46,21 @@ interface OidcVaultSession {
35
46
  * This controls the lifetime of the refresh-token-backed server-side session.
36
47
  * It is not derived from upstream OAuth `expires_in`, which only describes the
37
48
  * upstream access token lifetime. Leave unset when your application or store
38
- * owns session lifetime through another policy.
49
+ * owns session lifetime through another policy. `sessionTtlMs` assigns an
50
+ * absolute maximum at callback creation; a precreate hook may shorten it.
51
+ * Refresh preserves this timestamp rather than renewing the lifetime.
39
52
  */
40
53
  expiresAt?: number;
41
54
  createdAt: number;
42
55
  updatedAt: number;
56
+ /**
57
+ * Verified session profile. On refresh with a new `id_token`, the profile
58
+ * is rebuilt from fresh verified ID claims overlaid with freshly fetched
59
+ * matching UserInfo; retained values are never carried forward, so removed
60
+ * provider claims disappear. Application custom attributes belong in
61
+ * `metadata`, not in `user`. Without a new `id_token`, the retained
62
+ * profile is kept verbatim (fresh UserInfo still overlays per key).
63
+ */
43
64
  user?: OidcVaultUserProfile;
44
65
  /**
45
66
  * Store-portable application metadata.
@@ -101,6 +122,21 @@ interface ConsumeBackchannelLogoutTokenJtiInput {
101
122
  declare class OidcVaultStoreConflictError extends Error {
102
123
  constructor(message?: string);
103
124
  }
125
+ /**
126
+ * Portable store boundary for JSON-compatible plain objects/arrays and scalar
127
+ * values (strings, finite numbers, booleans, null). Built-in providers capture
128
+ * inputs at invocation before asynchronous work; returned portable data is
129
+ * detached from caller input and stored state. Native BSON/JSON/structured-clone
130
+ * values outside that subset retain provider-specific serialization semantics;
131
+ * opaque native objects have no cross-provider mutation-isolation guarantee.
132
+ *
133
+ * Bulk deletion counts primary records removed, never alias cleanup. Memory
134
+ * excludes expired sessions; MongoDB can count expired rows awaiting TTL cleanup;
135
+ * Redis counts actual primary deletions during a cursor traversal. Counts are not
136
+ * proof that no matches remain: concurrent arrivals can survive. MongoDB scoped
137
+ * deletion repeats until an empty query; Redis traverses once. Neither provides
138
+ * a global logout snapshot, and errors can follow earlier committed deletions.
139
+ */
104
140
  interface OidcVaultStoreProvider {
105
141
  /** Upsert an authorization transaction by `state`. */
106
142
  createAuthorizationTransaction(input: AuthorizationTransactionInput): Promise<void>;
@@ -108,29 +144,80 @@ interface OidcVaultStoreProvider {
108
144
  /** Upsert an exchange code record by `code`. */
109
145
  createExchangeCode(input: ExchangeCodeRecordInput): Promise<void>;
110
146
  consumeExchangeCode(code: string): Promise<ExchangeCodeRecord | null>;
111
- /** Upsert a session by `sessionId`, defaulting timestamps and logical lineage when omitted. */
147
+ /**
148
+ * Create a session by `sessionId`, defaulting timestamps and logical lineage when omitted.
149
+ *
150
+ * Duplicate-ID behavior is provider-specific (SVH-06): the memory and
151
+ * MongoDB providers replace the existing session (upsert), while the Redis
152
+ * provider rejects a live duplicate with `OidcVaultStoreConflictError`
153
+ * without changing the existing record or indexes (create-only, preserving
154
+ * RVR-02 index ownership). A rejected create preserves the original record
155
+ * and its subject/logical/provider-session index memberships; an accepted
156
+ * replacement takes over the ID with its own subject/logical/provider-session
157
+ * scope. Reusing an ID that holds only a stale rotation alias clears that
158
+ * alias on memory/Redis, while MongoDB can retain the stale alias row until
159
+ * expiry or lineage cleanup (FU-SVH-05b).
160
+ *
161
+ * Portable callers must always create sessions with a fresh unused
162
+ * `sessionId` and handle `OidcVaultStoreConflictError`: reusing a live ID is
163
+ * non-portable (it may replace or reject), and concurrent reuse races
164
+ * rotation source checks (SVH-08). The core middleware only creates fresh
165
+ * random IDs, so it never depends on duplicate-create behavior.
166
+ */
112
167
  createSession(input: OidcVaultSessionInput): Promise<OidcVaultSession>;
113
168
  getSession(sessionId: string): Promise<OidcVaultSession | null>;
114
169
  /**
115
170
  * Atomically replace an existing session with a distinct unused `nextSession.sessionId`.
116
171
  *
117
172
  * Providers preserve the existing logical session ID when the next session omits
118
- * one, retain the old public session ID as a revocation alias while the lineage
119
- * remains live, and throw `OidcVaultStoreConflictError` without changing source
120
- * or target data when the source is missing, the target already exists, or the
121
- * target ID equals the source ID.
173
+ * one and retain the old public session ID as an in-flight-request revocation
174
+ * alias. Rotation itself does not revoke the lineage. Providers throw
175
+ * `OidcVaultStoreConflictError` without changing source or target data when
176
+ * the source is missing, the target already exists, or the target ID equals
177
+ * the source ID.
178
+ *
179
+ * Each alias expires with its immediate successor session's `expiresAt` and is
180
+ * not extended by later rotations, so an earlier alias can stop working while
181
+ * the lineage is still live. A rotation that assigns an explicitly different
182
+ * logical session ID assigns the new source-ID alias to the new lineage;
183
+ * earlier aliases keep the old lineage and deadline if retained. Memory
184
+ * eagerly retires inactive old-lineage aliases on ownership transitions;
185
+ * MongoDB/Redis can retain them until expiry or explicit cleanup. Use distinct
186
+ * logical IDs for unrelated login families. Sessions without `expiresAt` have
187
+ * provider-specific retention: memory and Redis impose no alias time limit, while
188
+ * MongoDB applies its finite `rotatedSessionAliasRetentionMs` fallback
189
+ * (default 5 minutes). Generic callers must treat an expired alias as a hint,
190
+ * not a revocation channel; core refresh rotates the live session directly and
191
+ * preserves `expiresAt`, so it does not depend on alias lifetime (SVH-05).
122
192
  */
123
193
  rotateSession(input: RotateSessionInput): Promise<OidcVaultSession>;
194
+ /**
195
+ * Delete a live public-ID record, or revoke the logical lineage through an
196
+ * unexpired alias when no live record exists. Aliases are not readable through
197
+ * `getSession` and have no issuer/client filter. Scoped/direct deletion preserves
198
+ * unexpired aliases while another live member survives, even in another scope.
199
+ */
124
200
  deleteSession(sessionId: string): Promise<void>;
201
+ /** Revoke a logical lineage without an issuer/client filter; see provider count/concurrency notes. */
125
202
  deleteSessionsByLogicalSessionId(input: string | DeleteSessionsByLogicalSessionIdInput): Promise<number>;
126
203
  /**
127
204
  * Record a backchannel logout JTI once until its finite future expiry.
128
205
  *
129
206
  * Returns `false` for duplicate, expired, exact-boundary, `NaN`, or infinite
130
207
  * expiries. A JTI rejected for invalid or already-expired expiry is not stored.
208
+ *
209
+ * The core middleware (BOV-01) passes an opaque replay key that already
210
+ * namespaces the raw logout-token `jti` by issuer and client ID, so
211
+ * providers must treat `jti` as an opaque string and need no schema change. Pre-BOV-01 raw-`jti` records use
212
+ * a different key shape and expire naturally with the logout-token `exp`;
213
+ * they are never matched by namespaced keys. Retry safety comes from the
214
+ * atomic single-key reservation plus idempotent catch-up deletion in the
215
+ * route handler, not from a multi-key store transaction.
131
216
  */
132
217
  consumeBackchannelLogoutTokenJti(input: ConsumeBackchannelLogoutTokenJtiInput): Promise<boolean>;
218
+ /** Delete matching subject sessions, filtering by each supplied issuer/client; string input is unscoped. */
133
219
  deleteSessionsBySubject(input: string | DeleteSessionsBySubjectInput): Promise<number>;
220
+ /** Delete matching provider-session IDs, filtering by each supplied issuer/client; string input is unscoped. */
134
221
  deleteSessionsByProviderSessionId(input: string | DeleteSessionsByProviderSessionIdInput): Promise<number>;
135
222
  }
136
223
  interface OidcVaultHookContext {
@@ -141,6 +228,11 @@ interface OidcVaultHookContext {
141
228
  metadata?: Record<string, unknown>;
142
229
  }
143
230
  interface OidcVaultErrorContext extends OidcVaultHookContext {
231
+ /**
232
+ * Original private diagnostic, separate from sanitized browser JSON. Narrow
233
+ * before use; provider transport errors retain the original failure in
234
+ * `error.cause`, while issuer-result validation supplies a TypeError directly.
235
+ */
144
236
  error: unknown;
145
237
  }
146
238
  interface OidcVaultHooks {
@@ -148,6 +240,13 @@ interface OidcVaultHooks {
148
240
  onAuthorizationUrl?(context: OidcVaultHookContext): void | Promise<void>;
149
241
  onCallbackTokens?(context: OidcVaultHookContext): void | Promise<void>;
150
242
  onUserInfo?(context: OidcVaultHookContext): void | Promise<void>;
243
+ /**
244
+ * May mutate the session before persistence or veto creation by throwing.
245
+ * With `sessionTtlMs`, the session already carries the absolute expiry.
246
+ * After this hook, a valid earlier epoch-millisecond expiry is preserved;
247
+ * removal, extension, or an invalid timestamp restores the configured cap.
248
+ * Without `sessionTtlMs`, application/store lifetime policy is unchanged.
249
+ */
151
250
  onBeforeSessionCreate?(context: OidcVaultHookContext): void | Promise<void>;
152
251
  onSessionCreated?(context: OidcVaultHookContext): void | Promise<void>;
153
252
  onSessionRefreshed?(context: OidcVaultHookContext): void | Promise<void>;
@@ -160,12 +259,32 @@ interface IssueTokenInput {
160
259
  req: Request;
161
260
  res: Response;
162
261
  }
262
+ /**
263
+ * Local application credentials returned by `OidcVaultTokenIssuer.issue`.
264
+ * Runtime validation copies only these fields into a fresh result; additional
265
+ * properties are ignored and cannot override the response's session ID/user.
266
+ */
163
267
  interface OidcVaultTokenIssueResult {
268
+ /** Nonempty opaque token string, returned verbatim without trimming. */
164
269
  accessToken: string;
270
+ /** Token lifetime in seconds: a finite, nonnegative safe integer (zero is valid). */
165
271
  expiresIn: number;
272
+ /** Optional exact literal `Bearer`; omitted/undefined stays absent in JSON. */
166
273
  tokenType?: 'Bearer';
167
274
  }
275
+ /**
276
+ * Trusted application token issuer for exchange and refresh. Return a non-null,
277
+ * non-array object satisfying `OidcVaultTokenIssueResult`; only its declared
278
+ * credential fields enter the response. This contains accidental extensions,
279
+ * not arbitrary behavior of trusted issuers/hooks with session/request/response access.
280
+ */
168
281
  interface OidcVaultTokenIssuer {
282
+ /**
283
+ * Invalid results fail inside issuance rollback: revoke the session lineage
284
+ * (including the rotated refresh session), clear a cookie-transport session
285
+ * cookie, and return sanitized HTTP 500 / `OIDC_VAULT_INTERNAL_ERROR`.
286
+ * Field diagnostics are available privately through `hooks.onError`.
287
+ */
169
288
  issue(input: IssueTokenInput): Promise<OidcVaultTokenIssueResult>;
170
289
  }
171
290
  interface OidcVaultAccessTokenValidationResult {
@@ -180,13 +299,38 @@ interface OidcVaultAuthContext extends OidcVaultAccessTokenValidationResult {
180
299
  interface OidcVaultAccessTokenValidator {
181
300
  validate(token: string): Promise<OidcVaultAccessTokenValidationResult>;
182
301
  }
302
+ interface OidcVaultAccessTokenMiddlewareErrorContext {
303
+ error: unknown;
304
+ req: Request;
305
+ res: Response;
306
+ token?: string;
307
+ auth?: OidcVaultAuthContext;
308
+ }
183
309
  interface OidcVaultAccessTokenMiddlewareOptions {
184
310
  validator: OidcVaultAccessTokenValidator;
311
+ /**
312
+ * Pre-`next()` veto hook, not a post-commit notification: when it throws,
313
+ * downstream middleware never runs and `req.auth` is detached before the
314
+ * error response is sent. A valid bearer credential plus a failing hook
315
+ * never surfaces as an invalid-token 401: an `OidcVaultHttpError` from the
316
+ * hook keeps its own status/code/client message (a 401 keeps the `Bearer`
317
+ * challenge, other statuses carry no challenge), while any other hook
318
+ * error becomes a sanitized `500 OIDC_VAULT_AUTH_CONTEXT_FAILED` without
319
+ * leaking the original message. The original error is observable via
320
+ * `onError` for private server-side logs.
321
+ */
185
322
  onAuthContext?(input: {
186
323
  req: Request;
187
324
  res: Response;
188
325
  auth: OidcVaultAuthContext;
189
326
  }): void | Promise<void>;
327
+ /**
328
+ * Observes the original error for every bearer-middleware failure
329
+ * (extraction, validator, and `onAuthContext` failures) without affecting
330
+ * the sanitized client response. Failures thrown by this observer are
331
+ * swallowed so the original error response is preserved.
332
+ */
333
+ onError?(context: OidcVaultAccessTokenMiddlewareErrorContext): void | Promise<void>;
190
334
  }
191
335
  interface OidcVaultAuthenticatedRequest extends Request {
192
336
  auth?: OidcVaultAuthContext;
@@ -204,6 +348,19 @@ declare module 'express-serve-static-core' {
204
348
  }
205
349
  }
206
350
  interface OidcVaultConfig {
351
+ /**
352
+ * OIDC issuer identifier, preserved exactly after surrounding-whitespace
353
+ * trimming (no trailing slash is added, so `/tenant`, `/tenant/`, and
354
+ * `/tenant//` remain distinct). Must be an absolute http(s) URL without
355
+ * userinfo, query, or fragment; `http` is accepted for local-test
356
+ * providers. Required alongside `clientId` in discovery and manual modes.
357
+ * Any nonempty endpoint, including `userInfoEndpoint`/`endSessionEndpoint`,
358
+ * selects manual mode: supply `authorizationEndpoint`, `tokenEndpoint`, and
359
+ * `jwksUri`; no discovery or partial overrides. Blank strings are absent
360
+ * after trimming. Discovery responses
361
+ * must carry an exactly equal issuer, and ID/logout tokens are validated
362
+ * against this exact identifier.
363
+ */
207
364
  issuer?: string;
208
365
  authorizationEndpoint?: string;
209
366
  tokenEndpoint?: string;
@@ -215,6 +372,19 @@ interface OidcVaultConfig {
215
372
  scopes?: string;
216
373
  }
217
374
  interface OidcVaultLogoutResult {
375
+ /**
376
+ * Successful live-session logout commits revocation before upstream work
377
+ * (and clears the session cookie under cookie transport). Known foreign
378
+ * live sessions instead fail with 401. Without a live session, success means
379
+ * stale-alias deletion was attempted; an expired alias may no longer revoke
380
+ * a live lineage. Stateless application access tokens are not invalidated.
381
+ * Local-only logout (`redirect` unset or `false`) never contacts the provider.
382
+ * Redirected logout (`redirect: true`)
383
+ * treats the upstream end-session redirect as best-effort: when provider
384
+ * discovery fails or no `endSessionEndpoint` is available, the route still
385
+ * returns this local success. Discovery errors reach `onError` without
386
+ * undoing revocation or skipping the live session's `onLogout` notification.
387
+ */
218
388
  loggedOut: true;
219
389
  }
220
390
  interface OidcVaultBackchannelLogoutResult {
@@ -222,19 +392,51 @@ interface OidcVaultBackchannelLogoutResult {
222
392
  revokedSessions: number;
223
393
  }
224
394
  interface OidcVaultExchangeResult extends Partial<OidcVaultTokenIssueResult> {
395
+ /** Authoritative vault handle in body transport; omitted from cookie-transport JSON. */
225
396
  sessionId?: string;
397
+ /** Session profile; extra token-issuer result properties cannot override it. */
226
398
  user?: OidcVaultUserProfile;
227
399
  }
228
400
  type OidcVaultSessionTransport = 'body' | 'cookie';
229
401
  type OidcVaultCookieDeploymentMode = 'same-origin' | 'same-site' | 'cross-site';
230
402
  type OidcVaultCookieSameSite = 'lax' | 'strict' | 'none';
231
403
  interface OidcVaultCookieOptions {
404
+ /**
405
+ * Session cookie name. Must be a valid HTTP cookie name (printable ASCII
406
+ * token). Names with the `__Secure-` prefix require an effectively Secure
407
+ * cookie; names with the `__Host-` prefix additionally require no
408
+ * `cookie.domain` and `cookie.path: '/'`. Checked against effective
409
+ * serialized values, so `sameSite: 'none'` counts as Secure.
410
+ */
232
411
  name?: string;
233
412
  deploymentMode?: OidcVaultCookieDeploymentMode;
234
413
  sameSite?: OidcVaultCookieSameSite;
414
+ /**
415
+ * Explicit `Secure` override. When omitted, defaults to `true` for HTTPS
416
+ * `backendOrigin`, `sameSite: 'none'`, or `deploymentMode: 'cross-site'`;
417
+ * otherwise `false` as an intentional HTTP local-development policy
418
+ * (plaintext backends must opt into `secure: true` when served over HTTPS
419
+ * behind a proxy that reports an `http` origin). `SameSite=None` is always
420
+ * serialized with `Secure` regardless of this flag because browsers reject
421
+ * `SameSite=None` without it.
422
+ */
235
423
  secure?: boolean;
424
+ /**
425
+ * Optional cookie `Domain`. Must be a valid cookie domain when set. Must be
426
+ * omitted for `__Host-` prefixed names (host-only requirement).
427
+ */
236
428
  domain?: string;
429
+ /**
430
+ * Cookie `Path`. Must start with `/` and contain only header-safe printable
431
+ * ASCII (no CTLs, DEL, non-ASCII/Unicode, or `;`). `__Host-` prefixed names
432
+ * require exactly `'/'`.
433
+ */
237
434
  path?: string;
435
+ /**
436
+ * Must be `true` or omitted. Middleware creation rejects
437
+ * `httpOnly: false` for cookie session transport; there is no unsafe
438
+ * compatibility switch.
439
+ */
238
440
  httpOnly?: boolean;
239
441
  }
240
442
  interface OidcVaultOptions {
@@ -242,18 +444,84 @@ interface OidcVaultOptions {
242
444
  backendOrigin: string;
243
445
  storeProvider: OidcVaultStoreProvider;
244
446
  config?: OidcVaultConfig;
447
+ /**
448
+ * Lifecycle hooks shared by reference (not cloned): pre-commit hooks run
449
+ * before durable state changes and can veto by throwing, while
450
+ * `onSessionCreated`/`onSessionRefreshed`/`onLogout` are post-commit
451
+ * notifications whose failures reach `onError` without undoing state.
452
+ * Replacing the caller options object after middleware creation has no
453
+ * effect; the `hooks` service object itself is retained live.
454
+ */
245
455
  hooks?: OidcVaultHooks;
456
+ /** Optional local issuer; when omitted, exchange/refresh return no local token fields. */
246
457
  tokenIssuer?: OidcVaultTokenIssuer;
458
+ /**
459
+ * Default browser return target after backend callback completion. Required
460
+ * if login accepts a custom `returnTo`. Remains optional at middleware
461
+ * creation because non-callback routes do not need it; the callback route
462
+ * instead validates its destination (transaction `returnTo` or this value)
463
+ * before any provider call or durable session/code creation and fails with
464
+ * `500 OIDC_VAULT_MISSING_FRONTEND_REDIRECT_URI` when neither is configured.
465
+ */
247
466
  frontendRedirectUri?: string;
467
+ /**
468
+ * Optional provider-registered HTTP(S) URL used in the upstream end-session
469
+ * redirect. Only consulted for redirected logout (`redirect: true`); local
470
+ * logout never contacts the provider, and redirected logout treats upstream
471
+ * discovery failures or an absent endpoint as best-effort (local
472
+ * `200 { loggedOut: true }`, with `onLogout` still delivered for a live
473
+ * session). Discovery errors also reach `onError`.
474
+ */
248
475
  postLogoutRedirectUri?: string;
249
476
  fetchUserInfo?: boolean;
477
+ /**
478
+ * Authorization transaction lifetime in milliseconds (default: 10 minutes).
479
+ * Must be a positive safe integer; construction and creation require
480
+ * `now() + TTL` to be an integer within JavaScript Date's epoch range.
481
+ */
250
482
  authorizationTransactionTtlMs?: number;
483
+ /**
484
+ * One-time exchange code lifetime in milliseconds (default: 30 seconds).
485
+ * Same positive-safe-integer and computed-epoch checks as
486
+ * `authorizationTransactionTtlMs`.
487
+ */
251
488
  exchangeCodeTtlMs?: number;
489
+ /**
490
+ * Opt-in absolute server-side session lifetime in milliseconds, measured
491
+ * from callback session creation, not login start. Must be a positive safe
492
+ * integer producing an integer epoch within JavaScript Date's range;
493
+ * checked at construction and again at creation using `now` (or Date.now).
494
+ *
495
+ * Sets `expiresAt` before `onBeforeSessionCreate` and caps it after the hook:
496
+ * hooks may shorten the expiry but cannot extend/remove the maximum (invalid
497
+ * timestamps also restore the cap). Refresh preserves the stored expiry.
498
+ * Independent of upstream OAuth `expires_in`. Unset retains application/
499
+ * store-owned lifetime behavior; no default session expiry is assigned.
500
+ */
501
+ sessionTtlMs?: number;
252
502
  sessionTransport?: OidcVaultSessionTransport;
253
503
  cookie?: OidcVaultCookieOptions;
254
504
  trustedOrigins?: string[];
255
505
  requestBodyLimit?: string | number;
506
+ /**
507
+ * Overall deadline in milliseconds for a single upstream provider HTTP
508
+ * exchange, covering DNS/connect/TLS, response headers, and complete
509
+ * success/error body consumption. Cancellation is attempted promptly but
510
+ * never awaited: completion does not guarantee cleanup of custom streams.
511
+ * Defaults to 5000; must be a positive finite integer.
512
+ *
513
+ * Successful discovery metadata is shared across timeout policies, while
514
+ * in-flight fetches are isolated by `(issuer, providerRequestTimeoutMs)`.
515
+ * Remote JWKS resolvers are isolated by
516
+ * `(jwks_uri, providerRequestTimeoutMs)` because JOSE fixes the fetch
517
+ * timeout at resolver creation; the package JWKS transport additionally
518
+ * bounds JWKS bodies (1 MiB, 100 keys) that JOSE leaves unbounded.
519
+ * Network/reset failures are sanitized endpoint-specific 502s; original
520
+ * transport diagnostics are available privately via `hooks.onError`'s
521
+ * `error.cause`. JOSE timeouts retain `ERR_JWKS_TIMEOUT`.
522
+ */
256
523
  providerRequestTimeoutMs?: number;
524
+ /** Epoch-millisecond clock, also sampled during construction to validate TTLs. Defaults to Date.now. */
257
525
  now?: () => number;
258
526
  }
259
527
 
@@ -275,8 +543,20 @@ interface OidcVaultEnv {
275
543
  OIDC_SCOPES?: string;
276
544
  }
277
545
  /**
278
- * Resolve OIDC configuration from application input. If an issuer URL is
279
- * present, explicit endpoint fields are ignored and discovery mode wins.
546
+ * Resolve OIDC configuration from application input.
547
+ *
548
+ * Issuer discovery mode is selected only when an issuer is configured without
549
+ * any nonempty manual endpoint fields. Any such field, including optional
550
+ * `userInfoEndpoint` or `endSessionEndpoint`, selects manual mode: discovery
551
+ * is not used, and `issuer`, `authorizationEndpoint`, `tokenEndpoint`, and
552
+ * `jwksUri` are all required. Blank strings are treated as absent after trimming.
553
+ * `issuer` keeps ID tokens and logout tokens issuer-bound.
554
+ *
555
+ * The issuer identifier is syntax-validated (absolute http/https URL without
556
+ * userinfo, query, or fragment) but otherwise preserved exactly as configured
557
+ * after surrounding-whitespace trimming: no trailing slash is added and path
558
+ * variants such as `/tenant`, `/tenant/`, and `/tenant//` remain distinct.
559
+ * Endpoint URLs are normalized separately with `URL.toString()`.
280
560
  */
281
561
  declare function resolveOidcVaultConfig(config?: OidcVaultConfig): OidcVaultResolvedConfig;
282
562
  /**
@@ -303,7 +583,18 @@ declare function createOidcVaultJwtAccessTokenValidator(options: OidcVaultJwtAcc
303
583
  declare function normalizeOidcVaultBasePath(value?: string): string;
304
584
  /**
305
585
  * Create the core OIDC vault middleware.
586
+ *
587
+ * Construction takes an internal resolved snapshot of `options` without
588
+ * mutating the caller object: normalized values (such as
589
+ * `frontendRedirectUri`) are stored on the snapshot, plain-data containers
590
+ * (`cookie`, `trustedOrigins`, `config`) are shallow-copied, and service
591
+ * references (`storeProvider`, `hooks`, `tokenIssuer`, `now`) are retained
592
+ * by reference, never deep-cloned. Mutating or replacing the caller options
593
+ * after this call has no effect on the created router. Post-commit hooks
594
+ * (`onSessionCreated`, `onSessionRefreshed`, `onLogout`) are notifications
595
+ * whose failures are reported to `onError` without undoing committed state;
596
+ * other hooks run pre-commit and can veto the operation by throwing.
306
597
  */
307
598
  declare function createOidcVaultMiddleware(options: OidcVaultOptions): Router;
308
599
 
309
- export { type AuthorizationTransaction, type AuthorizationTransactionInput, type ConsumeBackchannelLogoutTokenJtiInput, DEFAULT_AUTHORIZATION_TRANSACTION_TTL_MS, DEFAULT_EXCHANGE_CODE_TTL_MS, DEFAULT_OIDC_SCOPES, DEFAULT_OIDC_VAULT_BASE_PATH, DEFAULT_OIDC_VAULT_REQUEST_BODY_LIMIT, type DeleteSessionsByLogicalSessionIdInput, type DeleteSessionsByProviderSessionIdInput, type DeleteSessionsBySubjectInput, type ExchangeCodeRecord, type ExchangeCodeRecordInput, type IssueTokenInput, OIDC_VAULT_ROUTE_PATHS, OIDC_VAULT_URL_ENCODED_PARAMETER_LIMIT, type OidcVaultAccessTokenMiddlewareOptions, type OidcVaultAccessTokenValidationResult, type OidcVaultAccessTokenValidator, type OidcVaultAuthContext, type OidcVaultAuthenticatedRequest, type OidcVaultBackchannelLogoutResult, type OidcVaultConfig, type OidcVaultConfigMode, type OidcVaultCookieDeploymentMode, type OidcVaultCookieOptions, type OidcVaultCookieSameSite, type OidcVaultEnv, type OidcVaultErrorContext, type OidcVaultExchangeResult, type OidcVaultHookContext, type OidcVaultHooks, type OidcVaultJwtAccessTokenValidatorOptions, type OidcVaultLogoutResult, type OidcVaultOptions, type OidcVaultProviderMetadata, type OidcVaultResolvedConfig, type OidcVaultRouteName, type OidcVaultSession, type OidcVaultSessionInput, type OidcVaultSessionTransport, OidcVaultStoreConflictError, type OidcVaultStoreProvider, type OidcVaultTokenIssueResult, type OidcVaultTokenIssuer, type OidcVaultUserProfile, type RotateSessionInput, createOidcVaultAccessTokenMiddleware, createOidcVaultJwtAccessTokenValidator, createOidcVaultMiddleware, normalizeOidcVaultBasePath, resolveOidcVaultConfig, resolveOidcVaultConfigFromEnv };
600
+ export { type AuthorizationTransaction, type AuthorizationTransactionInput, type ConsumeBackchannelLogoutTokenJtiInput, DEFAULT_AUTHORIZATION_TRANSACTION_TTL_MS, DEFAULT_EXCHANGE_CODE_TTL_MS, DEFAULT_OIDC_SCOPES, DEFAULT_OIDC_VAULT_BASE_PATH, DEFAULT_OIDC_VAULT_REQUEST_BODY_LIMIT, type DeleteSessionsByLogicalSessionIdInput, type DeleteSessionsByProviderSessionIdInput, type DeleteSessionsBySubjectInput, type ExchangeCodeRecord, type ExchangeCodeRecordInput, type IssueTokenInput, OIDC_VAULT_ROUTE_PATHS, OIDC_VAULT_URL_ENCODED_PARAMETER_LIMIT, type OidcVaultAccessTokenMiddlewareErrorContext, type OidcVaultAccessTokenMiddlewareOptions, type OidcVaultAccessTokenValidationResult, type OidcVaultAccessTokenValidator, type OidcVaultAuthContext, type OidcVaultAuthenticatedRequest, type OidcVaultBackchannelLogoutResult, type OidcVaultConfig, type OidcVaultConfigMode, type OidcVaultCookieDeploymentMode, type OidcVaultCookieOptions, type OidcVaultCookieSameSite, type OidcVaultEnv, type OidcVaultErrorContext, type OidcVaultExchangeResult, type OidcVaultHookContext, type OidcVaultHooks, type OidcVaultJwtAccessTokenValidatorOptions, type OidcVaultLogoutResult, type OidcVaultOptions, type OidcVaultProviderMetadata, type OidcVaultResolvedConfig, type OidcVaultRouteName, type OidcVaultSession, type OidcVaultSessionInput, type OidcVaultSessionTransport, OidcVaultStoreConflictError, type OidcVaultStoreProvider, type OidcVaultTokenIssueResult, type OidcVaultTokenIssuer, type OidcVaultUserProfile, type RotateSessionInput, createOidcVaultAccessTokenMiddleware, createOidcVaultJwtAccessTokenValidator, createOidcVaultMiddleware, normalizeOidcVaultBasePath, resolveOidcVaultConfig, resolveOidcVaultConfigFromEnv };