@web-ts-toolkit/express-request-attestation 0.50.0 → 0.52.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.
@@ -1,12 +1,10 @@
1
1
  /**
2
- * ATT-01 shared browser-safe protocol DTOs, constants, and error type.
2
+ * Shared browser-safe v1 protocol types, constants, and error type.
3
3
  *
4
- * This module is imported by BOTH public entries (`.` and `./signer`), so it
5
- * must stay free of Node built-ins, Express/Redis types, `Buffer`, `NodeJS`,
6
- * and `node:*` imports. See `tsconfig.signer-browser.json`, which typechecks
7
- * the signer closure with `types: []` to enforce that boundary.
4
+ * Both public entries (`.` and `./signer`) export these types. The browser
5
+ * entry requires no Node, Express, Redis, or MongoDB dependencies or types.
8
6
  *
9
- * Protocol summary (task section 4.2):
7
+ * Protocol summary:
10
8
  *
11
9
  * ```text
12
10
  * nonceHex = lowercase hex of 16 cryptographically random bytes
@@ -38,9 +36,9 @@ declare const KEY_BYTES_LENGTH: 32;
38
36
  declare const NONCE_BYTES_LENGTH: 16;
39
37
  /** Raw HMAC-SHA-256 output length in bytes (43 unpadded base64url chars). */
40
38
  declare const MAC_BYTES_LENGTH: 32;
41
- /** Key identifiers match `[A-Za-z0-9_-]{1,64}` (task section 4.1). */
39
+ /** Key identifiers match `[A-Za-z0-9_-]{1,64}`. */
42
40
  declare const MAX_KEY_ID_LENGTH: 64;
43
- /** Replay namespaces match `[A-Za-z0-9_-]{1,128}` (task section 4.2). */
41
+ /** Replay namespaces match `[A-Za-z0-9_-]{1,128}`. */
44
42
  declare const MAX_REPLAY_NAMESPACE_LENGTH: 128;
45
43
  /** Encoded transaction-ID header value: at most 1024 UTF-8 bytes. */
46
44
  declare const MAX_ENCODED_TRANSACTION_ID_BYTES: 1024;
@@ -58,7 +56,7 @@ declare const MAX_HEADER_NAME_BYTES: 128;
58
56
  /** Default parser/body byte budget (1 MiB); hard v1 cap is 16 MiB. */
59
57
  declare const DEFAULT_MAX_BODY_BYTES: 1048576;
60
58
  declare const MAX_BODY_BYTES_HARD_CAP: 16777216;
61
- /** Time policy defaults and bounds in milliseconds (task section 4.4). */
59
+ /** Time policy defaults and bounds in milliseconds. */
62
60
  declare const DEFAULT_MAX_AGE_MS: 30000;
63
61
  declare const MIN_MAX_AGE_MS: 1000;
64
62
  declare const MAX_MAX_AGE_MS: 120000;
@@ -76,7 +74,7 @@ declare const EMPTY_BODY_SHA256_HEX: "e3b0c44298fc1c149afbf4c8996fb92427ae41e464
76
74
  * Error thrown by shared protocol helpers for malformed input, failed
77
75
  * canonical checks, bound violations, and invalid option values.
78
76
  *
79
- * Server middleware (ATT-02) maps these to the fixed section 4.5 HTTP errors
77
+ * Server middleware maps request failures to fixed HTTP errors
80
78
  * without echoing key material, nonces, signatures, or request bodies.
81
79
  */
