@web-ts-toolkit/express-oidc-vault 0.51.0 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -335
- package/index.d.mts +15 -64
- package/index.d.ts +15 -64
- package/index.js +50 -31
- package/index.mjs +50 -31
- package/package.json +11 -1
- package/wire.d.mts +76 -0
- package/wire.d.ts +76 -0
- package/wire.js +18 -0
- package/wire.mjs +0 -0
package/index.d.mts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Request, Response, RequestHandler, Router } from 'express';
|
|
2
2
|
import { KeyObject } from 'node:crypto';
|
|
3
3
|
import { JWK } from 'jose';
|
|
4
|
+
import { OidcVaultUserProfile, OidcVaultTokenIssueResult, OidcVaultSessionTransport } from './wire.mjs';
|
|
5
|
+
export { OidcVaultExchangeResult, OidcVaultLoginInitiationInput, OidcVaultLoginInitiationResult, OidcVaultLogoutResult } from './wire.mjs';
|
|
4
6
|
|
|
5
7
|
type OidcVaultRouteName = 'login' | 'callback' | 'exchange' | 'refresh' | 'logout' | 'backchannel-logout';
|
|
6
8
|
/** Enabling the option defaults to `optional`; omitting it keeps bearer behavior. */
|
|
@@ -114,13 +116,6 @@ interface OidcVaultApiDeviceBindingOptions extends OidcVaultDeviceBindingOptions
|
|
|
114
116
|
/** Epoch-millisecond clock for proof age/nonce/replay; default Date.now. Shared instances need synchronized clocks. */
|
|
115
117
|
now?: () => number;
|
|
116
118
|
}
|
|
117
|
-
interface OidcVaultUserProfile {
|
|
118
|
-
sub: string;
|
|
119
|
-
email?: string;
|
|
120
|
-
name?: string;
|
|
121
|
-
preferredUsername?: string;
|
|
122
|
-
[key: string]: unknown;
|
|
123
|
-
}
|
|
124
119
|
/**
|
|
125
120
|
* Stored session identity. Exchange, refresh, and logout compare each defined
|
|
126
121
|
* field verbatim against resolved middleware config before use. Opt-in/guarded
|
|
@@ -515,19 +510,6 @@ interface IssueTokenInput {
|
|
|
515
510
|
*/
|
|
516
511
|
deviceBinding?: Readonly<OidcVaultVerifiedDpopBinding>;
|
|
517
512
|
}
|
|
518
|
-
/**
|
|
519
|
-
* Local application credentials returned by `OidcVaultTokenIssuer.issue`.
|
|
520
|
-
* Runtime validation copies only these fields into a fresh result; additional
|
|
521
|
-
* properties are ignored and cannot override the response's session ID/user.
|
|
522
|
-
*/
|
|
523
|
-
interface OidcVaultTokenIssueResult {
|
|
524
|
-
/** Nonempty opaque token for unbound issuance; bound issuance requires a compact signed JWT with matching `cnf.jkt`. */
|
|
525
|
-
accessToken: string;
|
|
526
|
-
/** Token lifetime in seconds: a finite, nonnegative safe integer (zero is valid). */
|
|
527
|
-
expiresIn: number;
|
|
528
|
-
/** Exact `DPoP` is required when bound. Unbound permits only `Bearer` or omission; omitted/undefined stays absent in JSON. */
|
|
529
|
-
tokenType?: 'Bearer' | 'DPoP';
|
|
530
|
-
}
|
|
531
513
|
/**
|
|
532
514
|
* Trusted application token issuer for exchange and refresh. Return a non-null,
|
|
533
515
|
* non-array object satisfying `OidcVaultTokenIssueResult`; only its declared
|
|
@@ -554,7 +536,7 @@ interface OidcVaultAccessTokenValidationResult {
|
|
|
554
536
|
/** Verified sender constraint. Legacy adapters may omit it only for genuinely unbound credentials. */
|
|
555
537
|
confirmation?: OidcVaultAccessTokenConfirmation | null;
|
|
556
538
|
}
|
|
557
|
-
/**
|
|
539
|
+
/** Exact one-field confirmation: canonical RFC 7638 SHA-256 thumbprint from verified token/introspection data, never from a proof alone. */
|
|
558
540
|
interface OidcVaultAccessTokenConfirmation {
|
|
559
541
|
jkt: string;
|
|
560
542
|
}
|
|
@@ -628,13 +610,18 @@ interface OidcVaultAccessTokenMiddlewareOptions {
|
|
|
628
610
|
interface OidcVaultAuthenticatedRequest extends Request {
|
|
629
611
|
auth?: OidcVaultAuthContext;
|
|
630
612
|
}
|
|
631
|
-
/**
|
|
613
|
+
/**
|
|
614
|
+
* Local JWT verification options, independent of the asymmetric DPoP proof
|
|
615
|
+
* policy; captured at construction. JWTs with cnf require the supported exact
|
|
616
|
+
* jkt profile and a nonnegative safe-integer exp with safe milliseconds in both
|
|
617
|
+
* validator methods. Unbound JWTs retain JOSE's optional-exp behavior.
|
|
618
|
+
*/
|
|
632
619
|
interface OidcVaultJwtAccessTokenValidatorOptions {
|
|
633
620
|
key: CryptoKey | KeyObject | JWK | Uint8Array;
|
|
634
621
|
issuer?: string;
|
|
635
622
|
audience?: string | string[];
|
|
636
623
|
algorithms?: string[];
|
|
637
|
-
/**
|
|
624
|
+
/** JOSE verification and bound-JWT profile/expiry checks precede this mapper. Original cnf is snapshotted; mapper-supplied confirmation/deviceBinding is ignored. */
|
|
638
625
|
mapClaims?(claims: Record<string, unknown>): OidcVaultAccessTokenValidationResult;
|
|
639
626
|
}
|
|
640
627
|
declare module 'express-serve-static-core' {
|
|
@@ -666,50 +653,10 @@ interface OidcVaultConfig {
|
|
|
666
653
|
clientSecret?: string;
|
|
667
654
|
scopes?: string;
|
|
668
655
|
}
|
|
669
|
-
interface OidcVaultLogoutResult {
|
|
670
|
-
/**
|
|
671
|
-
* Successful logout commits revocation before upstream work and clears the
|
|
672
|
-
* session cookie under cookie transport. Built-in live handles and unexpired
|
|
673
|
-
* aliases authenticate the surviving lineage's provider/original binding;
|
|
674
|
-
* wrong identity, missing/wrong/stale/replayed proof, or disabled acceptance
|
|
675
|
-
* of a bound lineage fails without deleting or clearing. No currently live
|
|
676
|
-
* target is idempotent success without deletion or proof reservation. Aliases
|
|
677
|
-
* grant revocation only, not refresh authentication or upstream redirects.
|
|
678
|
-
* Old custom bearer stores without revocation context retain legacy alias
|
|
679
|
-
* deletion behavior. Stateless application access tokens are not invalidated.
|
|
680
|
-
* Local-only logout (`redirect` unset or `false`) never contacts the provider.
|
|
681
|
-
* Redirected logout (`redirect: true`)
|
|
682
|
-
* treats the upstream end-session redirect as best-effort: when provider
|
|
683
|
-
* discovery fails or no `endSessionEndpoint` is available, the route still
|
|
684
|
-
* returns this local success. Discovery errors reach `onError` without
|
|
685
|
-
* undoing revocation or skipping the live session's `onLogout` notification.
|
|
686
|
-
*/
|
|
687
|
-
loggedOut: true;
|
|
688
|
-
}
|
|
689
656
|
interface OidcVaultBackchannelLogoutResult {
|
|
690
657
|
loggedOut: true;
|
|
691
658
|
revokedSessions: number;
|
|
692
659
|
}
|
|
693
|
-
interface OidcVaultExchangeResult extends Partial<OidcVaultTokenIssueResult> {
|
|
694
|
-
/** Authoritative vault handle in body transport; omitted from cookie-transport JSON. */
|
|
695
|
-
sessionId?: string;
|
|
696
|
-
/** Session profile; extra token-issuer result properties cannot override it. */
|
|
697
|
-
user?: OidcVaultUserProfile;
|
|
698
|
-
}
|
|
699
|
-
/** JSON body for opt-in `POST <basePath>/login`; proof/key and recognition signals belong in their configured headers, not the body. */
|
|
700
|
-
interface OidcVaultLoginInitiationInput {
|
|
701
|
-
/** Optional destination on the configured frontend origin. Query-string returnTo is ignored by POST login. */
|
|
702
|
-
returnTo?: string;
|
|
703
|
-
}
|
|
704
|
-
/**
|
|
705
|
-
* Exact successful POST-login JSON. Navigate to this URL after storing a proof
|
|
706
|
-
* key when DPoP is enabled. Use credentials: 'include' even with body session
|
|
707
|
-
* transport: initiation sets a temporary HttpOnly transaction cookie.
|
|
708
|
-
*/
|
|
709
|
-
interface OidcVaultLoginInitiationResult {
|
|
710
|
-
authorizationUrl: string;
|
|
711
|
-
}
|
|
712
|
-
type OidcVaultSessionTransport = 'body' | 'cookie';
|
|
713
660
|
type OidcVaultCookieDeploymentMode = 'same-origin' | 'same-site' | 'cross-site';
|
|
714
661
|
type OidcVaultCookieSameSite = 'lax' | 'strict' | 'none';
|
|
715
662
|
/**
|
|
@@ -971,6 +918,10 @@ declare function createOidcVaultAccessTokenMiddleware(options: OidcVaultAccessTo
|
|
|
971
918
|
* the sender constraint. The request-aware result always has confirmation
|
|
972
919
|
* (null only for genuinely unbound JWTs). Legacy unbound results omit it;
|
|
973
920
|
* bound results report it so binding-disabled vault middleware refuses downgrade.
|
|
921
|
+
* Bound JWTs additionally require canonical compact encoding and a nonnegative
|
|
922
|
+
* safe-integer exp with safe milliseconds, checked before mapClaims in both
|
|
923
|
+
* methods. JOSE verifies signature/expiry and configured issuer/audience first. Unbound
|
|
924
|
+
* JWTs retain JOSE's generic optional-exp contract.
|
|
974
925
|
* Proof/target/ath/nonce/replay enforcement belongs to the API middleware.
|
|
975
926
|
* Construction captures options and copies secret/JWK/allowlist containers.
|
|
976
927
|
*/
|
|
@@ -1017,4 +968,4 @@ declare function normalizeOidcVaultBasePath(value?: string): string;
|
|
|
1017
968
|
*/
|
|
1018
969
|
declare function createOidcVaultMiddleware(options: OidcVaultOptions): Router;
|
|
1019
970
|
|
|
1020
|
-
export { type AuthorizationTransaction, type AuthorizationTransactionInput, type ConsumeAuthorizationTransactionIfMatchesInput, type ConsumeBackchannelLogoutTokenJtiInput, type ConsumeExchangeCodeIfMatchesInput, 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 OidcVaultAccessTokenConfirmation, type OidcVaultAccessTokenMiddlewareErrorContext, type OidcVaultAccessTokenMiddlewareOptions, type OidcVaultAccessTokenRequestInput, type OidcVaultAccessTokenValidationResult, type OidcVaultAccessTokenValidator, type OidcVaultApiDeviceBindingOptions, type OidcVaultAuthContext, type OidcVaultAuthenticatedRequest, type OidcVaultBackchannelLogoutResult, type OidcVaultConfig, type OidcVaultConfigMode, type OidcVaultCookieDeploymentMode, type OidcVaultCookieOptions, type OidcVaultCookieSameSite, type OidcVaultDeviceBindingMode, type OidcVaultDeviceBindingOptions, type OidcVaultDeviceBindingStoreProvider, type OidcVaultDpopAlgorithm, type OidcVaultDpopBinding, type OidcVaultDpopNonceOptions, type OidcVaultDpopProofOptions, OidcVaultDpopReplayCapacityError, type OidcVaultDpopReplayStore, type OidcVaultEnv, type OidcVaultErrorContext, type
|
|
971
|
+
export { type AuthorizationTransaction, type AuthorizationTransactionInput, type ConsumeAuthorizationTransactionIfMatchesInput, type ConsumeBackchannelLogoutTokenJtiInput, type ConsumeExchangeCodeIfMatchesInput, 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 OidcVaultAccessTokenConfirmation, type OidcVaultAccessTokenMiddlewareErrorContext, type OidcVaultAccessTokenMiddlewareOptions, type OidcVaultAccessTokenRequestInput, type OidcVaultAccessTokenValidationResult, type OidcVaultAccessTokenValidator, type OidcVaultApiDeviceBindingOptions, type OidcVaultAuthContext, type OidcVaultAuthenticatedRequest, type OidcVaultBackchannelLogoutResult, type OidcVaultConfig, type OidcVaultConfigMode, type OidcVaultCookieDeploymentMode, type OidcVaultCookieOptions, type OidcVaultCookieSameSite, type OidcVaultDeviceBindingMode, type OidcVaultDeviceBindingOptions, type OidcVaultDeviceBindingStoreProvider, type OidcVaultDpopAlgorithm, type OidcVaultDpopBinding, type OidcVaultDpopNonceOptions, type OidcVaultDpopProofOptions, OidcVaultDpopReplayCapacityError, type OidcVaultDpopReplayStore, type OidcVaultEnv, type OidcVaultErrorContext, type OidcVaultFingerprintRecognitionOptions, type OidcVaultHookContext, type OidcVaultHooks, type OidcVaultJwtAccessTokenValidatorOptions, type OidcVaultOptions, type OidcVaultProviderMetadata, type OidcVaultRecordBindingMatch, type OidcVaultRequestAwareAccessTokenValidationResult, type OidcVaultRequestAwareAccessTokenValidator, type OidcVaultResolvedConfig, type OidcVaultRouteName, type OidcVaultSession, type OidcVaultSessionInput, type OidcVaultSessionRevocationContext, OidcVaultSessionTransport, OidcVaultStoreConflictError, type OidcVaultStoreProvider, OidcVaultTokenIssueResult, type OidcVaultTokenIssuer, type OidcVaultTransactionCookieOptions, OidcVaultUserProfile, type OidcVaultVerifiedDpopBinding, type ReserveDpopProofInput, type RotateSessionInput, createOidcVaultAccessTokenMiddleware, createOidcVaultJwtAccessTokenValidator, createOidcVaultMiddleware, normalizeOidcVaultBasePath, resolveOidcVaultConfig, resolveOidcVaultConfigFromEnv };
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Request, Response, RequestHandler, Router } from 'express';
|
|
2
2
|
import { KeyObject } from 'node:crypto';
|
|
3
3
|
import { JWK } from 'jose';
|
|
4
|
+
import { OidcVaultUserProfile, OidcVaultTokenIssueResult, OidcVaultSessionTransport } from './wire.js';
|
|
5
|
+
export { OidcVaultExchangeResult, OidcVaultLoginInitiationInput, OidcVaultLoginInitiationResult, OidcVaultLogoutResult } from './wire.js';
|
|
4
6
|
|
|
5
7
|
type OidcVaultRouteName = 'login' | 'callback' | 'exchange' | 'refresh' | 'logout' | 'backchannel-logout';
|
|
6
8
|
/** Enabling the option defaults to `optional`; omitting it keeps bearer behavior. */
|
|
@@ -114,13 +116,6 @@ interface OidcVaultApiDeviceBindingOptions extends OidcVaultDeviceBindingOptions
|
|
|
114
116
|
/** Epoch-millisecond clock for proof age/nonce/replay; default Date.now. Shared instances need synchronized clocks. */
|
|
115
117
|
now?: () => number;
|
|
116
118
|
}
|
|
117
|
-
interface OidcVaultUserProfile {
|
|
118
|
-
sub: string;
|
|
119
|
-
email?: string;
|
|
120
|
-
name?: string;
|
|
121
|
-
preferredUsername?: string;
|
|
122
|
-
[key: string]: unknown;
|
|
123
|
-
}
|
|
124
119
|
/**
|
|
125
120
|
* Stored session identity. Exchange, refresh, and logout compare each defined
|
|
126
121
|
* field verbatim against resolved middleware config before use. Opt-in/guarded
|
|
@@ -515,19 +510,6 @@ interface IssueTokenInput {
|
|
|
515
510
|
*/
|
|
516
511
|
deviceBinding?: Readonly<OidcVaultVerifiedDpopBinding>;
|
|
517
512
|
}
|
|
518
|
-
/**
|
|
519
|
-
* Local application credentials returned by `OidcVaultTokenIssuer.issue`.
|
|
520
|
-
* Runtime validation copies only these fields into a fresh result; additional
|
|
521
|
-
* properties are ignored and cannot override the response's session ID/user.
|
|
522
|
-
*/
|
|
523
|
-
interface OidcVaultTokenIssueResult {
|
|
524
|
-
/** Nonempty opaque token for unbound issuance; bound issuance requires a compact signed JWT with matching `cnf.jkt`. */
|
|
525
|
-
accessToken: string;
|
|
526
|
-
/** Token lifetime in seconds: a finite, nonnegative safe integer (zero is valid). */
|
|
527
|
-
expiresIn: number;
|
|
528
|
-
/** Exact `DPoP` is required when bound. Unbound permits only `Bearer` or omission; omitted/undefined stays absent in JSON. */
|
|
529
|
-
tokenType?: 'Bearer' | 'DPoP';
|
|
530
|
-
}
|
|
531
513
|
/**
|
|
532
514
|
* Trusted application token issuer for exchange and refresh. Return a non-null,
|
|
533
515
|
* non-array object satisfying `OidcVaultTokenIssueResult`; only its declared
|
|
@@ -554,7 +536,7 @@ interface OidcVaultAccessTokenValidationResult {
|
|
|
554
536
|
/** Verified sender constraint. Legacy adapters may omit it only for genuinely unbound credentials. */
|
|
555
537
|
confirmation?: OidcVaultAccessTokenConfirmation | null;
|
|
556
538
|
}
|
|
557
|
-
/**
|
|
539
|
+
/** Exact one-field confirmation: canonical RFC 7638 SHA-256 thumbprint from verified token/introspection data, never from a proof alone. */
|
|
558
540
|
interface OidcVaultAccessTokenConfirmation {
|
|
559
541
|
jkt: string;
|
|
560
542
|
}
|
|
@@ -628,13 +610,18 @@ interface OidcVaultAccessTokenMiddlewareOptions {
|
|
|
628
610
|
interface OidcVaultAuthenticatedRequest extends Request {
|
|
629
611
|
auth?: OidcVaultAuthContext;
|
|
630
612
|
}
|
|
631
|
-
/**
|
|
613
|
+
/**
|
|
614
|
+
* Local JWT verification options, independent of the asymmetric DPoP proof
|
|
615
|
+
* policy; captured at construction. JWTs with cnf require the supported exact
|
|
616
|
+
* jkt profile and a nonnegative safe-integer exp with safe milliseconds in both
|
|
617
|
+
* validator methods. Unbound JWTs retain JOSE's optional-exp behavior.
|
|
618
|
+
*/
|
|
632
619
|
interface OidcVaultJwtAccessTokenValidatorOptions {
|
|
633
620
|
key: CryptoKey | KeyObject | JWK | Uint8Array;
|
|
634
621
|
issuer?: string;
|
|
635
622
|
audience?: string | string[];
|
|
636
623
|
algorithms?: string[];
|
|
637
|
-
/**
|
|
624
|
+
/** JOSE verification and bound-JWT profile/expiry checks precede this mapper. Original cnf is snapshotted; mapper-supplied confirmation/deviceBinding is ignored. */
|
|
638
625
|
mapClaims?(claims: Record<string, unknown>): OidcVaultAccessTokenValidationResult;
|
|
639
626
|
}
|
|
640
627
|
declare module 'express-serve-static-core' {
|
|
@@ -666,50 +653,10 @@ interface OidcVaultConfig {
|
|
|
666
653
|
clientSecret?: string;
|
|
667
654
|
scopes?: string;
|
|
668
655
|
}
|
|
669
|
-
interface OidcVaultLogoutResult {
|
|
670
|
-
/**
|
|
671
|
-
* Successful logout commits revocation before upstream work and clears the
|
|
672
|
-
* session cookie under cookie transport. Built-in live handles and unexpired
|
|
673
|
-
* aliases authenticate the surviving lineage's provider/original binding;
|
|
674
|
-
* wrong identity, missing/wrong/stale/replayed proof, or disabled acceptance
|
|
675
|
-
* of a bound lineage fails without deleting or clearing. No currently live
|
|
676
|
-
* target is idempotent success without deletion or proof reservation. Aliases
|
|
677
|
-
* grant revocation only, not refresh authentication or upstream redirects.
|
|
678
|
-
* Old custom bearer stores without revocation context retain legacy alias
|
|
679
|
-
* deletion behavior. Stateless application access tokens are not invalidated.
|
|
680
|
-
* Local-only logout (`redirect` unset or `false`) never contacts the provider.
|
|
681
|
-
* Redirected logout (`redirect: true`)
|
|
682
|
-
* treats the upstream end-session redirect as best-effort: when provider
|
|
683
|
-
* discovery fails or no `endSessionEndpoint` is available, the route still
|
|
684
|
-
* returns this local success. Discovery errors reach `onError` without
|
|
685
|
-
* undoing revocation or skipping the live session's `onLogout` notification.
|
|
686
|
-
*/
|
|
687
|
-
loggedOut: true;
|
|
688
|
-
}
|
|
689
656
|
interface OidcVaultBackchannelLogoutResult {
|
|
690
657
|
loggedOut: true;
|
|
691
658
|
revokedSessions: number;
|
|
692
659
|
}
|
|
693
|
-
interface OidcVaultExchangeResult extends Partial<OidcVaultTokenIssueResult> {
|
|
694
|
-
/** Authoritative vault handle in body transport; omitted from cookie-transport JSON. */
|
|
695
|
-
sessionId?: string;
|
|
696
|
-
/** Session profile; extra token-issuer result properties cannot override it. */
|
|
697
|
-
user?: OidcVaultUserProfile;
|
|
698
|
-
}
|
|
699
|
-
/** JSON body for opt-in `POST <basePath>/login`; proof/key and recognition signals belong in their configured headers, not the body. */
|
|
700
|
-
interface OidcVaultLoginInitiationInput {
|
|
701
|
-
/** Optional destination on the configured frontend origin. Query-string returnTo is ignored by POST login. */
|
|
702
|
-
returnTo?: string;
|
|
703
|
-
}
|
|
704
|
-
/**
|
|
705
|
-
* Exact successful POST-login JSON. Navigate to this URL after storing a proof
|
|
706
|
-
* key when DPoP is enabled. Use credentials: 'include' even with body session
|
|
707
|
-
* transport: initiation sets a temporary HttpOnly transaction cookie.
|
|
708
|
-
*/
|
|
709
|
-
interface OidcVaultLoginInitiationResult {
|
|
710
|
-
authorizationUrl: string;
|
|
711
|
-
}
|
|
712
|
-
type OidcVaultSessionTransport = 'body' | 'cookie';
|
|
713
660
|
type OidcVaultCookieDeploymentMode = 'same-origin' | 'same-site' | 'cross-site';
|
|
714
661
|
type OidcVaultCookieSameSite = 'lax' | 'strict' | 'none';
|
|
715
662
|
/**
|
|
@@ -971,6 +918,10 @@ declare function createOidcVaultAccessTokenMiddleware(options: OidcVaultAccessTo
|
|
|
971
918
|
* the sender constraint. The request-aware result always has confirmation
|
|
972
919
|
* (null only for genuinely unbound JWTs). Legacy unbound results omit it;
|
|
973
920
|
* bound results report it so binding-disabled vault middleware refuses downgrade.
|
|
921
|
+
* Bound JWTs additionally require canonical compact encoding and a nonnegative
|
|
922
|
+
* safe-integer exp with safe milliseconds, checked before mapClaims in both
|
|
923
|
+
* methods. JOSE verifies signature/expiry and configured issuer/audience first. Unbound
|
|
924
|
+
* JWTs retain JOSE's generic optional-exp contract.
|
|
974
925
|
* Proof/target/ath/nonce/replay enforcement belongs to the API middleware.
|
|
975
926
|
* Construction captures options and copies secret/JWK/allowlist containers.
|
|
976
927
|
*/
|
|
@@ -1017,4 +968,4 @@ declare function normalizeOidcVaultBasePath(value?: string): string;
|
|
|
1017
968
|
*/
|
|
1018
969
|
declare function createOidcVaultMiddleware(options: OidcVaultOptions): Router;
|
|
1019
970
|
|
|
1020
|
-
export { type AuthorizationTransaction, type AuthorizationTransactionInput, type ConsumeAuthorizationTransactionIfMatchesInput, type ConsumeBackchannelLogoutTokenJtiInput, type ConsumeExchangeCodeIfMatchesInput, 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 OidcVaultAccessTokenConfirmation, type OidcVaultAccessTokenMiddlewareErrorContext, type OidcVaultAccessTokenMiddlewareOptions, type OidcVaultAccessTokenRequestInput, type OidcVaultAccessTokenValidationResult, type OidcVaultAccessTokenValidator, type OidcVaultApiDeviceBindingOptions, type OidcVaultAuthContext, type OidcVaultAuthenticatedRequest, type OidcVaultBackchannelLogoutResult, type OidcVaultConfig, type OidcVaultConfigMode, type OidcVaultCookieDeploymentMode, type OidcVaultCookieOptions, type OidcVaultCookieSameSite, type OidcVaultDeviceBindingMode, type OidcVaultDeviceBindingOptions, type OidcVaultDeviceBindingStoreProvider, type OidcVaultDpopAlgorithm, type OidcVaultDpopBinding, type OidcVaultDpopNonceOptions, type OidcVaultDpopProofOptions, OidcVaultDpopReplayCapacityError, type OidcVaultDpopReplayStore, type OidcVaultEnv, type OidcVaultErrorContext, type
|
|
971
|
+
export { type AuthorizationTransaction, type AuthorizationTransactionInput, type ConsumeAuthorizationTransactionIfMatchesInput, type ConsumeBackchannelLogoutTokenJtiInput, type ConsumeExchangeCodeIfMatchesInput, 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 OidcVaultAccessTokenConfirmation, type OidcVaultAccessTokenMiddlewareErrorContext, type OidcVaultAccessTokenMiddlewareOptions, type OidcVaultAccessTokenRequestInput, type OidcVaultAccessTokenValidationResult, type OidcVaultAccessTokenValidator, type OidcVaultApiDeviceBindingOptions, type OidcVaultAuthContext, type OidcVaultAuthenticatedRequest, type OidcVaultBackchannelLogoutResult, type OidcVaultConfig, type OidcVaultConfigMode, type OidcVaultCookieDeploymentMode, type OidcVaultCookieOptions, type OidcVaultCookieSameSite, type OidcVaultDeviceBindingMode, type OidcVaultDeviceBindingOptions, type OidcVaultDeviceBindingStoreProvider, type OidcVaultDpopAlgorithm, type OidcVaultDpopBinding, type OidcVaultDpopNonceOptions, type OidcVaultDpopProofOptions, OidcVaultDpopReplayCapacityError, type OidcVaultDpopReplayStore, type OidcVaultEnv, type OidcVaultErrorContext, type OidcVaultFingerprintRecognitionOptions, type OidcVaultHookContext, type OidcVaultHooks, type OidcVaultJwtAccessTokenValidatorOptions, type OidcVaultOptions, type OidcVaultProviderMetadata, type OidcVaultRecordBindingMatch, type OidcVaultRequestAwareAccessTokenValidationResult, type OidcVaultRequestAwareAccessTokenValidator, type OidcVaultResolvedConfig, type OidcVaultRouteName, type OidcVaultSession, type OidcVaultSessionInput, type OidcVaultSessionRevocationContext, OidcVaultSessionTransport, OidcVaultStoreConflictError, type OidcVaultStoreProvider, OidcVaultTokenIssueResult, type OidcVaultTokenIssuer, type OidcVaultTransactionCookieOptions, OidcVaultUserProfile, type OidcVaultVerifiedDpopBinding, type ReserveDpopProofInput, type RotateSessionInput, createOidcVaultAccessTokenMiddleware, createOidcVaultJwtAccessTokenValidator, createOidcVaultMiddleware, normalizeOidcVaultBasePath, resolveOidcVaultConfig, resolveOidcVaultConfigFromEnv };
|
package/index.js
CHANGED
|
@@ -1833,6 +1833,35 @@ async function fetchUserInfo(metadata, accessToken, options = {}) {
|
|
|
1833
1833
|
// src/token-issuance.ts
|
|
1834
1834
|
var import_jose3 = require("jose");
|
|
1835
1835
|
|
|
1836
|
+
// src/access-token-confirmation.ts
|
|
1837
|
+
var invalidAccessToken = (diagnostic) => new OidcVaultHttpError(401, "OIDC_VAULT_INVALID_ACCESS_TOKEN", diagnostic, "Access token validation failed.");
|
|
1838
|
+
var snapshotJkt = (value, invalid = invalidAccessToken) => {
|
|
1839
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1840
|
+
throw invalid("Access token confirmation must be a plain jkt object.");
|
|
1841
|
+
}
|
|
1842
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1843
|
+
const keys = Reflect.ownKeys(value);
|
|
1844
|
+
if (prototype !== Object.prototype && prototype !== null || keys.length !== 1 || keys[0] !== "jkt") {
|
|
1845
|
+
throw invalid("Access token confirmation contains unsupported fields.");
|
|
1846
|
+
}
|
|
1847
|
+
const { jkt } = value;
|
|
1848
|
+
if (!isCanonicalDpopJkt(jkt)) throw invalid("Access token confirmation is not a canonical SHA-256 thumbprint.");
|
|
1849
|
+
return Object.freeze({ jkt });
|
|
1850
|
+
};
|
|
1851
|
+
var isCompactSignedJwt = (token) => {
|
|
1852
|
+
const parts = token.split(".");
|
|
1853
|
+
return parts.length === 3 && parts.every(
|
|
1854
|
+
(part) => /^[A-Za-z0-9_-]+$/.test(part) ? Buffer.from(part, "base64url").toString("base64url") === part : false
|
|
1855
|
+
);
|
|
1856
|
+
};
|
|
1857
|
+
var readJwtExpirationMs = (exp) => {
|
|
1858
|
+
if (typeof exp !== "number" || !Number.isSafeInteger(exp) || exp < 0) return void 0;
|
|
1859
|
+
const milliseconds = exp * 1e3;
|
|
1860
|
+
return Number.isSafeInteger(milliseconds) ? milliseconds : void 0;
|
|
1861
|
+
};
|
|
1862
|
+
var snapshotVerifiedJwtConfirmation = (claims) => Object.hasOwn(claims, "cnf") ? snapshotJkt(claims.cnf) : null;
|
|
1863
|
+
var snapshotAccessTokenConfirmation = (value) => value === null ? null : snapshotJkt(value);
|
|
1864
|
+
|
|
1836
1865
|
// src/vault-session.ts
|
|
1837
1866
|
var invalidVaultSession = () => new OidcVaultHttpError(401, "OIDC_VAULT_INVALID_SESSION", "Session is missing or expired.");
|
|
1838
1867
|
var invalidExchangeCode = () => new OidcVaultHttpError(400, "OIDC_VAULT_INVALID_EXCHANGE_CODE", "Exchange code is invalid or expired.");
|
|
@@ -1977,13 +2006,7 @@ var createExchangeResponse = (options, session, issuedToken) => ({
|
|
|
1977
2006
|
expiresIn: issuedToken.expiresIn,
|
|
1978
2007
|
tokenType: issuedToken.tokenType
|
|
1979
2008
|
});
|
|
1980
|
-
var
|
|
1981
|
-
const parts = token.split(".");
|
|
1982
|
-
return parts.length === 3 && parts.every(
|
|
1983
|
-
(part) => /^[A-Za-z0-9_-]+$/.test(part) ? Buffer.from(part, "base64url").toString("base64url") === part : false
|
|
1984
|
-
);
|
|
1985
|
-
};
|
|
1986
|
-
var assertIssuedTokenConfirmation = (accessToken, jkt) => {
|
|
2009
|
+
var assertIssuedBoundJwt = (accessToken, jkt) => {
|
|
1987
2010
|
if (!isCompactSignedJwt(accessToken)) {
|
|
1988
2011
|
throw new TypeError("tokenIssuer.issue accessToken must be a compact signed JWT for bound issuance.");
|
|
1989
2012
|
}
|
|
@@ -1998,10 +2021,16 @@ var assertIssuedTokenConfirmation = (accessToken, jkt) => {
|
|
|
1998
2021
|
if (typeof header.alg !== "string" || header.alg.length === 0 || header.alg === "none" || header.b64 === false) {
|
|
1999
2022
|
throw new TypeError("tokenIssuer.issue accessToken must use a signed JWT algorithm for bound issuance.");
|
|
2000
2023
|
}
|
|
2001
|
-
const
|
|
2002
|
-
|
|
2024
|
+
const confirmation = snapshotJkt(
|
|
2025
|
+
claims.cnf,
|
|
2026
|
+
(diagnostic) => new TypeError(`tokenIssuer.issue accessToken cnf.jkt is invalid. ${diagnostic}`)
|
|
2027
|
+
);
|
|
2028
|
+
if (confirmation.jkt !== jkt) {
|
|
2003
2029
|
throw new TypeError("tokenIssuer.issue accessToken cnf.jkt must match the verified device binding.");
|
|
2004
2030
|
}
|
|
2031
|
+
if (readJwtExpirationMs(claims.exp) === void 0) {
|
|
2032
|
+
throw new TypeError("tokenIssuer.issue accessToken exp must be a nonnegative safe integer with safe milliseconds.");
|
|
2033
|
+
}
|
|
2005
2034
|
};
|
|
2006
2035
|
var withIssuedToken = async (req, res, options, session, verifiedBinding) => {
|
|
2007
2036
|
const binding = resolveVerifiedDeviceBinding(options.deviceBinding, session.deviceBinding, verifiedBinding);
|
|
@@ -2034,7 +2063,10 @@ var withIssuedToken = async (req, res, options, session, verifiedBinding) => {
|
|
|
2034
2063
|
}
|
|
2035
2064
|
} else {
|
|
2036
2065
|
if (tokenType !== "DPoP") throw new TypeError("tokenIssuer.issue tokenType must be DPoP for bound issuance.");
|
|
2037
|
-
|
|
2066
|
+
if (!Number.isSafeInteger(expiresIn * 1e3)) {
|
|
2067
|
+
throw new TypeError("tokenIssuer.issue expiresIn must have safe milliseconds for bound issuance.");
|
|
2068
|
+
}
|
|
2069
|
+
assertIssuedBoundJwt(accessToken, binding.jkt);
|
|
2038
2070
|
}
|
|
2039
2071
|
return { accessToken, expiresIn, ...tokenType === void 0 ? {} : { tokenType } };
|
|
2040
2072
|
} catch (error) {
|
|
@@ -2046,27 +2078,6 @@ var withIssuedToken = async (req, res, options, session, verifiedBinding) => {
|
|
|
2046
2078
|
|
|
2047
2079
|
// src/token-validation.ts
|
|
2048
2080
|
var import_jose4 = require("jose");
|
|
2049
|
-
|
|
2050
|
-
// src/access-token-confirmation.ts
|
|
2051
|
-
var invalidAccessToken = (diagnostic) => new OidcVaultHttpError(401, "OIDC_VAULT_INVALID_ACCESS_TOKEN", diagnostic, "Access token validation failed.");
|
|
2052
|
-
var snapshotJkt = (value) => {
|
|
2053
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2054
|
-
throw invalidAccessToken("Access token confirmation must be a plain jkt object.");
|
|
2055
|
-
}
|
|
2056
|
-
const prototype = Object.getPrototypeOf(value);
|
|
2057
|
-
const keys = Reflect.ownKeys(value);
|
|
2058
|
-
if (prototype !== Object.prototype && prototype !== null || keys.length !== 1 || keys[0] !== "jkt") {
|
|
2059
|
-
throw invalidAccessToken("Access token confirmation contains unsupported fields.");
|
|
2060
|
-
}
|
|
2061
|
-
const { jkt } = value;
|
|
2062
|
-
if (!isCanonicalDpopJkt(jkt))
|
|
2063
|
-
throw invalidAccessToken("Access token confirmation is not a canonical SHA-256 thumbprint.");
|
|
2064
|
-
return Object.freeze({ jkt });
|
|
2065
|
-
};
|
|
2066
|
-
var snapshotVerifiedJwtConfirmation = (claims) => Object.hasOwn(claims, "cnf") ? snapshotJkt(claims.cnf) : null;
|
|
2067
|
-
var snapshotAccessTokenConfirmation = (value) => value === null ? null : snapshotJkt(value);
|
|
2068
|
-
|
|
2069
|
-
// src/token-validation.ts
|
|
2070
2081
|
var BACKCHANNEL_LOGOUT_EVENT_CLAIM = "http://schemas.openid.net/event/backchannel-logout";
|
|
2071
2082
|
var validateBackchannelLogoutTokenTyp = (typ) => {
|
|
2072
2083
|
if (typ !== void 0 && (typeof typ !== "string" || typ.toLowerCase() !== "logout+jwt")) {
|
|
@@ -2228,6 +2239,14 @@ function createOidcVaultJwtAccessTokenValidator(options) {
|
|
|
2228
2239
|
const result = await (0, import_jose4.jwtVerify)(token, key, { issuer, audience, algorithms });
|
|
2229
2240
|
const claims = result.payload;
|
|
2230
2241
|
const confirmation = snapshotVerifiedJwtConfirmation(claims);
|
|
2242
|
+
if (confirmation !== null) {
|
|
2243
|
+
if (!isCompactSignedJwt(token)) {
|
|
2244
|
+
throw invalidAccessToken("Bound JWT must use canonical compact signed encoding.");
|
|
2245
|
+
}
|
|
2246
|
+
if (readJwtExpirationMs(claims.exp) === void 0) {
|
|
2247
|
+
throw invalidAccessToken("Bound JWT exp must be a nonnegative safe integer with safe milliseconds.");
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2231
2250
|
const mapped = mapClaims ? mapClaims(claims) : defaultJwtClaimsMapper(claims);
|
|
2232
2251
|
const { subject, sessionId, scope, claims: mappedClaims } = mapped;
|
|
2233
2252
|
return { subject, sessionId, scope, claims: mappedClaims, confirmation };
|
package/index.mjs
CHANGED
|
@@ -1785,6 +1785,35 @@ async function fetchUserInfo(metadata, accessToken, options = {}) {
|
|
|
1785
1785
|
// src/token-issuance.ts
|
|
1786
1786
|
import { decodeJwt, decodeProtectedHeader } from "jose";
|
|
1787
1787
|
|
|
1788
|
+
// src/access-token-confirmation.ts
|
|
1789
|
+
var invalidAccessToken = (diagnostic) => new OidcVaultHttpError(401, "OIDC_VAULT_INVALID_ACCESS_TOKEN", diagnostic, "Access token validation failed.");
|
|
1790
|
+
var snapshotJkt = (value, invalid = invalidAccessToken) => {
|
|
1791
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1792
|
+
throw invalid("Access token confirmation must be a plain jkt object.");
|
|
1793
|
+
}
|
|
1794
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1795
|
+
const keys = Reflect.ownKeys(value);
|
|
1796
|
+
if (prototype !== Object.prototype && prototype !== null || keys.length !== 1 || keys[0] !== "jkt") {
|
|
1797
|
+
throw invalid("Access token confirmation contains unsupported fields.");
|
|
1798
|
+
}
|
|
1799
|
+
const { jkt } = value;
|
|
1800
|
+
if (!isCanonicalDpopJkt(jkt)) throw invalid("Access token confirmation is not a canonical SHA-256 thumbprint.");
|
|
1801
|
+
return Object.freeze({ jkt });
|
|
1802
|
+
};
|
|
1803
|
+
var isCompactSignedJwt = (token) => {
|
|
1804
|
+
const parts = token.split(".");
|
|
1805
|
+
return parts.length === 3 && parts.every(
|
|
1806
|
+
(part) => /^[A-Za-z0-9_-]+$/.test(part) ? Buffer.from(part, "base64url").toString("base64url") === part : false
|
|
1807
|
+
);
|
|
1808
|
+
};
|
|
1809
|
+
var readJwtExpirationMs = (exp) => {
|
|
1810
|
+
if (typeof exp !== "number" || !Number.isSafeInteger(exp) || exp < 0) return void 0;
|
|
1811
|
+
const milliseconds = exp * 1e3;
|
|
1812
|
+
return Number.isSafeInteger(milliseconds) ? milliseconds : void 0;
|
|
1813
|
+
};
|
|
1814
|
+
var snapshotVerifiedJwtConfirmation = (claims) => Object.hasOwn(claims, "cnf") ? snapshotJkt(claims.cnf) : null;
|
|
1815
|
+
var snapshotAccessTokenConfirmation = (value) => value === null ? null : snapshotJkt(value);
|
|
1816
|
+
|
|
1788
1817
|
// src/vault-session.ts
|
|
1789
1818
|
var invalidVaultSession = () => new OidcVaultHttpError(401, "OIDC_VAULT_INVALID_SESSION", "Session is missing or expired.");
|
|
1790
1819
|
var invalidExchangeCode = () => new OidcVaultHttpError(400, "OIDC_VAULT_INVALID_EXCHANGE_CODE", "Exchange code is invalid or expired.");
|
|
@@ -1929,13 +1958,7 @@ var createExchangeResponse = (options, session, issuedToken) => ({
|
|
|
1929
1958
|
expiresIn: issuedToken.expiresIn,
|
|
1930
1959
|
tokenType: issuedToken.tokenType
|
|
1931
1960
|
});
|
|
1932
|
-
var
|
|
1933
|
-
const parts = token.split(".");
|
|
1934
|
-
return parts.length === 3 && parts.every(
|
|
1935
|
-
(part) => /^[A-Za-z0-9_-]+$/.test(part) ? Buffer.from(part, "base64url").toString("base64url") === part : false
|
|
1936
|
-
);
|
|
1937
|
-
};
|
|
1938
|
-
var assertIssuedTokenConfirmation = (accessToken, jkt) => {
|
|
1961
|
+
var assertIssuedBoundJwt = (accessToken, jkt) => {
|
|
1939
1962
|
if (!isCompactSignedJwt(accessToken)) {
|
|
1940
1963
|
throw new TypeError("tokenIssuer.issue accessToken must be a compact signed JWT for bound issuance.");
|
|
1941
1964
|
}
|
|
@@ -1950,10 +1973,16 @@ var assertIssuedTokenConfirmation = (accessToken, jkt) => {
|
|
|
1950
1973
|
if (typeof header.alg !== "string" || header.alg.length === 0 || header.alg === "none" || header.b64 === false) {
|
|
1951
1974
|
throw new TypeError("tokenIssuer.issue accessToken must use a signed JWT algorithm for bound issuance.");
|
|
1952
1975
|
}
|
|
1953
|
-
const
|
|
1954
|
-
|
|
1976
|
+
const confirmation = snapshotJkt(
|
|
1977
|
+
claims.cnf,
|
|
1978
|
+
(diagnostic) => new TypeError(`tokenIssuer.issue accessToken cnf.jkt is invalid. ${diagnostic}`)
|
|
1979
|
+
);
|
|
1980
|
+
if (confirmation.jkt !== jkt) {
|
|
1955
1981
|
throw new TypeError("tokenIssuer.issue accessToken cnf.jkt must match the verified device binding.");
|
|
1956
1982
|
}
|
|
1983
|
+
if (readJwtExpirationMs(claims.exp) === void 0) {
|
|
1984
|
+
throw new TypeError("tokenIssuer.issue accessToken exp must be a nonnegative safe integer with safe milliseconds.");
|
|
1985
|
+
}
|
|
1957
1986
|
};
|
|
1958
1987
|
var withIssuedToken = async (req, res, options, session, verifiedBinding) => {
|
|
1959
1988
|
const binding = resolveVerifiedDeviceBinding(options.deviceBinding, session.deviceBinding, verifiedBinding);
|
|
@@ -1986,7 +2015,10 @@ var withIssuedToken = async (req, res, options, session, verifiedBinding) => {
|
|
|
1986
2015
|
}
|
|
1987
2016
|
} else {
|
|
1988
2017
|
if (tokenType !== "DPoP") throw new TypeError("tokenIssuer.issue tokenType must be DPoP for bound issuance.");
|
|
1989
|
-
|
|
2018
|
+
if (!Number.isSafeInteger(expiresIn * 1e3)) {
|
|
2019
|
+
throw new TypeError("tokenIssuer.issue expiresIn must have safe milliseconds for bound issuance.");
|
|
2020
|
+
}
|
|
2021
|
+
assertIssuedBoundJwt(accessToken, binding.jkt);
|
|
1990
2022
|
}
|
|
1991
2023
|
return { accessToken, expiresIn, ...tokenType === void 0 ? {} : { tokenType } };
|
|
1992
2024
|
} catch (error) {
|
|
@@ -1998,27 +2030,6 @@ var withIssuedToken = async (req, res, options, session, verifiedBinding) => {
|
|
|
1998
2030
|
|
|
1999
2031
|
// src/token-validation.ts
|
|
2000
2032
|
import { jwtVerify } from "jose";
|
|
2001
|
-
|
|
2002
|
-
// src/access-token-confirmation.ts
|
|
2003
|
-
var invalidAccessToken = (diagnostic) => new OidcVaultHttpError(401, "OIDC_VAULT_INVALID_ACCESS_TOKEN", diagnostic, "Access token validation failed.");
|
|
2004
|
-
var snapshotJkt = (value) => {
|
|
2005
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2006
|
-
throw invalidAccessToken("Access token confirmation must be a plain jkt object.");
|
|
2007
|
-
}
|
|
2008
|
-
const prototype = Object.getPrototypeOf(value);
|
|
2009
|
-
const keys = Reflect.ownKeys(value);
|
|
2010
|
-
if (prototype !== Object.prototype && prototype !== null || keys.length !== 1 || keys[0] !== "jkt") {
|
|
2011
|
-
throw invalidAccessToken("Access token confirmation contains unsupported fields.");
|
|
2012
|
-
}
|
|
2013
|
-
const { jkt } = value;
|
|
2014
|
-
if (!isCanonicalDpopJkt(jkt))
|
|
2015
|
-
throw invalidAccessToken("Access token confirmation is not a canonical SHA-256 thumbprint.");
|
|
2016
|
-
return Object.freeze({ jkt });
|
|
2017
|
-
};
|
|
2018
|
-
var snapshotVerifiedJwtConfirmation = (claims) => Object.hasOwn(claims, "cnf") ? snapshotJkt(claims.cnf) : null;
|
|
2019
|
-
var snapshotAccessTokenConfirmation = (value) => value === null ? null : snapshotJkt(value);
|
|
2020
|
-
|
|
2021
|
-
// src/token-validation.ts
|
|
2022
2033
|
var BACKCHANNEL_LOGOUT_EVENT_CLAIM = "http://schemas.openid.net/event/backchannel-logout";
|
|
2023
2034
|
var validateBackchannelLogoutTokenTyp = (typ) => {
|
|
2024
2035
|
if (typ !== void 0 && (typeof typ !== "string" || typ.toLowerCase() !== "logout+jwt")) {
|
|
@@ -2180,6 +2191,14 @@ function createOidcVaultJwtAccessTokenValidator(options) {
|
|
|
2180
2191
|
const result = await jwtVerify(token, key, { issuer, audience, algorithms });
|
|
2181
2192
|
const claims = result.payload;
|
|
2182
2193
|
const confirmation = snapshotVerifiedJwtConfirmation(claims);
|
|
2194
|
+
if (confirmation !== null) {
|
|
2195
|
+
if (!isCompactSignedJwt(token)) {
|
|
2196
|
+
throw invalidAccessToken("Bound JWT must use canonical compact signed encoding.");
|
|
2197
|
+
}
|
|
2198
|
+
if (readJwtExpirationMs(claims.exp) === void 0) {
|
|
2199
|
+
throw invalidAccessToken("Bound JWT exp must be a nonnegative safe integer with safe milliseconds.");
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2183
2202
|
const mapped = mapClaims ? mapClaims(claims) : defaultJwtClaimsMapper(claims);
|
|
2184
2203
|
const { subject, sessionId, scope, claims: mappedClaims } = mapped;
|
|
2185
2204
|
return { subject, sessionId, scope, claims: mappedClaims, confirmation };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@web-ts-toolkit/express-oidc-vault",
|
|
3
3
|
"description": "Express OIDC sessions with body or cookie transport, optional DPoP-bound local JWTs, and pluggable vault stores",
|
|
4
4
|
"homepage": "https://web-ts-toolkit.pages.dev/docs/packages/express-oidc-vault",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.53.0",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"keywords": [
|
|
8
8
|
"express",
|
|
@@ -26,6 +26,16 @@
|
|
|
26
26
|
"import": "./index.mjs",
|
|
27
27
|
"require": "./index.js",
|
|
28
28
|
"default": "./index.js"
|
|
29
|
+
},
|
|
30
|
+
"./wire": {
|
|
31
|
+
"types": {
|
|
32
|
+
"import": "./wire.d.mts",
|
|
33
|
+
"require": "./wire.d.ts",
|
|
34
|
+
"default": "./wire.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"import": "./wire.mjs",
|
|
37
|
+
"require": "./wire.js",
|
|
38
|
+
"default": "./wire.js"
|
|
29
39
|
}
|
|
30
40
|
},
|
|
31
41
|
"engines": {
|
package/wire.d.mts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** Public JSON profile returned by exchange/refresh. */
|
|
2
|
+
interface OidcVaultUserProfile {
|
|
3
|
+
sub: string;
|
|
4
|
+
email?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
preferredUsername?: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Local application credentials returned by `OidcVaultTokenIssuer.issue`.
|
|
11
|
+
* Runtime validation copies only these fields into a fresh result; additional
|
|
12
|
+
* properties are ignored and cannot override the response's session ID/user.
|
|
13
|
+
*/
|
|
14
|
+
interface OidcVaultTokenIssueResult {
|
|
15
|
+
/**
|
|
16
|
+
* Nonempty opaque token for unbound issuance. Bound issuance requires a
|
|
17
|
+
* compact signed JWT (canonical unpadded base64url), exactly one cnf field
|
|
18
|
+
* `{ jkt }` matching the verified canonical SHA-256 thumbprint, and a
|
|
19
|
+
* nonnegative safe-integer exp in seconds with safe exp * 1000 milliseconds.
|
|
20
|
+
* Core checks the output shape; APIs independently authenticate the JWT.
|
|
21
|
+
*/
|
|
22
|
+
accessToken: string;
|
|
23
|
+
/**
|
|
24
|
+
* Token lifetime in seconds: a finite nonnegative safe integer; bound
|
|
25
|
+
* issuance also requires safe expiresIn * 1000. Zero is valid and may carry
|
|
26
|
+
* a rotated session handle, but supplies no live reusable API credential.
|
|
27
|
+
*/
|
|
28
|
+
expiresIn: number;
|
|
29
|
+
/** Exact `DPoP` is required when bound. Unbound permits only `Bearer` or omission; omitted/undefined stays absent in JSON. */
|
|
30
|
+
tokenType?: 'Bearer' | 'DPoP';
|
|
31
|
+
}
|
|
32
|
+
/** Exchange/refresh JSON. Local token fields are absent when no token issuer is configured. */
|
|
33
|
+
interface OidcVaultExchangeResult extends Partial<OidcVaultTokenIssueResult> {
|
|
34
|
+
/** Authoritative vault handle in body transport; omitted from cookie-transport JSON. */
|
|
35
|
+
sessionId?: string;
|
|
36
|
+
/** Session profile; extra token-issuer result properties cannot override it. */
|
|
37
|
+
user?: OidcVaultUserProfile;
|
|
38
|
+
}
|
|
39
|
+
/** JSON body for opt-in `POST <basePath>/login`; proof/key and recognition signals belong in their configured headers, not the body. */
|
|
40
|
+
interface OidcVaultLoginInitiationInput {
|
|
41
|
+
/** Optional destination on the configured frontend origin. Query-string returnTo is ignored by POST login. */
|
|
42
|
+
returnTo?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Exact successful POST-login JSON. Navigate to this URL after storing a proof
|
|
46
|
+
* key when DPoP is enabled. Use credentials: 'include' even with body session
|
|
47
|
+
* transport: initiation sets a temporary HttpOnly transaction cookie.
|
|
48
|
+
*/
|
|
49
|
+
interface OidcVaultLoginInitiationResult {
|
|
50
|
+
authorizationUrl: string;
|
|
51
|
+
}
|
|
52
|
+
/** Successful local logout; stateless application JWTs require separate revocation or expiry. */
|
|
53
|
+
interface OidcVaultLogoutResult {
|
|
54
|
+
/**
|
|
55
|
+
* Successful logout commits revocation before upstream work and clears the
|
|
56
|
+
* session cookie under cookie transport. Built-in live handles and unexpired
|
|
57
|
+
* aliases authenticate the surviving lineage's provider/original binding;
|
|
58
|
+
* wrong identity, missing/wrong/stale/replayed proof, or disabled acceptance
|
|
59
|
+
* of a bound lineage fails without deleting or clearing. No currently live
|
|
60
|
+
* target is idempotent success without deletion or proof reservation. Aliases
|
|
61
|
+
* grant revocation only, not refresh authentication or upstream redirects.
|
|
62
|
+
* Old custom bearer stores without revocation context retain legacy alias
|
|
63
|
+
* deletion behavior. Stateless application access tokens are not invalidated.
|
|
64
|
+
* Local-only logout (`redirect` unset or `false`) never contacts the provider.
|
|
65
|
+
* Redirected logout (`redirect: true`)
|
|
66
|
+
* treats the upstream end-session redirect as best-effort: when provider
|
|
67
|
+
* discovery fails or no `endSessionEndpoint` is available, the route still
|
|
68
|
+
* returns this local success. Discovery errors reach `onError` without
|
|
69
|
+
* undoing revocation or skipping the live session's `onLogout` notification.
|
|
70
|
+
*/
|
|
71
|
+
loggedOut: true;
|
|
72
|
+
}
|
|
73
|
+
/** Vault handle transport; independent of DPoP mode. Default: body. */
|
|
74
|
+
type OidcVaultSessionTransport = 'body' | 'cookie';
|
|
75
|
+
|
|
76
|
+
export type { OidcVaultExchangeResult, OidcVaultLoginInitiationInput, OidcVaultLoginInitiationResult, OidcVaultLogoutResult, OidcVaultSessionTransport, OidcVaultTokenIssueResult, OidcVaultUserProfile };
|