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.
- package/README.md +34 -21
- package/dist/_chunks/algorithms.mjs +44 -20
- package/dist/_chunks/digest.d.mts +4 -4
- package/dist/_chunks/digest.mjs +23 -15
- package/dist/_chunks/error.d.mts +29 -42
- package/dist/_chunks/export.mjs +1 -4
- package/dist/_chunks/index.d.mts +270 -234
- package/dist/_chunks/index2.d.mts +82 -189
- package/dist/_chunks/index4.d.mts +223 -60
- package/dist/_chunks/index5.d.mts +68 -31
- package/dist/_chunks/index6.d.mts +45 -64
- package/dist/_chunks/jwe.mjs +53 -48
- package/dist/_chunks/jwk.mjs +9 -3
- package/dist/_chunks/jws.mjs +55 -50
- package/dist/_chunks/jwt.d.mts +13 -49
- package/dist/_chunks/jwt.mjs +4 -158
- package/dist/_chunks/jwt2.mjs +162 -0
- package/dist/_chunks/libs/unsecure.d.mts +0 -6
- package/dist/_chunks/limits.d.mts +3 -2
- package/dist/_chunks/payload.mjs +13 -7
- package/dist/_chunks/wrap.mjs +3 -1
- package/dist/core/internal.d.mts +50 -2
- package/dist/core/internal.mjs +3 -1
- package/dist/core/jwe/index.d.mts +1 -1
- package/dist/core/jwk/index.d.mts +2 -2
- package/dist/core/jwk/index.mjs +2 -2
- package/dist/dpop/index.d.mts +77 -18
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +4 -4
- package/dist/session/index.d.mts +568 -22
- package/dist/session/index.mjs +1386 -34
- package/dist/utils/index.d.mts +4 -4
- package/dist/utils/index.mjs +2 -10
- package/package.json +4 -32
- package/dist/_chunks/codec-jwe.d.mts +0 -280
- package/dist/_chunks/codec-jws.mjs +0 -1005
- package/dist/_chunks/cookie-attributes.d.mts +0 -12
- package/dist/_chunks/cookie.mjs +0 -50
- package/dist/adapters/elysia/index.d.mts +0 -123
- package/dist/adapters/elysia/index.mjs +0 -120
- package/dist/adapters/h3/index.d.mts +0 -135
- package/dist/adapters/h3/index.mjs +0 -117
- package/dist/adapters/h3v1/index.d.mts +0 -148
- package/dist/adapters/h3v1/index.mjs +0 -128
package/dist/_chunks/jws.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { textEncoder } from "./libs/unsecure.mjs";
|
|
2
2
|
import { JWTError } from "../error.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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,
|
|
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)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
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
|
-
|
|
325
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
458
|
+
failures.push(asKeyLookupError(err, `signature[${i}]`));
|
|
453
459
|
continue;
|
|
454
460
|
}
|
|
455
461
|
if (typeof rawKeyMaterial === "string") {
|
|
456
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
490
|
-
|
|
494
|
+
keyCandidates,
|
|
495
|
+
keyAttempts
|
|
491
496
|
})) {
|
|
492
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
556
|
-
|
|
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);
|
package/dist/_chunks/jwt.d.mts
CHANGED
|
@@ -1,58 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
*
|
|
56
|
-
* `ERR_INVALID_OPTION
|
|
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;
|
package/dist/_chunks/jwt.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UnsecureError,
|
|
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
|
|
128
|
-
return key.kty
|
|
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
|
-
|
|
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 };
|