unjwt 0.8.5 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +34 -21
  2. package/dist/_chunks/algorithms.mjs +44 -20
  3. package/dist/_chunks/digest.d.mts +4 -4
  4. package/dist/_chunks/digest.mjs +23 -15
  5. package/dist/_chunks/error.d.mts +29 -42
  6. package/dist/_chunks/export.mjs +1 -4
  7. package/dist/_chunks/index.d.mts +270 -234
  8. package/dist/_chunks/index2.d.mts +82 -189
  9. package/dist/_chunks/index4.d.mts +223 -60
  10. package/dist/_chunks/index5.d.mts +68 -31
  11. package/dist/_chunks/index6.d.mts +45 -64
  12. package/dist/_chunks/jwe.mjs +53 -48
  13. package/dist/_chunks/jwk.mjs +9 -3
  14. package/dist/_chunks/jws.mjs +55 -50
  15. package/dist/_chunks/jwt.d.mts +13 -49
  16. package/dist/_chunks/jwt.mjs +4 -158
  17. package/dist/_chunks/jwt2.mjs +162 -0
  18. package/dist/_chunks/libs/unsecure.d.mts +0 -6
  19. package/dist/_chunks/limits.d.mts +3 -2
  20. package/dist/_chunks/payload.mjs +13 -7
  21. package/dist/_chunks/wrap.mjs +3 -1
  22. package/dist/core/internal.d.mts +50 -2
  23. package/dist/core/internal.mjs +3 -1
  24. package/dist/core/jwe/index.d.mts +1 -1
  25. package/dist/core/jwk/index.d.mts +2 -2
  26. package/dist/core/jwk/index.mjs +2 -2
  27. package/dist/dpop/index.d.mts +77 -18
  28. package/dist/index.d.mts +6 -6
  29. package/dist/index.mjs +4 -4
  30. package/dist/session/index.d.mts +568 -22
  31. package/dist/session/index.mjs +1386 -34
  32. package/dist/utils/index.d.mts +4 -4
  33. package/dist/utils/index.mjs +2 -10
  34. package/package.json +4 -32
  35. package/dist/_chunks/codec-jwe.d.mts +0 -280
  36. package/dist/_chunks/codec-jws.mjs +0 -1005
  37. package/dist/_chunks/cookie-attributes.d.mts +0 -12
  38. package/dist/_chunks/cookie.mjs +0 -50
  39. package/dist/adapters/elysia/index.d.mts +0 -123
  40. package/dist/adapters/elysia/index.mjs +0 -120
  41. package/dist/adapters/h3/index.d.mts +0 -135
  42. package/dist/adapters/h3/index.mjs +0 -117
  43. package/dist/adapters/h3v1/index.d.mts +0 -148
  44. package/dist/adapters/h3v1/index.mjs +0 -128
@@ -1,8 +1,9 @@
1
1
  import { textEncoder } from "./libs/unsecure.mjs";
2
2
  import { JWTError } from "../error.mjs";
3
- import { applyTypDefault, base64UrlStringify, computeJwtTimeClaims, getPlaintextBytes, isJWK, isJWKSet, parsePayloadBytes, parsePayloadSegment, parsePayloadText, validateCriticalHeadersJWS } from "./jwt.mjs";
4
- import { JWS_ALG_CTX, asJWTError, asKeyLookupError, assertCompactToken, assertConsumerOptions, assertEntryLimit, assertJWKSetCandidates, assertOctKeyLength, assertOptionEntry, assertProducerConfirmation, assertProducerCrit, assertProducerKeyHeaders, assertProtectedOnlyOption, assertSerializationObject, buildJWKSetFilter, checkAlgAllowed, checkAlgSupported, checkProtectedTyp, checkSerializationEntry, copyJsonObject, decodeProtectedHeader, decodeSegment, getJWKsFromSet, importKey, keyMatchesHeader, resolveKeyInput, resolveLimit, serializeProtectedHeader, sign as sign$1, stringKeyError, verify as verify$1 } from "./algorithms.mjs";
5
- import { inferJWSAllowedAlgorithms, isJWSAlgorithm, jwsHashOf, modulusLengthOf } from "./digest.mjs";
3
+ import { base64UrlStringify, isJWK, isJWKSet } from "./jwt.mjs";
4
+ import { applyTypDefault, computeJwtTimeClaims, getPlaintextBytes, parsePayloadBytes, parsePayloadSegment, parsePayloadText, validateCriticalHeadersJWS } from "./jwt2.mjs";
5
+ import { JWS_ALG_CTX, asJWTError, asKeyLookupError, assertCompactToken, assertConsumerOptions, assertEntryLimit, assertJWKSetCandidates, assertOctKeyLength, assertOptionEntry, assertProducerConfirmation, assertProducerCrit, assertProducerKeyHeaders, assertProtectedOnlyOption, assertSerializationObject, buildJWKSetFilter, checkAlgAllowed, checkAlgSupported, checkProtectedTyp, checkSerializationEntry, copyJsonObject, createKeyAttemptBudget, decodeProtectedHeader, decodeSegment, getJWKsFromSet, importKey, keyMatchesHeader, resolveKeyInput, resolveLimit, serializeProtectedHeader, sign as sign$1, stringKeyError, verify as verify$1 } from "./algorithms.mjs";
6
+ import { inferJWSAllowedAlgorithms, isJWSAlgorithm, jwsAlgsFromJWK, jwsHashOf, modulusLengthOf } from "./digest.mjs";
6
7
  import { resolvePayloadMode, validateClaimsIfJson } from "./payload.mjs";
