@web-ts-toolkit/oidc-vault-dpop-client 0.48.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/LICENSE +201 -0
- package/README.md +398 -0
- package/index.d.mts +269 -0
- package/index.d.ts +269 -0
- package/index.js +875 -0
- package/index.mjs +842 -0
- package/llms.txt +45 -0
- package/package.json +54 -0
package/index.d.mts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-only DPoP client (CLIENT-02 port of apps/oidc-vault-dpop-example/src/auth/*).
|
|
3
|
+
* No `node:*` or `express` imports. Browser globals (crypto.subtle, indexedDB,
|
|
4
|
+
* sessionStorage, navigator.locks, BroadcastChannel) only behind
|
|
5
|
+
* assertDpopBrowserFeatures / lazy factory calls, never at module top-level.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface PublicDpopJwk {
|
|
9
|
+
kty: 'EC';
|
|
10
|
+
crv: 'P-256';
|
|
11
|
+
x: string;
|
|
12
|
+
y: string;
|
|
13
|
+
}
|
|
14
|
+
interface DpopKey {
|
|
15
|
+
readonly privateKey: CryptoKey;
|
|
16
|
+
readonly publicJwk: Readonly<PublicDpopJwk>;
|
|
17
|
+
readonly jkt: string;
|
|
18
|
+
readonly scopeId: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface DpopKeyScope {
|
|
22
|
+
frontendOrigin: string;
|
|
23
|
+
backendOrigin: string;
|
|
24
|
+
basePath: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Read (or, at fresh login, atomically create) the non-extractable ES256/P-256
|
|
29
|
+
* browser key scoped to `[frontendOrigin, backendOrigin, basePath]`.
|
|
30
|
+
*
|
|
31
|
+
* Call with the default `create=true` ONLY at fresh login (`session.login()`
|
|
32
|
+
* does this for you). All credential-bearing operations use `create=false`,
|
|
33
|
+
* so losing IndexedDB cannot silently rebind a login: a missing or changed
|
|
34
|
+
* key throws `DPOP_KEY_LOST` and the caller must start a fresh login. There is
|
|
35
|
+
* no ephemeral-key or Bearer fallback.
|
|
36
|
+
*
|
|
37
|
+
* Candidate crypto work happens outside the readwrite transaction. IndexedDB
|
|
38
|
+
* serializes the final read/add across tabs, making exactly one candidate win.
|
|
39
|
+
*
|
|
40
|
+
* Canonical import: `import { getOrCreateDpopKey } from
|
|
41
|
+
* '@web-ts-toolkit/oidc-vault-dpop-client'` (named root import).
|
|
42
|
+
*/
|
|
43
|
+
declare const getOrCreateDpopKey: (input: DpopKeyScope, options?: {
|
|
44
|
+
create?: boolean;
|
|
45
|
+
}) => Promise<DpopKey>;
|
|
46
|
+
|
|
47
|
+
interface DpopProofInput {
|
|
48
|
+
method: string;
|
|
49
|
+
url: string | URL;
|
|
50
|
+
/** API only. Vault login/exchange/refresh/logout present no access token. */
|
|
51
|
+
accessToken?: string;
|
|
52
|
+
nonce?: string;
|
|
53
|
+
now?: () => number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Mint a fresh DPoP proof for exactly one request attempt with the scoped key.
|
|
57
|
+
*
|
|
58
|
+
* Every invocation signs a new proof: exact uppercase `htm`, canonical absolute
|
|
59
|
+
* `htu` without query/fragment (see `normalizeDpopTarget`), current integer
|
|
60
|
+
* `iat`, fresh 128-bit random `jti`, API-only `ath`, and the optional server
|
|
61
|
+
* `nonce`. The protected header is `{ typ: 'dpop+jwt', alg: 'ES256', jwk:
|
|
62
|
+
* <public P-256 JWK> }`. Call again — with a new `jti`/`iat`/signature — for
|
|
63
|
+
* every nonce or refresh retry; never reuse a proof across attempts.
|
|
64
|
+
*
|
|
65
|
+
* Canonical import: `import { createDpopProof } from
|
|
66
|
+
* '@web-ts-toolkit/oidc-vault-dpop-client'` (named root import).
|
|
67
|
+
*/
|
|
68
|
+
declare const createDpopProof: (key: DpopKey, input: DpopProofInput) => Promise<string>;
|
|
69
|
+
|
|
70
|
+
/** DPoP proof algorithms, independent of the app-local access token's signing algorithm. */
|
|
71
|
+
type OidcVaultDpopAlgorithm = 'ES256' | 'PS256' | 'RS256';
|
|
72
|
+
/**
|
|
73
|
+
* Immutable persisted sender constraint. `jkt` is the canonical RFC 7638
|
|
74
|
+
* SHA-256 JWK thumbprint (43 base64url characters). Store no JWK, private key,
|
|
75
|
+
* proof algorithm, or historical enforcement mode in this record.
|
|
76
|
+
*/
|
|
77
|
+
interface OidcVaultDpopBinding {
|
|
78
|
+
type: 'dpop';
|
|
79
|
+
jkt: string;
|
|
80
|
+
}
|
|
81
|
+
/** Verified request context; `alg` is checked against the current proof policy. */
|
|
82
|
+
interface OidcVaultVerifiedDpopBinding extends OidcVaultDpopBinding {
|
|
83
|
+
alg: OidcVaultDpopAlgorithm;
|
|
84
|
+
}
|
|
85
|
+
interface OidcVaultUserProfile {
|
|
86
|
+
sub: string;
|
|
87
|
+
email?: string;
|
|
88
|
+
name?: string;
|
|
89
|
+
preferredUsername?: string;
|
|
90
|
+
[key: string]: unknown;
|
|
91
|
+
}
|
|
92
|
+
interface OidcVaultAccessTokenValidationResult {
|
|
93
|
+
subject: string;
|
|
94
|
+
sessionId?: string;
|
|
95
|
+
scope?: string;
|
|
96
|
+
claims?: Record<string, unknown>;
|
|
97
|
+
/** Verified sender constraint. Legacy adapters may omit it only for genuinely unbound credentials. */
|
|
98
|
+
confirmation?: OidcVaultAccessTokenConfirmation | null;
|
|
99
|
+
}
|
|
100
|
+
/** Canonical RFC 7638 SHA-256 thumbprint derived from verified token/introspection data, never from a proof alone. */
|
|
101
|
+
interface OidcVaultAccessTokenConfirmation {
|
|
102
|
+
jkt: string;
|
|
103
|
+
}
|
|
104
|
+
interface OidcVaultAuthContext extends OidcVaultAccessTokenValidationResult {
|
|
105
|
+
token: string;
|
|
106
|
+
/** Present only after original-key proof, API ath, nonce and shared replay admission all pass. */
|
|
107
|
+
deviceBinding?: Readonly<OidcVaultVerifiedDpopBinding>;
|
|
108
|
+
}
|
|
109
|
+
declare module 'express-serve-static-core' {
|
|
110
|
+
interface Request {
|
|
111
|
+
auth?: OidcVaultAuthContext;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
type OidcVaultSessionTransport = 'body' | 'cookie';
|
|
115
|
+
|
|
116
|
+
interface DpopAccessToken {
|
|
117
|
+
readonly accessToken: string;
|
|
118
|
+
readonly tokenType: 'DPoP';
|
|
119
|
+
readonly expiresAt: number;
|
|
120
|
+
readonly jkt: string;
|
|
121
|
+
readonly generation: string;
|
|
122
|
+
readonly user?: OidcVaultUserProfile;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Browser-only DPoP client (CLIENT-02 port of apps/oidc-vault-dpop-example/src/auth/*).
|
|
127
|
+
* No `node:*` or `express` imports. Browser globals (crypto.subtle, indexedDB,
|
|
128
|
+
* sessionStorage, navigator.locks, BroadcastChannel) only behind
|
|
129
|
+
* assertDpopBrowserFeatures / lazy factory calls, never at module top-level.
|
|
130
|
+
*/
|
|
131
|
+
/** Copyable private example helper. Recognition is NOT proof of possession or an API sender constraint. */
|
|
132
|
+
type DeviceFingerprintSignalSource = () => Promise<string | undefined>;
|
|
133
|
+
interface DeviceFingerprintOptions {
|
|
134
|
+
/** Must match the backend's fingerprintRecognition.headerName; default X-Device-Fingerprint. */
|
|
135
|
+
headerName?: string;
|
|
136
|
+
}
|
|
137
|
+
interface DeviceFingerprint {
|
|
138
|
+
/**
|
|
139
|
+
* Obtain the current signal for POST login/exchange/refresh only. No signal is
|
|
140
|
+
* persisted or cached here. Undefined deliberately means unenrolled; collection
|
|
141
|
+
* failures throw rather than silently disabling an enrolled session's check.
|
|
142
|
+
* Call only after the application's collection/disclosure choice and scope
|
|
143
|
+
* these headers to its configured vault origin. Logout/API need no signal.
|
|
144
|
+
*/
|
|
145
|
+
headers(): Promise<Readonly<Record<string, string>>>;
|
|
146
|
+
}
|
|
147
|
+
/** Generic injected source: no browser collection, vendor import, storage, or network activity at construction. */
|
|
148
|
+
declare const createDeviceFingerprint: (source: DeviceFingerprintSignalSource, options?: DeviceFingerprintOptions) => DeviceFingerprint;
|
|
149
|
+
/** Structural adapter for optional frontend FingerprintJS; no package dependency is required by this helper. */
|
|
150
|
+
interface FingerprintJsAgent {
|
|
151
|
+
get(): Promise<{
|
|
152
|
+
visitorId: string;
|
|
153
|
+
}>;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Inject () => FingerprintJS.load() if the frontend opts into that dependency.
|
|
157
|
+
* Only the agent load is shared; get() runs for each operation, so the helper
|
|
158
|
+
* never pins an old identifier through a recognition change. Failed load can
|
|
159
|
+
* be retried explicitly. Wrap with createDeviceFingerprint for bounded signals
|
|
160
|
+
* and fixed errors that do not echo vendor diagnostics/identifiers.
|
|
161
|
+
*/
|
|
162
|
+
declare const fingerprintJsSignalSource: (load: () => Promise<FingerprintJsAgent>) => DeviceFingerprintSignalSource;
|
|
163
|
+
|
|
164
|
+
interface OidcVaultDpopSessionOptions {
|
|
165
|
+
backendOrigin: string;
|
|
166
|
+
basePath?: string;
|
|
167
|
+
sessionTransport?: OidcVaultSessionTransport;
|
|
168
|
+
/** Current signal on login/exchange/refresh only; optional recognition, never PoP. */
|
|
169
|
+
fingerprint?: DeviceFingerprint;
|
|
170
|
+
fetch?: typeof globalThis.fetch;
|
|
171
|
+
navigate?: (authorizationUrl: string) => void;
|
|
172
|
+
now?: () => number;
|
|
173
|
+
}
|
|
174
|
+
interface OidcVaultDpopSession {
|
|
175
|
+
readonly scopeId: string;
|
|
176
|
+
readonly sessionTransport: OidcVaultSessionTransport;
|
|
177
|
+
login(returnTo?: string): Promise<void>;
|
|
178
|
+
exchange(code: string): Promise<DpopAccessToken>;
|
|
179
|
+
/** Single-flight; no Authorization/ath even if the old JWT expired. */
|
|
180
|
+
refresh(options?: {
|
|
181
|
+
rejectedToken?: string;
|
|
182
|
+
}): Promise<DpopAccessToken>;
|
|
183
|
+
logout(): Promise<void>;
|
|
184
|
+
getAccessToken(): DpopAccessToken | undefined;
|
|
185
|
+
/** Existing key only. Missing/changed persistence clears auth and requires login. */
|
|
186
|
+
getKey(): Promise<DpopKey>;
|
|
187
|
+
clear(): Promise<void>;
|
|
188
|
+
dispose(): void;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Create a browser DPoP session for one vault mount (`backendOrigin` + `basePath`).
|
|
192
|
+
* Canonical import: `import { createOidcVaultDpopSession } from
|
|
193
|
+
* '@web-ts-toolkit/oidc-vault-dpop-client'` (named root import, no default export).
|
|
194
|
+
* Lifecycle: `login` → `exchange(code)` → `getAccessToken`/`fetchWithDpop` →
|
|
195
|
+
* `refresh` → `logout`. Cookie transport additionally needs Web Locks +
|
|
196
|
+
* `BroadcastChannel` at runtime (see `assertDpopBrowserFeatures`).
|
|
197
|
+
* Copyable app helper. It is not an export of the Express package.
|
|
198
|
+
*/
|
|
199
|
+
declare const createOidcVaultDpopSession: (options: OidcVaultDpopSessionOptions) => OidcVaultDpopSession;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Browser-only DPoP client (CLIENT-02 port of apps/oidc-vault-dpop-example/src/auth/*).
|
|
203
|
+
* No `node:*` or `express` imports. Browser globals (crypto.subtle, indexedDB,
|
|
204
|
+
* sessionStorage, navigator.locks, BroadcastChannel) only behind
|
|
205
|
+
* assertDpopBrowserFeatures / lazy factory calls, never at module top-level.
|
|
206
|
+
*/
|
|
207
|
+
declare class DpopNonceCache {
|
|
208
|
+
private readonly nonces;
|
|
209
|
+
get(space: string, jkt: string): string | undefined;
|
|
210
|
+
remember(space: string, jkt: string, nonce: string | null): boolean;
|
|
211
|
+
clear(): void;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Browser-only DPoP client (CLIENT-02 port of apps/oidc-vault-dpop-example/src/auth/*).
|
|
216
|
+
* No `node:*` or `express` imports. Browser globals (crypto.subtle, indexedDB,
|
|
217
|
+
* sessionStorage, navigator.locks, BroadcastChannel) only behind
|
|
218
|
+
* assertDpopBrowserFeatures / lazy factory calls, never at module top-level.
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
interface DpopApi {
|
|
222
|
+
/** Exact origin receiving the proof/Authorization. No wildcard or redirects. */
|
|
223
|
+
origin: string;
|
|
224
|
+
/** Backend API replayNamespace (the nonce protection space), not a route. */
|
|
225
|
+
replayNamespace: string;
|
|
226
|
+
/** Usually omit for JWT APIs; include only for an explicitly cookie-using API. */
|
|
227
|
+
credentials?: 'omit' | 'include';
|
|
228
|
+
}
|
|
229
|
+
interface DpopFetchContext {
|
|
230
|
+
session: OidcVaultDpopSession;
|
|
231
|
+
apis: readonly DpopApi[];
|
|
232
|
+
fetch?: typeof globalThis.fetch;
|
|
233
|
+
nonces?: DpopNonceCache;
|
|
234
|
+
now?: () => number;
|
|
235
|
+
}
|
|
236
|
+
interface DpopFetchOptions extends Omit<RequestInit, 'redirect' | 'credentials'> {
|
|
237
|
+
/**
|
|
238
|
+
* GET/HEAD/OPTIONS may retry. All other methods default to one attempt.
|
|
239
|
+
* Explicitly authorize idempotent retries only for a server operation whose
|
|
240
|
+
* contract supports them (e.g. PUT or POST with a server idempotency key).
|
|
241
|
+
*/
|
|
242
|
+
retry?: 'never' | 'idempotent';
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Scoped DPoP `fetch` for one configured API origin: mints a fresh proof per
|
|
246
|
+
* attempt, sends `Authorization: DPoP`, then retries once on a DPoP nonce
|
|
247
|
+
* challenge and at most once via `session.refresh()` on `invalid_token`.
|
|
248
|
+
* Canonical import: `import { fetchWithDpop } from
|
|
249
|
+
* '@web-ts-toolkit/oidc-vault-dpop-client'` (named root import). Only origins
|
|
250
|
+
* listed in `context.apis` are called; authentication headers are owned by the
|
|
251
|
+
* helper and must not be set by callers.
|
|
252
|
+
*/
|
|
253
|
+
declare const fetchWithDpop: (context: DpopFetchContext, input: string | URL, options?: DpopFetchOptions) => Promise<Response>;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Browser-only DPoP client (CLIENT-02 port of apps/oidc-vault-dpop-example/src/auth/*).
|
|
257
|
+
* No `node:*` or `express` imports. Browser globals (crypto.subtle, indexedDB,
|
|
258
|
+
* sessionStorage, navigator.locks, BroadcastChannel) only behind
|
|
259
|
+
* assertDpopBrowserFeatures / lazy factory calls, never at module top-level.
|
|
260
|
+
*/
|
|
261
|
+
/** Fixed local failures; never include keys, credentials, fingerprints or URLs. */
|
|
262
|
+
declare class OidcVaultDpopClientError extends Error {
|
|
263
|
+
readonly code: string;
|
|
264
|
+
readonly requiresLogin: boolean;
|
|
265
|
+
readonly status?: number;
|
|
266
|
+
constructor(code: string, message: string, requiresLogin?: boolean, status?: number);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export { type DeviceFingerprint, type DeviceFingerprintOptions, type DeviceFingerprintSignalSource, type DpopAccessToken, type DpopApi, type DpopFetchContext, type DpopFetchOptions, type DpopKey, type DpopKeyScope, DpopNonceCache, type DpopProofInput, type FingerprintJsAgent, OidcVaultDpopClientError, type OidcVaultDpopSession, type OidcVaultDpopSessionOptions, createDeviceFingerprint, createDpopProof, createOidcVaultDpopSession, fetchWithDpop, fingerprintJsSignalSource, getOrCreateDpopKey };
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-only DPoP client (CLIENT-02 port of apps/oidc-vault-dpop-example/src/auth/*).
|
|
3
|
+
* No `node:*` or `express` imports. Browser globals (crypto.subtle, indexedDB,
|
|
4
|
+
* sessionStorage, navigator.locks, BroadcastChannel) only behind
|
|
5
|
+
* assertDpopBrowserFeatures / lazy factory calls, never at module top-level.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface PublicDpopJwk {
|
|
9
|
+
kty: 'EC';
|
|
10
|
+
crv: 'P-256';
|
|
11
|
+
x: string;
|
|
12
|
+
y: string;
|
|
13
|
+
}
|
|
14
|
+
interface DpopKey {
|
|
15
|
+
readonly privateKey: CryptoKey;
|
|
16
|
+
readonly publicJwk: Readonly<PublicDpopJwk>;
|
|
17
|
+
readonly jkt: string;
|
|
18
|
+
readonly scopeId: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface DpopKeyScope {
|
|
22
|
+
frontendOrigin: string;
|
|
23
|
+
backendOrigin: string;
|
|
24
|
+
basePath: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Read (or, at fresh login, atomically create) the non-extractable ES256/P-256
|
|
29
|
+
* browser key scoped to `[frontendOrigin, backendOrigin, basePath]`.
|
|
30
|
+
*
|
|
31
|
+
* Call with the default `create=true` ONLY at fresh login (`session.login()`
|
|
32
|
+
* does this for you). All credential-bearing operations use `create=false`,
|
|
33
|
+
* so losing IndexedDB cannot silently rebind a login: a missing or changed
|
|
34
|
+
* key throws `DPOP_KEY_LOST` and the caller must start a fresh login. There is
|
|
35
|
+
* no ephemeral-key or Bearer fallback.
|
|
36
|
+
*
|
|
37
|
+
* Candidate crypto work happens outside the readwrite transaction. IndexedDB
|
|
38
|
+
* serializes the final read/add across tabs, making exactly one candidate win.
|
|
39
|
+
*
|
|
40
|
+
* Canonical import: `import { getOrCreateDpopKey } from
|
|
41
|
+
* '@web-ts-toolkit/oidc-vault-dpop-client'` (named root import).
|
|
42
|
+
*/
|
|
43
|
+
declare const getOrCreateDpopKey: (input: DpopKeyScope, options?: {
|
|
44
|
+
create?: boolean;
|
|
45
|
+
}) => Promise<DpopKey>;
|
|
46
|
+
|
|
47
|
+
interface DpopProofInput {
|
|
48
|
+
method: string;
|
|
49
|
+
url: string | URL;
|
|
50
|
+
/** API only. Vault login/exchange/refresh/logout present no access token. */
|
|
51
|
+
accessToken?: string;
|
|
52
|
+
nonce?: string;
|
|
53
|
+
now?: () => number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Mint a fresh DPoP proof for exactly one request attempt with the scoped key.
|
|
57
|
+
*
|
|
58
|
+
* Every invocation signs a new proof: exact uppercase `htm`, canonical absolute
|
|
59
|
+
* `htu` without query/fragment (see `normalizeDpopTarget`), current integer
|
|
60
|
+
* `iat`, fresh 128-bit random `jti`, API-only `ath`, and the optional server
|
|
61
|
+
* `nonce`. The protected header is `{ typ: 'dpop+jwt', alg: 'ES256', jwk:
|
|
62
|
+
* <public P-256 JWK> }`. Call again — with a new `jti`/`iat`/signature — for
|
|
63
|
+
* every nonce or refresh retry; never reuse a proof across attempts.
|
|
64
|
+
*
|
|
65
|
+
* Canonical import: `import { createDpopProof } from
|
|
66
|
+
* '@web-ts-toolkit/oidc-vault-dpop-client'` (named root import).
|
|
67
|
+
*/
|
|
68
|
+
declare const createDpopProof: (key: DpopKey, input: DpopProofInput) => Promise<string>;
|
|
69
|
+
|
|
70
|
+
/** DPoP proof algorithms, independent of the app-local access token's signing algorithm. */
|
|
71
|
+
type OidcVaultDpopAlgorithm = 'ES256' | 'PS256' | 'RS256';
|
|
72
|
+
/**
|
|
73
|
+
* Immutable persisted sender constraint. `jkt` is the canonical RFC 7638
|
|
74
|
+
* SHA-256 JWK thumbprint (43 base64url characters). Store no JWK, private key,
|
|
75
|
+
* proof algorithm, or historical enforcement mode in this record.
|
|
76
|
+
*/
|
|
77
|
+
interface OidcVaultDpopBinding {
|
|
78
|
+
type: 'dpop';
|
|
79
|
+
jkt: string;
|
|
80
|
+
}
|
|
81
|
+
/** Verified request context; `alg` is checked against the current proof policy. */
|
|
82
|
+
interface OidcVaultVerifiedDpopBinding extends OidcVaultDpopBinding {
|
|
83
|
+
alg: OidcVaultDpopAlgorithm;
|
|
84
|
+
}
|
|
85
|
+
interface OidcVaultUserProfile {
|
|
86
|
+
sub: string;
|
|
87
|
+
email?: string;
|
|
88
|
+
name?: string;
|
|
89
|
+
preferredUsername?: string;
|
|
90
|
+
[key: string]: unknown;
|
|
91
|
+
}
|
|
92
|
+
interface OidcVaultAccessTokenValidationResult {
|
|
93
|
+
subject: string;
|
|
94
|
+
sessionId?: string;
|
|
95
|
+
scope?: string;
|
|
96
|
+
claims?: Record<string, unknown>;
|
|
97
|
+
/** Verified sender constraint. Legacy adapters may omit it only for genuinely unbound credentials. */
|
|
98
|
+
confirmation?: OidcVaultAccessTokenConfirmation | null;
|
|
99
|
+
}
|
|
100
|
+
/** Canonical RFC 7638 SHA-256 thumbprint derived from verified token/introspection data, never from a proof alone. */
|
|
101
|
+
interface OidcVaultAccessTokenConfirmation {
|
|
102
|
+
jkt: string;
|
|
103
|
+
}
|
|
104
|
+
interface OidcVaultAuthContext extends OidcVaultAccessTokenValidationResult {
|
|
105
|
+
token: string;
|
|
106
|
+
/** Present only after original-key proof, API ath, nonce and shared replay admission all pass. */
|
|
107
|
+
deviceBinding?: Readonly<OidcVaultVerifiedDpopBinding>;
|
|
108
|
+
}
|
|
109
|
+
declare module 'express-serve-static-core' {
|
|
110
|
+
interface Request {
|
|
111
|
+
auth?: OidcVaultAuthContext;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
type OidcVaultSessionTransport = 'body' | 'cookie';
|
|
115
|
+
|
|
116
|
+
interface DpopAccessToken {
|
|
117
|
+
readonly accessToken: string;
|
|
118
|
+
readonly tokenType: 'DPoP';
|
|
119
|
+
readonly expiresAt: number;
|
|
120
|
+
readonly jkt: string;
|
|
121
|
+
readonly generation: string;
|
|
122
|
+
readonly user?: OidcVaultUserProfile;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Browser-only DPoP client (CLIENT-02 port of apps/oidc-vault-dpop-example/src/auth/*).
|
|
127
|
+
* No `node:*` or `express` imports. Browser globals (crypto.subtle, indexedDB,
|
|
128
|
+
* sessionStorage, navigator.locks, BroadcastChannel) only behind
|
|
129
|
+
* assertDpopBrowserFeatures / lazy factory calls, never at module top-level.
|
|
130
|
+
*/
|
|
131
|
+
/** Copyable private example helper. Recognition is NOT proof of possession or an API sender constraint. */
|
|
132
|
+
type DeviceFingerprintSignalSource = () => Promise<string | undefined>;
|
|
133
|
+
interface DeviceFingerprintOptions {
|
|
134
|
+
/** Must match the backend's fingerprintRecognition.headerName; default X-Device-Fingerprint. */
|
|
135
|
+
headerName?: string;
|
|
136
|
+
}
|
|
137
|
+
interface DeviceFingerprint {
|
|
138
|
+
/**
|
|
139
|
+
* Obtain the current signal for POST login/exchange/refresh only. No signal is
|
|
140
|
+
* persisted or cached here. Undefined deliberately means unenrolled; collection
|
|
141
|
+
* failures throw rather than silently disabling an enrolled session's check.
|
|
142
|
+
* Call only after the application's collection/disclosure choice and scope
|
|
143
|
+
* these headers to its configured vault origin. Logout/API need no signal.
|
|
144
|
+
*/
|
|
145
|
+
headers(): Promise<Readonly<Record<string, string>>>;
|
|
146
|
+
}
|
|
147
|
+
/** Generic injected source: no browser collection, vendor import, storage, or network activity at construction. */
|
|
148
|
+
declare const createDeviceFingerprint: (source: DeviceFingerprintSignalSource, options?: DeviceFingerprintOptions) => DeviceFingerprint;
|
|
149
|
+
/** Structural adapter for optional frontend FingerprintJS; no package dependency is required by this helper. */
|
|
150
|
+
interface FingerprintJsAgent {
|
|
151
|
+
get(): Promise<{
|
|
152
|
+
visitorId: string;
|
|
153
|
+
}>;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Inject () => FingerprintJS.load() if the frontend opts into that dependency.
|
|
157
|
+
* Only the agent load is shared; get() runs for each operation, so the helper
|
|
158
|
+
* never pins an old identifier through a recognition change. Failed load can
|
|
159
|
+
* be retried explicitly. Wrap with createDeviceFingerprint for bounded signals
|
|
160
|
+
* and fixed errors that do not echo vendor diagnostics/identifiers.
|
|
161
|
+
*/
|
|
162
|
+
declare const fingerprintJsSignalSource: (load: () => Promise<FingerprintJsAgent>) => DeviceFingerprintSignalSource;
|
|
163
|
+
|
|
164
|
+
interface OidcVaultDpopSessionOptions {
|
|
165
|
+
backendOrigin: string;
|
|
166
|
+
basePath?: string;
|
|
167
|
+
sessionTransport?: OidcVaultSessionTransport;
|
|
168
|
+
/** Current signal on login/exchange/refresh only; optional recognition, never PoP. */
|
|
169
|
+
fingerprint?: DeviceFingerprint;
|
|
170
|
+
fetch?: typeof globalThis.fetch;
|
|
171
|
+
navigate?: (authorizationUrl: string) => void;
|
|
172
|
+
now?: () => number;
|
|
173
|
+
}
|
|
174
|
+
interface OidcVaultDpopSession {
|
|
175
|
+
readonly scopeId: string;
|
|
176
|
+
readonly sessionTransport: OidcVaultSessionTransport;
|
|
177
|
+
login(returnTo?: string): Promise<void>;
|
|
178
|
+
exchange(code: string): Promise<DpopAccessToken>;
|
|
179
|
+
/** Single-flight; no Authorization/ath even if the old JWT expired. */
|
|
180
|
+
refresh(options?: {
|
|
181
|
+
rejectedToken?: string;
|
|
182
|
+
}): Promise<DpopAccessToken>;
|
|
183
|
+
logout(): Promise<void>;
|
|
184
|
+
getAccessToken(): DpopAccessToken | undefined;
|
|
185
|
+
/** Existing key only. Missing/changed persistence clears auth and requires login. */
|
|
186
|
+
getKey(): Promise<DpopKey>;
|
|
187
|
+
clear(): Promise<void>;
|
|
188
|
+
dispose(): void;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Create a browser DPoP session for one vault mount (`backendOrigin` + `basePath`).
|
|
192
|
+
* Canonical import: `import { createOidcVaultDpopSession } from
|
|
193
|
+
* '@web-ts-toolkit/oidc-vault-dpop-client'` (named root import, no default export).
|
|
194
|
+
* Lifecycle: `login` → `exchange(code)` → `getAccessToken`/`fetchWithDpop` →
|
|
195
|
+
* `refresh` → `logout`. Cookie transport additionally needs Web Locks +
|
|
196
|
+
* `BroadcastChannel` at runtime (see `assertDpopBrowserFeatures`).
|
|
197
|
+
* Copyable app helper. It is not an export of the Express package.
|
|
198
|
+
*/
|
|
199
|
+
declare const createOidcVaultDpopSession: (options: OidcVaultDpopSessionOptions) => OidcVaultDpopSession;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Browser-only DPoP client (CLIENT-02 port of apps/oidc-vault-dpop-example/src/auth/*).
|
|
203
|
+
* No `node:*` or `express` imports. Browser globals (crypto.subtle, indexedDB,
|
|
204
|
+
* sessionStorage, navigator.locks, BroadcastChannel) only behind
|
|
205
|
+
* assertDpopBrowserFeatures / lazy factory calls, never at module top-level.
|
|
206
|
+
*/
|
|
207
|
+
declare class DpopNonceCache {
|
|
208
|
+
private readonly nonces;
|
|
209
|
+
get(space: string, jkt: string): string | undefined;
|
|
210
|
+
remember(space: string, jkt: string, nonce: string | null): boolean;
|
|
211
|
+
clear(): void;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Browser-only DPoP client (CLIENT-02 port of apps/oidc-vault-dpop-example/src/auth/*).
|
|
216
|
+
* No `node:*` or `express` imports. Browser globals (crypto.subtle, indexedDB,
|
|
217
|
+
* sessionStorage, navigator.locks, BroadcastChannel) only behind
|
|
218
|
+
* assertDpopBrowserFeatures / lazy factory calls, never at module top-level.
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
interface DpopApi {
|
|
222
|
+
/** Exact origin receiving the proof/Authorization. No wildcard or redirects. */
|
|
223
|
+
origin: string;
|
|
224
|
+
/** Backend API replayNamespace (the nonce protection space), not a route. */
|
|
225
|
+
replayNamespace: string;
|
|
226
|
+
/** Usually omit for JWT APIs; include only for an explicitly cookie-using API. */
|
|
227
|
+
credentials?: 'omit' | 'include';
|
|
228
|
+
}
|
|
229
|
+
interface DpopFetchContext {
|
|
230
|
+
session: OidcVaultDpopSession;
|
|
231
|
+
apis: readonly DpopApi[];
|
|
232
|
+
fetch?: typeof globalThis.fetch;
|
|
233
|
+
nonces?: DpopNonceCache;
|
|
234
|
+
now?: () => number;
|
|
235
|
+
}
|
|
236
|
+
interface DpopFetchOptions extends Omit<RequestInit, 'redirect' | 'credentials'> {
|
|
237
|
+
/**
|
|
238
|
+
* GET/HEAD/OPTIONS may retry. All other methods default to one attempt.
|
|
239
|
+
* Explicitly authorize idempotent retries only for a server operation whose
|
|
240
|
+
* contract supports them (e.g. PUT or POST with a server idempotency key).
|
|
241
|
+
*/
|
|
242
|
+
retry?: 'never' | 'idempotent';
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Scoped DPoP `fetch` for one configured API origin: mints a fresh proof per
|
|
246
|
+
* attempt, sends `Authorization: DPoP`, then retries once on a DPoP nonce
|
|
247
|
+
* challenge and at most once via `session.refresh()` on `invalid_token`.
|
|
248
|
+
* Canonical import: `import { fetchWithDpop } from
|
|
249
|
+
* '@web-ts-toolkit/oidc-vault-dpop-client'` (named root import). Only origins
|
|
250
|
+
* listed in `context.apis` are called; authentication headers are owned by the
|
|
251
|
+
* helper and must not be set by callers.
|
|
252
|
+
*/
|
|
253
|
+
declare const fetchWithDpop: (context: DpopFetchContext, input: string | URL, options?: DpopFetchOptions) => Promise<Response>;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Browser-only DPoP client (CLIENT-02 port of apps/oidc-vault-dpop-example/src/auth/*).
|
|
257
|
+
* No `node:*` or `express` imports. Browser globals (crypto.subtle, indexedDB,
|
|
258
|
+
* sessionStorage, navigator.locks, BroadcastChannel) only behind
|
|
259
|
+
* assertDpopBrowserFeatures / lazy factory calls, never at module top-level.
|
|
260
|
+
*/
|
|
261
|
+
/** Fixed local failures; never include keys, credentials, fingerprints or URLs. */
|
|
262
|
+
declare class OidcVaultDpopClientError extends Error {
|
|
263
|
+
readonly code: string;
|
|
264
|
+
readonly requiresLogin: boolean;
|
|
265
|
+
readonly status?: number;
|
|
266
|
+
constructor(code: string, message: string, requiresLogin?: boolean, status?: number);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export { type DeviceFingerprint, type DeviceFingerprintOptions, type DeviceFingerprintSignalSource, type DpopAccessToken, type DpopApi, type DpopFetchContext, type DpopFetchOptions, type DpopKey, type DpopKeyScope, DpopNonceCache, type DpopProofInput, type FingerprintJsAgent, OidcVaultDpopClientError, type OidcVaultDpopSession, type OidcVaultDpopSessionOptions, createDeviceFingerprint, createDpopProof, createOidcVaultDpopSession, fetchWithDpop, fingerprintJsSignalSource, getOrCreateDpopKey };
|