82
80
  declare class AttestationProtocolError extends Error {
@@ -138,40 +136,58 @@ interface TransactionIdFields {
138
136
  }
139
137
  /**
140
138
  * Low-level signer input. Callers supply explicit timestamp/nonce/body digest
141
- * so identical inputs are deterministic; only the request wrapper (ATT-07)
142
- * chooses fresh timestamps/nonces automatically.
139
+ * so identical inputs are deterministic; `fetchWithAttestation` chooses fresh
140
+ * timestamps/nonces automatically. Public HMAC proofs do not authenticate users.
143
141
  */
144
142
  interface SignInput {
143
+ /** Actual request method, normalized to uppercase. */
145
144
  readonly method: string;
145
+ /** Externally visible origin-form path/query; preserve percent escapes, slashes, and query order. */
146
146
  readonly requestTarget: string;
147
+ /** Final Content-Type including multipart boundary, or empty for an absent field. */
147
148
  readonly contentType: string;
149
+ /** Lowercase SHA-256 hex of the exact submitted bytes; hash zero bytes for an empty body. */
148
150
  readonly bodyHashHex: string;
151
+ /** Nonnegative safe-integer epoch milliseconds. */
149
152
  readonly timestampMs: number;
153
+ /** Fresh 128-bit nonce as 32 lowercase hex characters; use `generateNonceHex()`. */
150
154
  readonly nonceHex: string;
151
155
  }
152
156
  /**
153
- * Low-level HMAC signer (implemented with WebCrypto in ATT-06). A returned
154
- * signer exposes immutable protection-space binding plus `sign(input)`.
157
+ * Low-level WebCrypto HMAC signer with immutable protection-space binding.
158
+ * Publicly delivered key material provides no caller/session authentication.
155
159
  */
156
160
  interface RequestSigner {
157
161
  readonly version: 1;
158
162
  readonly keyId: string;
159
163
  readonly publicOrigin: string;
160
164
  readonly replayNamespace: string;
165
+ /** Deterministic for explicit inputs; returns a canonical envelope or rejects with a local protocol/client error. */
161
166
  sign(input: SignInput): Promise<string>;
162
167
  }
163
- /** Options accepted by `createRequestSigner` (ATT-06). */
168
+ /** Explicit key and protection-space binding for `createRequestSigner`. */
164
169
  interface RequestSignerOptions {
170
+ /** Provisioned key identifier matching `[A-Za-z0-9_-]{1,64}`. */
165
171
  readonly keyId: string;
166
- /** Exactly 32 bytes; copied on input, never exposed. */
172
+ /** Exactly 32 bytes; copied on input. Non-extractable wrapping does not hide publicly delivered material. */
167
173
  readonly key: Uint8Array;
174
+ /** Static canonical HTTPS origin (loopback HTTP allowed), identical to the verifier. */
168
175
  readonly publicOrigin: string;
176
+ /** Namespace matching `[A-Za-z0-9_-]{1,128}`, identical to the verifier. */
169
177
  readonly replayNamespace: string;
170
178
  }
171
- /** Partial time-policy overrides in milliseconds. */
179
+ /**
180
+ * Partial time-policy overrides in milliseconds. All verifiers in one
181
+ * `(publicOrigin, replayNamespace)` must share these values, replay state,
182
+ * and per-key acceptance windows. A live policy migration needs a coordinated
183
+ * new protection space, retaining the old ledger through its last `retainUntilMs`.
184
+ */
172
185
  interface TimePolicyOptions {
186
+ /** Maximum proof age; default 30000 ms, safe integer in 1000–120000. */
173
187
  readonly maxAgeMs?: number;
188
+ /** Client clock allowance; default 5000 ms, safe integer in 0–30000. */
174
189
  readonly clockSkewMs?: number;
190
+ /** Pairwise verifier/store clock difference; default 5000 ms (0–30000). Extends retention, not freshness. */
175
191
  readonly clusterClockGuardMs?: number;
176
192
  }
177
193
  /** Validated, frozen time policy. All values are safe-integer milliseconds. */
@@ -199,7 +215,7 @@ interface ProofTiming {
199
215
  /** Retention admission verdicts for replay stores. */
200
216
  type RetentionAdmission = 'admissible' | 'expired' | 'overlong';
201
217
 
202
- /** Key IDs match `[A-Za-z0-9_-]{1,64}` (task section 4.1 snapshot rules). */
218
+ /** Key IDs match `[A-Za-z0-9_-]{1,64}`. */
203
219
  declare function validateKeyId(keyId: unknown): asserts keyId is string;
204
220
  /** Replay namespaces match `[A-Za-z0-9_-]{1,128}`. */
205
221
  declare function validateReplayNamespace(namespace: unknown): asserts namespace is string;
@@ -252,13 +268,15 @@ declare function canonicalizeRequestTarget(target: unknown): string;
252
268
  /**
253
269
  * Configured signature header-name validation. Names must be valid HTTP
254
270
  * tokens and must not collide with auth, cookie, host/origin, body-metadata,
255
- * or the package's `x-attestation-error` response marker.
271
+ * or the package's `x-attestation-error` response marker. This is general HTTP
272
+ * validation; `fetchWithAttestation` additionally rejects browser-forbidden names.
256
273
  */
257
274
  declare function validateHeaderName(name: unknown): asserts name is string;
258
275
  /**
259
276
  * Exactly-one incoming signature field. Returns `null` when absent (the
260
277
  * server maps that to `ATTESTATION_MISSING`); throws on duplicate raw-header
261
- * presentations or comma-joined values instead of selecting one value.
278
+ * presentations, oversized fields, or comma-joined values instead of selecting
279
+ * one value. Envelope decoding performs the remaining canonicality checks.
262
280
  */
263
281
  declare function extractSingleHeaderValue(raw: string | readonly string[] | undefined): string | null;
264
282
  /** Opaque replay keys are 1-256 printable ASCII bytes; never truncated. */
@@ -291,8 +309,8 @@ declare function encodeTransactionId(fields: TransactionIdFields): string;
291
309
  * Strict envelope decode. The 1024-byte bound is enforced BEFORE decoding;
292
310
  * then canonical base64url, UTF-8, canonical JSON array shape, version 1,
293
311
  * and every field rule are enforced. Unknown versions, extra array fields,
294
- * alternate numeric spellings, padding, and duplicate protocol material all
295
- * throw.
312
+ * alternate numeric spellings, padding, UTF-8 BOMs, and duplicate protocol
313
+ * material all throw.
296
314
  */
297
315
  declare function decodeTransactionId(headerValue: unknown): TransactionIdFields;
298
316
 
@@ -338,17 +356,20 @@ declare function assertTimestampMs(name: string, value: unknown): asserts value
338
356
  /**
339
357
  * Validate partial overrides and return the frozen effective policy.
340
358
  * Unknown option keys are rejected so typos cannot silently widen windows.
359
+ * Defaults are 30000 ms age, 5000 ms skew, and 5000 ms cluster clock guard.
360
+ * A protection space must use identical policy values and key acceptance
361
+ * windows everywhere; the returned policy does not coordinate deployments.
341
362
  */
342
363
  declare function resolveTimePolicy(options?: TimePolicyOptions): ResolvedTimePolicy;
343
364
  /**
344
365
  * Evaluate one proof's timing against a KNOWN key entry's acceptance window.
345
366
  *
346
- * Key-status ordering follows section 4.5: unknown/inactive well-formed key
367
+ * Key-status ordering: unknown/inactive well-formed key
347
368
  * IDs are `stale-key` (the caller determines key lookup; pass the entry's
348
369
  * window here), a known active key with an elapsed timestamp is `expired`,
349
370
  * and excessive future skew is `future`. The verifier repeats this check
350
371
  * after async provider work, immediately before reservation, and before
351
- * calling the handler after reservation (ATT-02).
372
+ * calling the handler after reservation.
352
373
  *
353
374
  * Deadline arithmetic must remain safe integers; a timestamp so large that
354
375
  * `timestampMs + maxAgeMs + clockSkewMs` leaves safe-integer range cannot
@@ -367,7 +388,7 @@ declare function evaluateProofTiming(input: {
367
388
  */
368
389
  declare function maxVerifierRemainingMs(policy: ResolvedTimePolicy): number;
369
390
  /**
370
- * Store admission bound: `maxAgeMs + 2 * clockSkewMs + 2 * clockGuardMs`
391
+ * Store admission bound: `maxAgeMs + 2 * clockSkewMs + 2 * clusterClockGuardMs`
371
392
  * (default 50000 ms). A store clock may additionally trail the verifier by
372
393
  * the declared guard. Capped globally at 240000 ms.
373
394
  */
@@ -1,12 +1,10 @@
1
1
  /**
2
- * ATT-01 shared browser-safe protocol DTOs, constants, and error type.
2
+ * Shared browser-safe v1 protocol types, constants, and error type.
3
3
  *
4
- * This module is imported by BOTH public entries (`.` and `./signer`), so it
5
- * must stay free of Node built-ins, Express/Redis types, `Buffer`, `NodeJS`,
6
- * and `node:*` imports. See `tsconfig.signer-browser.json`, which typechecks
7
- * the signer closure with `types: []` to enforce that boundary.
4
+ * Both public entries (`.` and `./signer`) export these types. The browser
5
+ * entry requires no Node, Express, Redis, or MongoDB dependencies or types.
8
6
  *
9
- * Protocol summary (task section 4.2):
7
+ * Protocol summary:
10
8
  *
11
9
  * ```text
12
10
  * nonceHex = lowercase hex of 16 cryptographically random bytes
@@ -38,9 +36,9 @@ declare const KEY_BYTES_LENGTH: 32;
38
36
  declare const NONCE_BYTES_LENGTH: 16;
39
37
  /** Raw HMAC-SHA-256 output length in bytes (43 unpadded base64url chars). */
40
38
  declare const MAC_BYTES_LENGTH: 32;
41
- /** Key identifiers match `[A-Za-z0-9_-]{1,64}` (task section 4.1). */
39
+ /** Key identifiers match `[A-Za-z0-9_-]{1,64}`. */
42
40
  declare const MAX_KEY_ID_LENGTH: 64;
43
- /** Replay namespaces match `[A-Za-z0-9_-]{1,128}` (task section 4.2). */
41
+ /** Replay namespaces match `[A-Za-z0-9_-]{1,128}`. */
44
42
  declare const MAX_REPLAY_NAMESPACE_LENGTH: 128;
45
43
  /** Encoded transaction-ID header value: at most 1024 UTF-8 bytes. */
46
44
  declare const MAX_ENCODED_TRANSACTION_ID_BYTES: 1024;
@@ -58,7 +56,7 @@ declare const MAX_HEADER_NAME_BYTES: 128;
58
56
  /** Default parser/body byte budget (1 MiB); hard v1 cap is 16 MiB. */
59
57
  declare const DEFAULT_MAX_BODY_BYTES: 1048576;
60
58
  declare const MAX_BODY_BYTES_HARD_CAP: 16777216;
61
- /** Time policy defaults and bounds in milliseconds (task section 4.4). */
59
+ /** Time policy defaults and bounds in milliseconds. */
62
60
  declare const DEFAULT_MAX_AGE_MS: 30000;
63
61
  declare const MIN_MAX_AGE_MS: 1000;
64
62
  declare const MAX_MAX_AGE_MS: 120000;
@@ -76,7 +74,7 @@ declare const EMPTY_BODY_SHA256_HEX: "e3b0c44298fc1c149afbf4c8996fb92427ae41e464
76
74
  * Error thrown by shared protocol helpers for malformed input, failed
77
75
  * canonical checks, bound violations, and invalid option values.
78
76
  *
79
- * Server middleware (ATT-02) maps these to the fixed section 4.5 HTTP errors
77
+ * Server middleware maps request failures to fixed HTTP errors
80
78
  * without echoing key material, nonces, signatures, or request bodies.
81
79
  */
82
80
  declare class AttestationProtocolError extends Error {
@@ -138,40 +136,58 @@ interface TransactionIdFields {
138
136
  }
139
137
  /**
140
138
  * Low-level signer input. Callers supply explicit timestamp/nonce/body digest
141
- * so identical inputs are deterministic; only the request wrapper (ATT-07)
142
- * chooses fresh timestamps/nonces automatically.
139
+ * so identical inputs are deterministic; `fetchWithAttestation` chooses fresh
140
+ * timestamps/nonces automatically. Public HMAC proofs do not authenticate users.
143
141
  */
144
142
  interface SignInput {
143
+ /** Actual request method, normalized to uppercase. */
145
144
  readonly method: string;
145
+ /** Externally visible origin-form path/query; preserve percent escapes, slashes, and query order. */
146
146
  readonly requestTarget: string;
147
+ /** Final Content-Type including multipart boundary, or empty for an absent field. */
147
148
  readonly contentType: string;
149
+ /** Lowercase SHA-256 hex of the exact submitted bytes; hash zero bytes for an empty body. */
148
150
  readonly bodyHashHex: string;
151
+ /** Nonnegative safe-integer epoch milliseconds. */
149
152
  readonly timestampMs: number;
153
+ /** Fresh 128-bit nonce as 32 lowercase hex characters; use `generateNonceHex()`. */
150
154
  readonly nonceHex: string;
151
155
  }
152
156
  /**
153
- * Low-level HMAC signer (implemented with WebCrypto in ATT-06). A returned
154
- * signer exposes immutable protection-space binding plus `sign(input)`.
157
+ * Low-level WebCrypto HMAC signer with immutable protection-space binding.
158
+ * Publicly delivered key material provides no caller/session authentication.
155
159
  */
156
160
  interface RequestSigner {
157
161
  readonly version: 1;
158
162
  readonly keyId: string;
159
163
  readonly publicOrigin: string;
160
164
  readonly replayNamespace: string;
165
+ /** Deterministic for explicit inputs; returns a canonical envelope or rejects with a local protocol/client error. */
161
166
  sign(input: SignInput): Promise<string>;
162
167
  }
163
- /** Options accepted by `createRequestSigner` (ATT-06). */
168
+ /** Explicit key and protection-space binding for `createRequestSigner`. */
164
169
  interface RequestSignerOptions {
170
+ /** Provisioned key identifier matching `[A-Za-z0-9_-]{1,64}`. */
165
171
  readonly keyId: string;
166
- /** Exactly 32 bytes; copied on input, never exposed. */
172
+ /** Exactly 32 bytes; copied on input. Non-extractable wrapping does not hide publicly delivered material. */
167
173
  readonly key: Uint8Array;
174
+ /** Static canonical HTTPS origin (loopback HTTP allowed), identical to the verifier. */
168
175
  readonly publicOrigin: string;
176
+ /** Namespace matching `[A-Za-z0-9_-]{1,128}`, identical to the verifier. */
169
177
  readonly replayNamespace: string;
170
178
  }
171
- /** Partial time-policy overrides in milliseconds. */
179
+ /**
180
+ * Partial time-policy overrides in milliseconds. All verifiers in one
181
+ * `(publicOrigin, replayNamespace)` must share these values, replay state,
182
+ * and per-key acceptance windows. A live policy migration needs a coordinated
183
+ * new protection space, retaining the old ledger through its last `retainUntilMs`.
184
+ */
172
185
  interface TimePolicyOptions {
186
+ /** Maximum proof age; default 30000 ms, safe integer in 1000–120000. */
173
187
  readonly maxAgeMs?: number;
188
+ /** Client clock allowance; default 5000 ms, safe integer in 0–30000. */
174
189
  readonly clockSkewMs?: number;
190
+ /** Pairwise verifier/store clock difference; default 5000 ms (0–30000). Extends retention, not freshness. */
175
191
  readonly clusterClockGuardMs?: number;
176
192
  }
177
193
  /** Validated, frozen time policy. All values are safe-integer milliseconds. */
@@ -199,7 +215,7 @@ interface ProofTiming {
199
215
  /** Retention admission verdicts for replay stores. */
200
216
  type RetentionAdmission = 'admissible' | 'expired' | 'overlong';
201
217
 
202
- /** Key IDs match `[A-Za-z0-9_-]{1,64}` (task section 4.1 snapshot rules). */
218
+ /** Key IDs match `[A-Za-z0-9_-]{1,64}`. */
203
219
  declare function validateKeyId(keyId: unknown): asserts keyId is string;
204
220
  /** Replay namespaces match `[A-Za-z0-9_-]{1,128}`. */
205
221
  declare function validateReplayNamespace(namespace: unknown): asserts namespace is string;
@@ -252,13 +268,15 @@ declare function canonicalizeRequestTarget(target: unknown): string;
252
268
  /**
253
269
  * Configured signature header-name validation. Names must be valid HTTP
254
270
  * tokens and must not collide with auth, cookie, host/origin, body-metadata,
255
- * or the package's `x-attestation-error` response marker.
271
+ * or the package's `x-attestation-error` response marker. This is general HTTP
272
+ * validation; `fetchWithAttestation` additionally rejects browser-forbidden names.
256
273
  */
257
274
  declare function validateHeaderName(name: unknown): asserts name is string;
258
275
  /**
259
276
  * Exactly-one incoming signature field. Returns `null` when absent (the
260
277
  * server maps that to `ATTESTATION_MISSING`); throws on duplicate raw-header
261
- * presentations or comma-joined values instead of selecting one value.
278
+ * presentations, oversized fields, or comma-joined values instead of selecting
279
+ * one value. Envelope decoding performs the remaining canonicality checks.
262
280
  */
263
281
  declare function extractSingleHeaderValue(raw: string | readonly string[] | undefined): string | null;
264
282
  /** Opaque replay keys are 1-256 printable ASCII bytes; never truncated. */
@@ -291,8 +309,8 @@ declare function encodeTransactionId(fields: TransactionIdFields): string;
291
309
  * Strict envelope decode. The 1024-byte bound is enforced BEFORE decoding;
292
310
  * then canonical base64url, UTF-8, canonical JSON array shape, version 1,
293
311
  * and every field rule are enforced. Unknown versions, extra array fields,
294
- * alternate numeric spellings, padding, and duplicate protocol material all
295
- * throw.
312
+ * alternate numeric spellings, padding, UTF-8 BOMs, and duplicate protocol
313
+ * material all throw.
296
314
  */
297
315
  declare function decodeTransactionId(headerValue: unknown): TransactionIdFields;
298
316
 
@@ -338,17 +356,20 @@ declare function assertTimestampMs(name: string, value: unknown): asserts value
338
356
  /**
339
357
  * Validate partial overrides and return the frozen effective policy.
340
358
  * Unknown option keys are rejected so typos cannot silently widen windows.
359
+ * Defaults are 30000 ms age, 5000 ms skew, and 5000 ms cluster clock guard.
360
+ * A protection space must use identical policy values and key acceptance
361
+ * windows everywhere; the returned policy does not coordinate deployments.
341
362
  */
342
363
  declare function resolveTimePolicy(options?: TimePolicyOptions): ResolvedTimePolicy;
343
364
  /**
344
365
  * Evaluate one proof's timing against a KNOWN key entry's acceptance window.
345
366
  *
346
- * Key-status ordering follows section 4.5: unknown/inactive well-formed key
367
+ * Key-status ordering: unknown/inactive well-formed key
347
368
  * IDs are `stale-key` (the caller determines key lookup; pass the entry's
348
369
  * window here), a known active key with an elapsed timestamp is `expired`,
349
370
  * and excessive future skew is `future`. The verifier repeats this check
350
371
  * after async provider work, immediately before reservation, and before
351
- * calling the handler after reservation (ATT-02).
372
+ * calling the handler after reservation.
352
373
  *
353
374
  * Deadline arithmetic must remain safe integers; a timestamp so large that
354
375
  * `timestampMs + maxAgeMs + clockSkewMs` leaves safe-integer range cannot
@@ -367,7 +388,7 @@ declare function evaluateProofTiming(input: {
367
388
  */
368
389
  declare function maxVerifierRemainingMs(policy: ResolvedTimePolicy): number;
369
390
  /**
370
- * Store admission bound: `maxAgeMs + 2 * clockSkewMs + 2 * clockGuardMs`
391
+ * Store admission bound: `maxAgeMs + 2 * clockSkewMs + 2 * clusterClockGuardMs`
371
392
  * (default 50000 ms). A store clock may additionally trail the verifier by
372
393
  * the declared guard. Capped globally at 240000 ms.
373
394
  */