7
8
  async function resolveSigningKey(alg, key, usage) {
8
9
  if (key instanceof Uint8Array) {
@@ -19,6 +20,9 @@ async function resolveSigningKey(alg, key, usage) {
19
20
  }
20
21
  return key;
21
22
  }
23
+ function signingAlgOf(jwk) {
24
+ return jwk.alg ?? jwsAlgsFromJWK(jwk)?.[0];
25
+ }
22
26
  function assertNotStringKey(key) {
23
27
  if (typeof key === "string") throw stringKeyError();
24
28
  }
@@ -107,32 +111,33 @@ async function verifyWithKey(alg, rawKeyMaterial, options) {
107
111
  }
108
112
  }
109
113
  async function verifyWithKeyMaterial(alg, rawKeyMaterial, options) {
110
- const { signature, signingInput, kid, allowSymmetric, keyCandidates } = options;
114
+ const { signature, signingInput, kid, keyCandidates, keyAttempts } = options;
111
115
  if (isJWKSet(rawKeyMaterial)) {
112
116
  const candidates = getJWKsFromSet(rawKeyMaterial, buildJWKSetFilter({
113
117
  kid,
114
118
  alg
115
- }, {
116
- op: "sig",
117
- allowSymmetric
118
- }));
119
+ }, { op: "sig" }));
119
120
  assertJWKSetCandidates(candidates, {
120
121
  limit: keyCandidates,
121
122
  kid
122
123
  });
123
124
  const attempts = [];
124
- for (const candidate of candidates) try {
125
- const verificationKey = await resolveSigningKey(alg, await importKey(candidate, {
126
- alg,
127
- expect: "public"
128
- }), "verify");
129
- if (await verify$1(alg, verificationKey, signature, signingInput)) return true;
130
- attempts.push(new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID"));
131
- } catch (error) {
132
- attempts.push(asKeyUsageError(error, alg));
125
+ for (const candidate of candidates) {
126
+ keyAttempts?.spend();
127
+ try {
128
+ const verificationKey = await resolveSigningKey(alg, await importKey(candidate, {
129
+ alg,
130
+ expect: "public"
131
+ }), "verify");
132
+ if (await verify$1(alg, verificationKey, signature, signingInput)) return true;
133
+ attempts.push(new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID"));
134
+ } catch (error) {
135
+ attempts.push(asKeyUsageError(error, alg));
136
+ }
133
137
  }
134
138
  throw new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID", attempts);
135
139
  }
140
+ keyAttempts?.spend();
136
141
  const verificationKey = await resolveSigningKey(alg, await importKey(rawKeyMaterial, {
137
142
  alg,
138
143
  expect: "public"
@@ -212,8 +217,8 @@ function _resolveSignerAlg(signer, index) {
212
217
  }
213
218
  function _inferSignerAlg(key, index) {
214
219
  if (isJWK(key)) {
215
- const alg = key.alg;
216
- if (!alg) throw new JWTError(`Cannot infer "alg" for signer[${index}]: the JWK has no "alg" property.`, "ERR_JWS_SIGNER_ALG_INFERENCE");
220
+ const alg = signingAlgOf(key);
221
+ if (!alg) throw new JWTError(`Cannot infer "alg" for signer[${index}]: the JWK has no "alg" property and its key allows more than one.`, "ERR_JWS_SIGNER_ALG_INFERENCE");
217
222
  return alg;
218
223
  }
219
224
  const inferred = inferJWSAllowedAlgorithms(key);
@@ -269,7 +274,7 @@ function _normalizeSerialization(input) {
269
274
  throw new JWTError("Invalid JWS JSON Serialization: missing \"signatures\" or \"signature\".", "ERR_JWS_INVALID_SERIALIZATION");
270
275
  }
271
276
  async function _verifyOneSignature(payload, entry, index, keyResolver, context) {
272
- const { options, payloadMode, keyCandidates } = context;
277
+ const { options, payloadMode, keyCandidates, keyAttempts } = context;
273
278
  const fail = (error, protectedHeader, signerHeader) => {
274
279
  const outcome = {
275
280
  signerIndex: index,
@@ -301,8 +306,7 @@ async function _verifyOneSignature(payload, entry, index, keyResolver, context)
301
306
  if (typeof rawKeyMaterial === "string") return fail(stringKeyError(), protectedHeader, unprotectedHeader);
302
307
  const algError = checkAlgAllowed(alg, rawKeyMaterial, {
303
308
  ctx: JWS_ALG_CTX,
304
- algorithms: options.algorithms,
305
- allowSymmetric: options.allowSymmetricJWKSet
309
+ algorithms: options.algorithms
306
310
  });
307
311
  if (algError) return fail(algError, protectedHeader, unprotectedHeader);
308
312
  let signatureBytes;
@@ -321,10 +325,11 @@ async function _verifyOneSignature(payload, entry, index, keyResolver, context)
321
325
  signature: signatureBytes,
322
326
  signingInput: signingInputBytes,
323
327
  kid: effective.kid,
324
- allowSymmetric: options.allowSymmetricJWKSet,
325
- keyCandidates
328
+ keyCandidates,
329
+ keyAttempts
326
330
  });
327
331
  } catch (err) {
332
+ if (keyAttempts.exhausted) throw keyAttempts.exhausted;
328
333
  return fail(asJWTError(err, INVALID_SIGNATURE), protectedHeader, unprotectedHeader);
329
334
  }
330
335
  if (!verified) return fail(new JWTError(`JWS signature[${index}] verification failed.`, "ERR_JWS_SIGNATURE_INVALID"), protectedHeader, unprotectedHeader);
@@ -423,13 +428,14 @@ async function verifyMulti(jws, keyOrLookup, options = {}) {
423
428
  const payloadMode = resolvePayloadMode(options);
424
429
  const signatureLimit = resolveLimit(options.limits, "signatures");
425
430
  const keyCandidates = resolveLimit(options.limits, "keyCandidates");
431
+ const keyAttempts = createKeyAttemptBudget(resolveLimit(options.limits, "keyAttempts"));
426
432
  const general = _readGeneralSerialization(jws, signatureLimit);
427
- let lastError;
433
+ const failures = [];
428
434
  let cryptoAttempted = false;
429
435
  for (let i = 0; i < general.signatures.length; i++) {
430
436
  const read = _readSignatureHeaders(general.signatures[i], i);
431
437
  if (read.error) {
432
- lastError = read.error;
438
+ failures.push(read.error);
433
439
  continue;
434
440
  }
435
441
  const { wireSig, protectedHeader, unprotectedHeader, effective, alg } = read;
@@ -438,7 +444,7 @@ async function verifyMulti(jws, keyOrLookup, options = {}) {
438
444
  label: `signature[${i}]`
439
445
  });
440
446
  if (typError) {
441
- lastError = typError;
447
+ failures.push(typError);
442
448
  continue;
443
449
  }
444
450
  let rawKeyMaterial;
@@ -449,26 +455,25 @@ async function verifyMulti(jws, keyOrLookup, options = {}) {
449
455
  what: `signature[${i}]`
450
456
  });
451
457
  } catch (err) {
452
- lastError = asKeyLookupError(err, `signature[${i}]`);
458
+ failures.push(asKeyLookupError(err, `signature[${i}]`));
453
459
  continue;
454
460
  }
455
461
  if (typeof rawKeyMaterial === "string") {
456
- lastError = stringKeyError();
462
+ failures.push(stringKeyError());
457
463
  continue;
458
464
  }
459
465
  const algError = checkAlgAllowed(alg, rawKeyMaterial, {
460
466
  ctx: JWS_ALG_CTX,
461
- algorithms: options.algorithms,
462
- allowSymmetric: options.allowSymmetricJWKSet
467
+ algorithms: options.algorithms
463
468
  });
464
469
  if (algError) {
465
- lastError = algError;
470
+ failures.push(algError);
471
+ continue;
472
+ }
473
+ if (options.strictSignerMatch && !keyMatchesHeader(effective, rawKeyMaterial, { ctx: JWS_ALG_CTX })) {
474
+ failures.push(new JWTError(`JWS signature[${i}] does not match the key under strictSignerMatch.`, "ERR_JWS_NO_MATCHING_SIGNER"));
466
475
  continue;
467
476
  }
468
- if (options.strictSignerMatch && !keyMatchesHeader(effective, rawKeyMaterial, {
469
- ctx: JWS_ALG_CTX,
470
- allowSymmetric: options.allowSymmetricJWKSet
471
- })) continue;
472
477
  let signatureBytes;
473
478
  try {
474
479
  signatureBytes = decodeSegment(wireSig.signature, {
@@ -476,7 +481,7 @@ async function verifyMulti(jws, keyOrLookup, options = {}) {
476
481
  what: "Signature"
477
482
  });
478
483
  } catch (err) {
479
- lastError = asJWTError(err, INVALID_JWS);
484
+ failures.push(asJWTError(err, INVALID_JWS));
480
485
  continue;
481
486
  }
482
487
  const signingInputBytes = textEncoder.encode(`${wireSig.protected ?? ""}.${general.payload}`);
@@ -486,10 +491,10 @@ async function verifyMulti(jws, keyOrLookup, options = {}) {
486
491
  signature: signatureBytes,
487
492
  signingInput: signingInputBytes,
488
493
  kid: effective.kid,
489
- allowSymmetric: options.allowSymmetricJWKSet,
490
- keyCandidates
494
+ keyCandidates,
495
+ keyAttempts
491
496
  })) {
492
- lastError = new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
497
+ failures.push(new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID"));
493
498
  continue;
494
499
  }
495
500
  validateCriticalHeadersJWS(protectedHeader, options.recognizedHeaders);
@@ -504,25 +509,29 @@ async function verifyMulti(jws, keyOrLookup, options = {}) {
504
509
  if (unprotectedHeader) result.signerHeader = unprotectedHeader;
505
510
  return result;
506
511
  } catch (err) {
512
+ if (keyAttempts.exhausted) throw keyAttempts.exhausted;
507
513
  const error = asJWTError(err, INVALID_SIGNATURE);
508
514
  if (!PER_SIGNATURE_ERROR_CODES.has(error.code)) throw error;
509
- lastError = error;
515
+ failures.push(error);
510
516
  }
511
517
  }
512
518
  if (options.strictSignerMatch && !cryptoAttempted) throw new JWTError("No signature matched the provided key under strictSignerMatch.", "ERR_JWS_NO_MATCHING_SIGNER");
513
- throw lastError ?? new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
519
+ if (failures.length === 1) throw failures[0];
520
+ throw new JWTError(INVALID_SIGNATURE.message, INVALID_SIGNATURE.code, failures);
514
521
  }
515
522
  async function verifyMultiAll(jws, keyResolver, options = {}) {
516
523
  assertConsumerOptions(options);
517
524
  const payloadMode = resolvePayloadMode(options);
518
525
  const signatureLimit = resolveLimit(options.limits, "signatures");
519
526
  const keyCandidates = resolveLimit(options.limits, "keyCandidates");
527
+ const keyAttempts = createKeyAttemptBudget(resolveLimit(options.limits, "keyAttempts"));
520
528
  const general = _readGeneralSerialization(jws, signatureLimit);
521
529
  const outcomes = [];
522
530
  for (let i = 0; i < general.signatures.length; i++) outcomes.push(await _verifyOneSignature(general.payload, general.signatures[i], i, keyResolver, {
523
531
  options,
524
532
  payloadMode,
525
- keyCandidates
533
+ keyCandidates,
534
+ keyAttempts
526
535
  }));
527
536
  return outcomes;
528
537
  }
@@ -552,10 +561,8 @@ async function sign(payload, key, options = {}) {
552
561
  });
553
562
  assertProducerConfirmation(payload);
554
563
  let { alg } = options;
555
- if (!alg) {
556
- if (isJWK(key) && key.alg) alg = key.alg;
557
- else throw new JWTError("JWS \"alg\" must be provided in options", "ERR_JWS_ALG_MISSING");
558
- }
564
+ alg ??= isJWK(key) ? signingAlgOf(key) : void 0;
565
+ if (!alg) throw new JWTError("JWS \"alg\" must be provided in options or inferable from the key", "ERR_JWS_ALG_MISSING");
559
566
  assertJWSAlgSupported(alg);
560
567
  let signingKey;
561
568
  try {
@@ -614,15 +621,13 @@ async function verify(jws, key, options = {}) {
614
621
  assertNotStringKey(keyInput);
615
622
  const algError = checkAlgAllowed(alg, keyInput, {
616
623
  ctx: JWS_ALG_CTX,
617
- algorithms: options.algorithms,
618
- allowSymmetric: options.allowSymmetricJWKSet
624
+ algorithms: options.algorithms
619
625
  });
620
626
  if (algError) throw algError;
621
627
  if (!await verifyWithKey(alg, keyInput, {
622
628
  signature: signatureBytes,
623
629
  signingInput: textEncoder.encode(`${protectedHeaderEncoded}.${payloadEncoded}`),
624
630
  kid: protectedHeader.kid,
625
- allowSymmetric: options.allowSymmetricJWKSet,
626
631
  keyCandidates: keyCandidateLimit
627
632
  })) throw new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
628
633
  validateCriticalHeadersJWS(protectedHeader, options.recognizedHeaders);
@@ -1,58 +1,22 @@
1
- import { JWTErrorCode } from "./error.mjs";
2
- import { Duration, JOSEPayload, JOSEPayloadMode, JWTClaimValidationOptions, JWTClaims, JWTTimeClaimOptions, PayloadOf } from "./index.mjs";
3
- /** Set `typ: "JWT"` on a header without `typ` when the payload is an object (not a `Uint8Array`). */
4
- export declare function applyTypDefault<T extends {
5
- typ?: string;
6
- }>(header: T, payload: unknown): T;
7
- /** Options for {@link parsePayloadBytes} and {@link parsePayloadSegment}. */
8
- export interface ParsePayloadOptions<Mode extends JOSEPayloadMode = JOSEPayloadMode> {
9
- /** How to interpret the payload bytes; the return type follows it. @default "json" */
10
- payload?: Mode;
11
- /** Family the rejection is reported under (`ERR_JWS_INVALID` / `ERR_JWE_INVALID`). @default "JWS" */
12
- family?: "JWS" | "JWE";
13
- }
14
- /** Parse payload bytes under the declared mode: `"json"` needs a UTF-8 JSON object, `"string"` UTF-8; throws `ERR_JWS_INVALID` / `ERR_JWE_INVALID`. */
15
- export declare function parsePayloadBytes<T = JWTClaims>(bytes: Uint8Array<ArrayBuffer>, options?: ParsePayloadOptions<"json">): T;
16
- export declare function parsePayloadBytes<Mode extends JOSEPayloadMode = JOSEPayloadMode>(bytes: Uint8Array<ArrayBuffer>, options?: ParsePayloadOptions<Mode>): PayloadOf<Mode>;
17
- /** Decode payload bytes as UTF-8; throws `ERR_JWS_INVALID` / `ERR_JWE_INVALID` otherwise. */
18
- export declare function parsePayloadText(bytes: Uint8Array<ArrayBuffer>, options?: Pick<ParsePayloadOptions, "family">): string;
19
- /** {@link parsePayloadBytes} over a Base64URL-encoded payload segment. */
20
- export declare function parsePayloadSegment<T = JWTClaims>(payloadEncoded: string, options?: ParsePayloadOptions<"json">): T;
21
- export declare function parsePayloadSegment<Mode extends JOSEPayloadMode = JOSEPayloadMode>(payloadEncoded: string, options?: ParsePayloadOptions<Mode>): PayloadOf<Mode>;
22
- /** Encode a payload to bytes; a non-serializable one throws `ERR_INVALID_OPTION`. */
23
- export declare function getPlaintextBytes(payload: JOSEPayload): Uint8Array<ArrayBuffer>;
24
- /** Options for {@link computeJwtTimeClaims}. */
25
- export type ComputeJwtTimeClaimsOptions = JWTTimeClaimOptions;
26
- /**
27
- * Return a copy of an object payload with `iat` and the requested `exp` / `nbf`, or `undefined` when none applies.
28
- * `currentDate` is the clock: `iat` when written, and the base of `expiresIn` / `notBeforeIn`;
29
- * `issuedAt` writes `iat` alone, a `Date` as that instant. Both members of `expiresIn`/`expiresAt` or `notBeforeIn`/`notBeforeAt` throw `ERR_JWT_CLAIM_INVALID`.
30
- */
31
- export declare function computeJwtTimeClaims(payload: unknown, options?: ComputeJwtTimeClaimsOptions): JWTClaims | undefined;
32
- /** Validate critical headers in JWS semantics. */
33
- export declare function validateCriticalHeadersJWS(protectedHeader: {
34
- crit?: string[];
35
- } & Record<string, unknown>, recognizedHeaders?: string[]): void;
36
- /** Validate critical headers in JWE semantics. */
37
- export declare function validateCriticalHeadersJWE(protectedHeader: {
38
- crit?: string[];
39
- } & Record<string, unknown>, recognizedHeaders?: string[]): void;
40
- /** Assert `crit` is a non-empty string array with no registered header name (RFC 7515 §4.1.11 / RFC 7516 §4.1.13). */
41
- export declare function assertCritShape(crit: unknown, options: {
42
- family: "JWS" | "JWE";
43
- code?: JWTErrorCode;
44
- }): asserts crit is string[];
45
- /** Whether headers declare JSON content: a string `typ` naming JWT, or a JSON `cty`. */
1
+ import { Duration, JWTClaimValidationOptions, JWTClaims } from "./index.mjs";
2
+ /** Whether the header declares JSON content: a `typ` containing `jwt` in any case, or a JSON `cty`. */
46
3
  export declare function isJWTContent(header: {
47
4
  typ?: string;
48
5
  cty?: string;
49
6
  } | undefined): boolean;
50
- /** Parse a `Duration` (e.g. `3600` or `"10m"`) into positive integer seconds; else `ERR_INVALID_OPTION`. */
7
+ /**
8
+ * Parses a `Duration` (`3600`, `"10m"`, `"7D"`) into positive integer seconds.
9
+ * @throws `ERR_INVALID_OPTION` if `duration` is malformed or not positive.
10
+ */
51
11
  export declare function computeDurationInSeconds(duration: Duration): number;
52
- /** The `ERR_JWT_EXPIRED` message for a token whose `exp` (NumericDate seconds) has passed. */
12
+ /** The `ERR_JWT_EXPIRED` message for an `exp` (NumericDate seconds) in the past. */
53
13
  export declare function expiredClaimMessage(exp: number): string;
54
14
  /**
55
- * Validate JWT claims against the set expectations. A malformed expectation throws
56
- * `ERR_INVALID_OPTION`; an empty `audience` or `issuer` list matches no token.
15
+ * Checks `jwtClaims` against `options`. An empty `audience` or `issuer` list matches no token.
16
+ * @throws `ERR_INVALID_OPTION` if an option is malformed.
17
+ * @throws `ERR_JWT_CLAIM_MISSING` if a claim that `requiredClaims` or a claim option names is absent.
18
+ * @throws `ERR_JWT_CLAIM_INVALID` if a claim does not match or a time claim is not a NumericDate.
19
+ * @throws `ERR_JWT_NBF` if `nbf` is in the future.
20
+ * @throws `ERR_JWT_EXPIRED` if `exp` has passed or `iat` is older than `maxTokenAge`.
57
21
  */
58
22
  export declare function validateJwtClaims(jwtClaims: JWTClaims, options?: JWTClaimValidationOptions): void;
@@ -1,4 +1,4 @@
1
- import { UnsecureError, safeJsonParse, textEncoder } from "./libs/unsecure.mjs";
1
+ import { UnsecureError, textEncoder } from "./libs/unsecure.mjs";
2
2
  import { JWTError } from "../error.mjs";
3
3
  function base64UrlStringify(data) {
4
4
  return encode(bytesOf(data, "Base64Url"), "base64url");
@@ -82,10 +82,6 @@ function parse(text, alphabet, padded, label, options) {
82
82
  function malformed(label, detail) {
83
83
  return new UnsecureError("MALFORMED", `${label}.parse: ${detail}`);
84
84
  }
85
- const base64UrlEncode = base64UrlStringify;
86
- const base64UrlDecode = base64UrlParse;
87
- const base64Encode = base64Stringify;
88
- const base64Decode = base64Parse;
89
85
  function isJWK(key) {
90
86
  return typeof key === "object" && key !== null && "kty" in key && (key.kty === "oct" || key.kty === "RSA" || key.kty === "EC" || key.kty === "OKP");
91
87
  }
@@ -124,13 +120,8 @@ function isPublicJWK(key) {
124
120
  if (key.kty === "OKP") return typeof key.x === "string";
125
121
  return typeof key.n === "string" && typeof key.e === "string";
126
122
  }
127
- function hasSymmetricSecret(key) {
128
- return key.kty === "oct" || Object.hasOwn(key, "k");
129
- }
130
- const MAX_QUOTED_VALUE = 64;
131
- function quoteTokenValue(value) {
132
- const text = String(value);
133
- return JSON.stringify(text.length > MAX_QUOTED_VALUE ? `${text.slice(0, MAX_QUOTED_VALUE)}…` : text);
123
+ function hasMisplacedSecret(key) {
124
+ return key.kty !== "oct" && Object.hasOwn(key, "k");
134
125
  }
135
126
  function concatUint8Arrays(...arrays) {
136
127
  const totalLength = arrays.reduce((length, arr) => length + arr.length, 0);
@@ -299,149 +290,4 @@ function _expiredCause(claims) {
299
290
  exp
300
291
  };
301
292
  }
302
- function applyTypDefault(header, payload) {
303
- const declared = header.typ;
304
- if (declared !== void 0 && typeof declared !== "string") throw new JWTError("\"typ\" must be a string.", "ERR_INVALID_OPTION");
305
- if (declared === void 0 && typeof payload === "object" && payload !== null && !(payload instanceof Uint8Array)) header.typ = "JWT";
306
- return header;
307
- }
308
- function parsePayloadBytes(bytes, options) {
309
- const mode = options?.payload ?? "json";
310
- if (mode === "bytes") return bytes;
311
- const family = options?.family ?? "JWS";
312
- const text = parsePayloadText(bytes, { family });
313
- if (mode === "string") return text;
314
- return _parsePayloadObject(text, family);
315
- }
316
- function parsePayloadText(bytes, options) {
317
- const family = options?.family ?? "JWS";
318
- try {
319
- return _utf8Decoder.decode(bytes);
320
- } catch (error) {
321
- throw new JWTError(`Invalid ${family}: Payload is not valid UTF-8.`, `ERR_${family}_INVALID`, error);
322
- }
323
- }
324
- function parsePayloadSegment(payloadEncoded, options) {
325
- return parsePayloadBytes(base64UrlParse(payloadEncoded, { returnAs: "uint8array" }), options);
326
- }
327
- function getPlaintextBytes(payload) {
328
- if (payload instanceof Uint8Array) return payload;
329
- if (typeof payload === "string") return textEncoder.encode(payload);
330
- if (typeof payload === "object" && payload !== null) {
331
- let json;
332
- try {
333
- json = JSON.stringify(payload);
334
- } catch (error) {
335
- throw new JWTError("Payload is not JSON-serializable.", "ERR_INVALID_OPTION", error);
336
- }
337
- return textEncoder.encode(json);
338
- }
339
- throw new JWTError("Plaintext must be a string, Uint8Array, or a JSON-serializable object.", "ERR_INVALID_OPTION");
340
- }
341
- function computeJwtTimeClaims(payload, options) {
342
- const { currentDate, issuedAt, expiresIn, expiresAt, notBeforeIn, notBeforeAt } = options ?? {};
343
- if (issuedAt !== void 0 && typeof issuedAt !== "boolean" && !(issuedAt instanceof Date)) throw new JWTError("\"issuedAt\" must be a boolean or a Date.", "ERR_INVALID_OPTION");
344
- if (expiresIn !== void 0 && expiresAt !== void 0) throw new JWTError("\"expiresIn\" and \"expiresAt\" are mutually exclusive.", "ERR_JWT_CLAIM_INVALID");
345
- if (notBeforeIn !== void 0 && notBeforeAt !== void 0) throw new JWTError("\"notBeforeIn\" and \"notBeforeAt\" are mutually exclusive.", "ERR_JWT_CLAIM_INVALID");
346
- const hasExpHint = expiresIn !== void 0 || expiresAt !== void 0;
347
- const hasNbfHint = notBeforeIn !== void 0 || notBeforeAt !== void 0;
348
- const hasIatHint = issuedAt === true || issuedAt instanceof Date;
349
- if (!hasExpHint && !hasNbfHint && !hasIatHint) return void 0;
350
- if (!payload || typeof payload !== "object" || payload instanceof Uint8Array) return void 0;
351
- const wantsExp = hasExpHint && (!("exp" in payload) || payload.exp === void 0);
352
- const wantsNbf = hasNbfHint && (!("nbf" in payload) || payload.nbf === void 0);
353
- const wantsIat = hasIatHint && (!("iat" in payload) || payload.iat === void 0);
354
- if (!wantsExp && !wantsNbf && !wantsIat) return void 0;
355
- const now = Math.floor((currentDate ?? /* @__PURE__ */ new Date()).getTime() / 1e3);
356
- const claims = { ...payload };
357
- claims.iat ||= issuedAt instanceof Date ? Math.floor(_instantOf(issuedAt, "issuedAt") / 1e3) : now;
358
- if (wantsExp) {
359
- if (expiresAt !== void 0) claims.exp = Math.floor(_instantOf(expiresAt, "expiresAt") / 1e3);
360
- else if (expiresIn !== void 0) claims.exp = now + computeDurationInSeconds(expiresIn);
361
- }
362
- if (wantsNbf) {
363
- if (notBeforeAt !== void 0) claims.nbf = Math.floor(_instantOf(notBeforeAt, "notBeforeAt") / 1e3);
364
- else if (notBeforeIn !== void 0) {
365
- const offset = parseDurationSeconds(notBeforeIn);
366
- if (offset < 0) throw new JWTError("\"notBeforeIn\" must be zero or a positive duration.", "ERR_JWT_CLAIM_INVALID");
367
- claims.nbf = now + offset;
368
- }
369
- }
370
- return claims;
371
- }
372
- const BASE_PROCESSED = /* @__PURE__ */ Object.freeze({
373
- JWS: ["b64"],
374
- JWE: []
375
- });
376
- function validateCriticalHeadersJWS(protectedHeader, recognizedHeaders) {
377
- validateCriticalHeaders(protectedHeader, {
378
- family: "JWS",
379
- recognizedHeaders
380
- });
381
- }
382
- function validateCriticalHeadersJWE(protectedHeader, recognizedHeaders) {
383
- validateCriticalHeaders(protectedHeader, {
384
- family: "JWE",
385
- recognizedHeaders
386
- });
387
- }
388
- const REGISTERED_HEADER_PARAMS = /* @__PURE__ */ new Set([
389
- "alg",
390
- "enc",
391
- "zip",
392
- "jku",
393
- "jwk",
394
- "kid",
395
- "x5u",
396
- "x5c",
397
- "x5t",
398
- "x5t#S256",
399
- "typ",
400
- "cty",
401
- "crit",
402
- "epk",
403
- "apu",
404
- "apv",
405
- "iv",
406
- "tag",
407
- "p2s",
408
- "p2c"
409
- ]);
410
- function assertCritShape(crit, options) {
411
- const { family } = options;
412
- const code = options.code ?? `ERR_${family}_INVALID`;
413
- if (!Array.isArray(crit) || crit.length === 0 || crit.some((param) => typeof param !== "string")) throw new JWTError(`Invalid ${family}: "crit" must be a non-empty array of strings.`, code);
414
- for (const param of crit) if (REGISTERED_HEADER_PARAMS.has(param)) throw new JWTError(`Invalid ${family}: "crit" must not list registered header parameters.`, code);
415
- }
416
- function _instantOf(value, name) {
417
- const ms = value instanceof Date ? value.getTime() : NaN;
418
- if (!Number.isFinite(ms)) throw new JWTError(`"${name}" must be a valid Date.`, "ERR_INVALID_OPTION");
419
- return ms;
420
- }
421
- const _utf8Decoder = /* @__PURE__ */ new TextDecoder("utf-8", {
422
- fatal: true,
423
- ignoreBOM: true
424
- });
425
- function _parsePayloadObject(text, family) {
426
- const code = `ERR_${family}_INVALID`;
427
- const message = `Invalid ${family}: Payload is not a JSON object.`;
428
- let parsed;
429
- try {
430
- parsed = safeJsonParse(text);
431
- } catch (error) {
432
- throw new JWTError(message, code, error);
433
- }
434
- if (!isPlainObject(parsed)) throw new JWTError(message, code);
435
- return parsed;
436
- }
437
- function validateCriticalHeaders(protectedHeader, options) {
438
- const { crit } = protectedHeader;
439
- if (crit === void 0) return;
440
- const { family, recognizedHeaders = [] } = options;
441
- assertCritShape(crit, { family });
442
- for (const param of crit) {
443
- if (!Object.hasOwn(protectedHeader, param)) throw new JWTError(`Critical header parameter ${quoteTokenValue(param)} is missing from the protected header.`, `ERR_${family}_INVALID`);
444
- if (!recognizedHeaders.includes(param) && !BASE_PROCESSED[family].includes(param)) throw new JWTError(`Unprocessed critical header parameters: ${quoteTokenValue(param)}`, `ERR_${family}_INVALID`);
445
- }
446
- }
447
- export { applyTypDefault, assertClaimValidationOptions, assertCritShape, assertCryptoKey, assertPlainObject, assertStringList, base64Decode, base64Encode, base64Parse, base64Stringify, base64UrlDecode, base64UrlEncode, base64UrlParse, base64UrlStringify, computeDurationInSeconds, computeJwtTimeClaims, concatUint8Arrays, expiredClaimMessage, getPlaintextBytes, hasSymmetricSecret, isAsymmetricJWK, isCryptoKey, isCryptoKeyPair, isJWK, isJWKSet, isJWTContent, isPlainObject, isPrivateJWK, isPublicJWK, isSymmetricJWK, maybeArray, parsePayloadBytes, parsePayloadSegment, parsePayloadText, quoteTokenValue, uint32be, validateCriticalHeadersJWE, validateCriticalHeadersJWS, validateJwtClaims };
293
+ export { assertClaimValidationOptions, assertCryptoKey, assertPlainObject, assertStringList, base64Parse, base64Stringify, base64UrlParse, base64UrlStringify, computeDurationInSeconds, concatUint8Arrays, expiredClaimMessage, hasMisplacedSecret, isAsymmetricJWK, isCryptoKey, isCryptoKeyPair, isJWK, isJWKSet, isJWTContent, isPlainObject, isPrivateJWK, isPublicJWK, isSymmetricJWK, maybeArray, parseDurationSeconds, uint32be, validateJwtClaims };