unjwt 0.8.3 → 0.8.5
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 +3 -9
- package/dist/_chunks/algorithms.mjs +1541 -0
- package/dist/_chunks/{_codec-jwe.d.mts → codec-jwe.d.mts} +40 -9
- package/dist/_chunks/{_codec-jws.mjs → codec-jws.mjs} +148 -95
- package/dist/_chunks/cookie.mjs +50 -0
- package/dist/_chunks/digest.mjs +10 -3
- package/dist/_chunks/error.d.mts +103 -0
- package/dist/_chunks/export.mjs +97 -0
- package/dist/_chunks/index.d.mts +5 -103
- package/dist/_chunks/index2.d.mts +206 -12
- package/dist/_chunks/index3.d.mts +12 -109
- package/dist/_chunks/{jwe.d.mts → index4.d.mts} +3 -1
- package/dist/_chunks/{jws.d.mts → index5.d.mts} +6 -2
- package/dist/_chunks/index6.d.mts +113 -0
- package/dist/_chunks/jwe.mjs +208 -74
- package/dist/_chunks/jwk.mjs +214 -2237
- package/dist/_chunks/jws.mjs +289 -202
- package/dist/_chunks/jwt.d.mts +2 -1
- package/dist/_chunks/{_jwt-internal.mjs → jwt.mjs} +124 -68
- package/dist/_chunks/libs/unsecure.d.mts +35 -25
- package/dist/_chunks/libs/unsecure.mjs +148 -302
- package/dist/_chunks/{_internal.d.mts → limits.d.mts} +1 -1
- package/dist/_chunks/payload.mjs +23 -0
- package/dist/_chunks/wrap.mjs +209 -0
- package/dist/adapters/elysia/index.d.mts +4 -4
- package/dist/adapters/elysia/index.mjs +5 -3
- package/dist/adapters/h3/index.d.mts +13 -13
- package/dist/adapters/h3/index.mjs +8 -10
- package/dist/adapters/h3v1/index.d.mts +5 -5
- package/dist/adapters/h3v1/index.mjs +6 -8
- package/dist/core/internal.mjs +1 -1
- package/dist/core/{jwe.d.mts → jwe/index.d.mts} +5 -4
- package/dist/core/jwe/index.mjs +5 -0
- package/dist/core/jwk/index.d.mts +6 -0
- package/dist/core/jwk/index.mjs +7 -0
- package/dist/core/{jws.d.mts → jws/index.d.mts} +5 -4
- package/dist/core/jws/index.mjs +5 -0
- package/dist/dpop/index.d.mts +73 -0
- package/dist/dpop/index.mjs +296 -0
- package/dist/error.d.mts +3 -0
- package/dist/error.mjs +20 -0
- package/dist/index.d.mts +10 -9
- package/dist/index.mjs +7 -3
- package/dist/session/index.d.mts +4 -4
- package/dist/session/index.mjs +6 -46
- package/dist/utils/index.d.mts +7 -0
- package/dist/{core/utils → utils}/index.mjs +4 -3
- package/package.json +22 -10
- package/dist/core/jwe.mjs +0 -5
- package/dist/core/jwk.d.mts +0 -207
- package/dist/core/jwk.mjs +0 -4
- package/dist/core/jws.mjs +0 -5
- package/dist/core/utils/index.d.mts +0 -6
- /package/dist/_chunks/{_cookie-attributes.d.mts → cookie-attributes.d.mts} +0 -0
|
@@ -1,43 +1,86 @@
|
|
|
1
|
-
import { UnsecureError,
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
constructor(message, code, cause) {
|
|
6
|
-
super(message);
|
|
7
|
-
this.name = "JWTError";
|
|
8
|
-
this.code = code;
|
|
9
|
-
this.cause = cause;
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
function isJWTError(error, code) {
|
|
13
|
-
if (!(error instanceof JWTError)) return false;
|
|
14
|
-
return code === void 0 || error.code === code;
|
|
1
|
+
import { UnsecureError, safeJsonParse, textEncoder } from "./libs/unsecure.mjs";
|
|
2
|
+
import { JWTError } from "../error.mjs";
|
|
3
|
+
function base64UrlStringify(data) {
|
|
4
|
+
return encode(bytesOf(data, "Base64Url"), "base64url");
|
|
15
5
|
}
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
return code === void 0 || error.code === code;
|
|
6
|
+
function base64Stringify(data) {
|
|
7
|
+
return encode(bytesOf(data, "Base64"), "base64");
|
|
19
8
|
}
|
|
20
|
-
function
|
|
21
|
-
return
|
|
9
|
+
function base64UrlParse(text, options) {
|
|
10
|
+
return parse(text, URL, false, "Base64Url", options);
|
|
22
11
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
...options,
|
|
26
|
-
alphabet: "base64url",
|
|
27
|
-
padding: false
|
|
28
|
-
};
|
|
29
|
-
return typeof input === "string" ? base64Parse$1(input, parseOptions) : base64Parse$1(input, parseOptions);
|
|
12
|
+
function base64Parse(text, options) {
|
|
13
|
+
return parse(text, STD, true, "Base64", options);
|
|
30
14
|
}
|
|
31
|
-
|
|
32
|
-
|
|
15
|
+
const URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
16
|
+
const STD = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
17
|
+
const fatalDecoder = /* @__PURE__ */ new TextDecoder("utf-8", {
|
|
18
|
+
ignoreBOM: true,
|
|
19
|
+
fatal: true
|
|
20
|
+
});
|
|
21
|
+
function bytesOf(data, label) {
|
|
22
|
+
if (typeof data === "string") return textEncoder.encode(data);
|
|
23
|
+
if (data instanceof Uint8Array) return data;
|
|
24
|
+
throw new UnsecureError("INVALID_TYPE", `${label}.stringify: expected a string or Uint8Array.`);
|
|
25
|
+
}
|
|
26
|
+
function encode(bytes, name) {
|
|
27
|
+
const buffer = globalThis.Buffer;
|
|
28
|
+
if (typeof buffer?.from === "function") return buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString(name);
|
|
29
|
+
if ("toBase64" in Uint8Array.prototype) return (bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength ? bytes : bytes.slice()).toBase64({
|
|
30
|
+
alphabet: name,
|
|
31
|
+
omitPadding: name === "base64url"
|
|
32
|
+
});
|
|
33
|
+
const alphabet = name === "base64url" ? URL : STD;
|
|
34
|
+
const padded = name === "base64";
|
|
35
|
+
let out = "";
|
|
36
|
+
let i = 0;
|
|
37
|
+
for (; i + 2 < bytes.length; i += 3) {
|
|
38
|
+
const n = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
|
|
39
|
+
out += alphabet[n >>> 18] + alphabet[n >>> 12 & 63] + alphabet[n >>> 6 & 63] + alphabet[n & 63];
|
|
40
|
+
}
|
|
41
|
+
const rem = bytes.length - i;
|
|
42
|
+
if (rem === 1) {
|
|
43
|
+
const n = bytes[i] << 16;
|
|
44
|
+
out += alphabet[n >>> 18] + alphabet[n >>> 12 & 63] + (padded ? "==" : "");
|
|
45
|
+
} else if (rem === 2) {
|
|
46
|
+
const n = bytes[i] << 16 | bytes[i + 1] << 8;
|
|
47
|
+
out += alphabet[n >>> 18] + alphabet[n >>> 12 & 63] + alphabet[n >>> 6 & 63] + (padded ? "=" : "");
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
function parse(text, alphabet, padded, label, options) {
|
|
52
|
+
if (typeof text !== "string") throw new UnsecureError("INVALID_TYPE", `${label}.parse: expected a string, got ${typeof text}.`);
|
|
53
|
+
const wantString = options?.returnAs === void 0 || options.returnAs === "string";
|
|
54
|
+
let end = text.length;
|
|
55
|
+
while (end > 0 && text.charCodeAt(end - 1) === 61) end--;
|
|
56
|
+
const rem = end % 4;
|
|
57
|
+
if (rem === 1) throw malformed(label, `${end} symbols cannot encode whole bytes.`);
|
|
58
|
+
const padNeeded = padded && rem !== 0 ? 4 - rem : 0;
|
|
59
|
+
if (text.length - end !== padNeeded) throw malformed(label, `expected ${padNeeded} "=" padding characters, found ${text.length - end}.`);
|
|
60
|
+
const out = new Uint8Array(end * 6 >>> 3);
|
|
61
|
+
let acc = 0;
|
|
62
|
+
let held = 0;
|
|
63
|
+
let j = 0;
|
|
64
|
+
for (let i = 0; i < end; i++) {
|
|
65
|
+
const value = alphabet.indexOf(text[i]);
|
|
66
|
+
if (value < 0) throw malformed(label, `invalid character ${JSON.stringify(text[i])} at index ${i}.`);
|
|
67
|
+
acc = acc << 6 | value;
|
|
68
|
+
held += 6;
|
|
69
|
+
if (held >= 8) {
|
|
70
|
+
held -= 8;
|
|
71
|
+
out[j++] = acc >>> held & 255;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (held !== 0 && (acc & (1 << held) - 1) !== 0) throw malformed(label, "the last symbol sets bits past the final byte.");
|
|
75
|
+
if (!wantString) return out;
|
|
76
|
+
try {
|
|
77
|
+
return fatalDecoder.decode(out);
|
|
78
|
+
} catch {
|
|
79
|
+
throw malformed(label, "decoded bytes are not valid UTF-8.");
|
|
80
|
+
}
|
|
33
81
|
}
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
...options,
|
|
37
|
-
alphabet: "base64",
|
|
38
|
-
padding: true
|
|
39
|
-
};
|
|
40
|
-
return typeof input === "string" ? base64Parse$1(input, parseOptions) : base64Parse$1(input, parseOptions);
|
|
82
|
+
function malformed(label, detail) {
|
|
83
|
+
return new UnsecureError("MALFORMED", `${label}.parse: ${detail}`);
|
|
41
84
|
}
|
|
42
85
|
const base64UrlEncode = base64UrlStringify;
|
|
43
86
|
const base64UrlDecode = base64UrlParse;
|
|
@@ -75,11 +118,20 @@ function isPrivateJWK(key) {
|
|
|
75
118
|
return "d" in key && typeof key.d === "string";
|
|
76
119
|
}
|
|
77
120
|
function isPublicJWK(key) {
|
|
78
|
-
if (!isJWK(key) || key.kty === "oct" ||
|
|
121
|
+
if (!isJWK(key) || key.kty === "oct" || Object.hasOwn(key, "k")) return false;
|
|
122
|
+
if ("d" in key && typeof key.d === "string") return false;
|
|
79
123
|
if (key.kty === "EC") return typeof key.x === "string" && typeof key.y === "string";
|
|
80
124
|
if (key.kty === "OKP") return typeof key.x === "string";
|
|
81
125
|
return typeof key.n === "string" && typeof key.e === "string";
|
|
82
126
|
}
|
|
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);
|
|
134
|
+
}
|
|
83
135
|
function concatUint8Arrays(...arrays) {
|
|
84
136
|
const totalLength = arrays.reduce((length, arr) => length + arr.length, 0);
|
|
85
137
|
const result = new Uint8Array(totalLength);
|
|
@@ -91,7 +143,7 @@ function concatUint8Arrays(...arrays) {
|
|
|
91
143
|
return result;
|
|
92
144
|
}
|
|
93
145
|
function uint32be(value) {
|
|
94
|
-
if (value < 0 || value >= 2 ** 32) throw new JWTError(`value must be >= 0 and <= ${2 ** 32 - 1}
|
|
146
|
+
if (value < 0 || value >= 2 ** 32) throw new JWTError(`value must be >= 0 and <= ${2 ** 32 - 1} (got ${value})`, "ERR_INVALID_OPTION");
|
|
95
147
|
return new Uint8Array([
|
|
96
148
|
value >>> 24,
|
|
97
149
|
value >>> 16,
|
|
@@ -142,7 +194,7 @@ function parseDurationSeconds(value) {
|
|
|
142
194
|
}
|
|
143
195
|
if (typeof value === "string") {
|
|
144
196
|
const match = value.match(DURATION_REGEX);
|
|
145
|
-
if (!match || !match[1]) throw new JWTError("
|
|
197
|
+
if (!match || !match[1]) throw new JWTError("Duration must be an integer or a string like '10m' or '7D'.", "ERR_INVALID_OPTION");
|
|
146
198
|
const n = Number.parseInt(match[1], 10);
|
|
147
199
|
const unit = match[2] || "s";
|
|
148
200
|
return n * TIME_CONSTANTS[unit];
|
|
@@ -155,12 +207,12 @@ function computeDurationInSeconds(duration) {
|
|
|
155
207
|
return seconds;
|
|
156
208
|
}
|
|
157
209
|
function expiredClaimMessage(exp) {
|
|
158
|
-
return `JWT "exp"
|
|
210
|
+
return `JWT "exp" is in the past (${_formatNumericDate(exp)}).`;
|
|
159
211
|
}
|
|
160
212
|
function validateJwtClaims(jwtClaims, options = {}) {
|
|
161
213
|
assertClaimValidationOptions(options);
|
|
162
214
|
const clockTolerance = _normalizeClockTolerance(options.clockTolerance);
|
|
163
|
-
const currentTime =
|
|
215
|
+
const currentTime = (options.currentDate ?? /* @__PURE__ */ new Date()).getTime() / 1e3;
|
|
164
216
|
if (options.requiredClaims !== void 0 || options.issuer !== void 0 || options.audience !== void 0 || options.subject !== void 0 || options.maxTokenAge !== void 0) {
|
|
165
217
|
const allRequiredClaims = new Set(options.requiredClaims);
|
|
166
218
|
if (options.issuer !== void 0) allRequiredClaims.add("iss");
|
|
@@ -171,33 +223,25 @@ function validateJwtClaims(jwtClaims, options = {}) {
|
|
|
171
223
|
for (const claimName of allRequiredClaims) if (!Object.hasOwn(jwtClaims, claimName)) missingClaims.push(claimName);
|
|
172
224
|
if (missingClaims.length > 0) throw new JWTError(`Missing required JWT Claims: ${missingClaims.join(", ")}`, "ERR_JWT_CLAIM_MISSING");
|
|
173
225
|
}
|
|
174
|
-
_assertNumericDate(jwtClaims.exp, "exp"
|
|
175
|
-
_assertNumericDate(jwtClaims.nbf, "nbf"
|
|
176
|
-
_assertNumericDate(jwtClaims.iat, "iat"
|
|
226
|
+
_assertNumericDate(jwtClaims.exp, "exp");
|
|
227
|
+
_assertNumericDate(jwtClaims.nbf, "nbf");
|
|
228
|
+
_assertNumericDate(jwtClaims.iat, "iat");
|
|
177
229
|
if (options.issuer !== void 0) {
|
|
178
230
|
const expectedIssuers = maybeArray(options.issuer);
|
|
179
|
-
if (!jwtClaims.iss || !expectedIssuers.includes(jwtClaims.iss)) throw new JWTError(`Invalid JWT "iss"
|
|
231
|
+
if (!jwtClaims.iss || !expectedIssuers.includes(jwtClaims.iss)) throw new JWTError(`Invalid JWT "iss": expected ${expectedIssuers.join(" or ")}, got ${jwtClaims.iss}`, "ERR_JWT_CLAIM_INVALID");
|
|
180
232
|
}
|
|
181
|
-
if (options.subject !== void 0 && jwtClaims.sub !== options.subject) throw new JWTError(`Invalid JWT "sub"
|
|
233
|
+
if (options.subject !== void 0 && jwtClaims.sub !== options.subject) throw new JWTError(`Invalid JWT "sub": expected ${options.subject}, got ${jwtClaims.sub}`, "ERR_JWT_CLAIM_INVALID");
|
|
182
234
|
if (options.audience !== void 0) {
|
|
183
235
|
const expectedAudiences = maybeArray(options.audience);
|
|
184
236
|
const claimAudience = maybeArray(jwtClaims.aud || []);
|
|
185
|
-
if (!claimAudience.some((aud) => expectedAudiences.includes(aud))) throw new JWTError(`Invalid JWT "aud"
|
|
237
|
+
if (!claimAudience.some((aud) => expectedAudiences.includes(aud))) throw new JWTError(`Invalid JWT "aud": expected ${expectedAudiences.join(" or ")}, got ${claimAudience.join(", ")}`, "ERR_JWT_CLAIM_INVALID");
|
|
186
238
|
}
|
|
187
|
-
if (typeof jwtClaims.nbf === "number" && jwtClaims.nbf > currentTime + clockTolerance) throw new JWTError(`JWT "nbf"
|
|
188
|
-
if (typeof jwtClaims.exp === "number" && jwtClaims.exp <= currentTime - clockTolerance) throw new JWTError(expiredClaimMessage(jwtClaims.exp), "ERR_JWT_EXPIRED",
|
|
189
|
-
jti: jwtClaims.jti,
|
|
190
|
-
iat: jwtClaims.iat,
|
|
191
|
-
exp: jwtClaims.exp
|
|
192
|
-
});
|
|
239
|
+
if (typeof jwtClaims.nbf === "number" && jwtClaims.nbf > currentTime + clockTolerance) throw new JWTError(`JWT "nbf" is in the future (${_formatNumericDate(jwtClaims.nbf)}).`, "ERR_JWT_NBF");
|
|
240
|
+
if (typeof jwtClaims.exp === "number" && jwtClaims.exp <= currentTime - clockTolerance) throw new JWTError(expiredClaimMessage(jwtClaims.exp), "ERR_JWT_EXPIRED", _expiredCause(jwtClaims));
|
|
193
241
|
if (options.maxTokenAge !== void 0) {
|
|
194
|
-
if (typeof jwtClaims.iat !== "number") throw new JWTError("JWT \"iat\"
|
|
195
|
-
if (jwtClaims.iat > currentTime + clockTolerance) throw new JWTError(`JWT "iat"
|
|
196
|
-
if (jwtClaims.iat < currentTime - computeDurationInSeconds(options.maxTokenAge) - clockTolerance) throw new JWTError(`JWT "iat"
|
|
197
|
-
jti: jwtClaims.jti,
|
|
198
|
-
iat: jwtClaims.iat,
|
|
199
|
-
exp: jwtClaims.exp
|
|
200
|
-
});
|
|
242
|
+
if (typeof jwtClaims.iat !== "number") throw new JWTError("JWT \"iat\" must be a number when maxTokenAge is set.", "ERR_JWT_CLAIM_INVALID");
|
|
243
|
+
if (jwtClaims.iat > currentTime + clockTolerance) throw new JWTError(`JWT "iat" is in the future (${_formatNumericDate(jwtClaims.iat)}).`, "ERR_JWT_CLAIM_INVALID");
|
|
244
|
+
if (jwtClaims.iat < currentTime - computeDurationInSeconds(options.maxTokenAge) - clockTolerance) throw new JWTError(`JWT "iat" is older than maxTokenAge ${options.maxTokenAge}.`, "ERR_JWT_EXPIRED", _expiredCause(jwtClaims));
|
|
201
245
|
}
|
|
202
246
|
}
|
|
203
247
|
function assertClaimValidationOptions(options) {
|
|
@@ -238,10 +282,22 @@ function _assertExpectedValues(value, name) {
|
|
|
238
282
|
function _isNonEmptyString(value) {
|
|
239
283
|
return typeof value === "string" && value !== "";
|
|
240
284
|
}
|
|
241
|
-
function _assertNumericDate(value, claim
|
|
285
|
+
function _assertNumericDate(value, claim) {
|
|
242
286
|
if (value === void 0) return;
|
|
243
|
-
if (typeof value !== "number" || !Number.isFinite(value)) throw new JWTError(`JWT "${claim}"
|
|
244
|
-
if (value < 0) throw new JWTError(`JWT "${claim}"
|
|
287
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new JWTError(`JWT "${claim}" must be a number.`, "ERR_JWT_CLAIM_INVALID");
|
|
288
|
+
if (value < 0) throw new JWTError(`JWT "${claim}" must be a non-negative NumericDate.`, "ERR_JWT_CLAIM_INVALID");
|
|
289
|
+
}
|
|
290
|
+
function _formatNumericDate(seconds) {
|
|
291
|
+
const date = /* @__PURE__ */ new Date(seconds * 1e3);
|
|
292
|
+
return Number.isFinite(date.getTime()) ? date.toISOString() : String(seconds);
|
|
293
|
+
}
|
|
294
|
+
function _expiredCause(claims) {
|
|
295
|
+
const { jti, iat, exp } = claims;
|
|
296
|
+
return {
|
|
297
|
+
jti: typeof jti === "string" ? jti : void 0,
|
|
298
|
+
iat,
|
|
299
|
+
exp
|
|
300
|
+
};
|
|
245
301
|
}
|
|
246
302
|
function applyTypDefault(header, payload) {
|
|
247
303
|
const declared = header.typ;
|
|
@@ -285,8 +341,8 @@ function getPlaintextBytes(payload) {
|
|
|
285
341
|
function computeJwtTimeClaims(payload, options) {
|
|
286
342
|
const { currentDate, issuedAt, expiresIn, expiresAt, notBeforeIn, notBeforeAt } = options ?? {};
|
|
287
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");
|
|
288
|
-
if (expiresIn !== void 0 && expiresAt !== void 0) throw new JWTError("\"expiresIn\" and \"expiresAt\" are mutually exclusive
|
|
289
|
-
if (notBeforeIn !== void 0 && notBeforeAt !== void 0) throw new JWTError("\"notBeforeIn\" and \"notBeforeAt\" are mutually exclusive
|
|
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");
|
|
290
346
|
const hasExpHint = expiresIn !== void 0 || expiresAt !== void 0;
|
|
291
347
|
const hasNbfHint = notBeforeIn !== void 0 || notBeforeAt !== void 0;
|
|
292
348
|
const hasIatHint = issuedAt === true || issuedAt instanceof Date;
|
|
@@ -296,7 +352,7 @@ function computeJwtTimeClaims(payload, options) {
|
|
|
296
352
|
const wantsNbf = hasNbfHint && (!("nbf" in payload) || payload.nbf === void 0);
|
|
297
353
|
const wantsIat = hasIatHint && (!("iat" in payload) || payload.iat === void 0);
|
|
298
354
|
if (!wantsExp && !wantsNbf && !wantsIat) return void 0;
|
|
299
|
-
const now = Math.
|
|
355
|
+
const now = Math.floor((currentDate ?? /* @__PURE__ */ new Date()).getTime() / 1e3);
|
|
300
356
|
const claims = { ...payload };
|
|
301
357
|
claims.iat ||= issuedAt instanceof Date ? Math.floor(_instantOf(issuedAt, "issuedAt") / 1e3) : now;
|
|
302
358
|
if (wantsExp) {
|
|
@@ -384,8 +440,8 @@ function validateCriticalHeaders(protectedHeader, options) {
|
|
|
384
440
|
const { family, recognizedHeaders = [] } = options;
|
|
385
441
|
assertCritShape(crit, { family });
|
|
386
442
|
for (const param of crit) {
|
|
387
|
-
if (!Object.hasOwn(protectedHeader, param)) throw new JWTError(`Critical header parameter
|
|
388
|
-
if (!recognizedHeaders.includes(param) && !BASE_PROCESSED[family].includes(param)) throw new JWTError(`Unprocessed critical header parameters: ${param}`, `ERR_${family}_INVALID`);
|
|
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`);
|
|
389
445
|
}
|
|
390
446
|
}
|
|
391
|
-
export {
|
|
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 };
|
|
@@ -92,7 +92,8 @@ export declare function safeJsonParse<T = any>(json: string): T;
|
|
|
92
92
|
* Remove prototype-pollution vectors from a plain record in-place.
|
|
93
93
|
*
|
|
94
94
|
* Strips own properties named `__proto__`, `prototype`, and `constructor`
|
|
95
|
-
* through nested objects and arrays
|
|
95
|
+
* through nested objects and arrays — a named property hung on an array
|
|
96
|
+
* included — enumerable or not. Cycle-safe.
|
|
96
97
|
*
|
|
97
98
|
* Values are read from their property descriptors — array elements included —
|
|
98
99
|
* so a getter is never invoked: an accessor is neither traversed nor removed
|
|
@@ -114,17 +115,36 @@ export declare function safeJsonParse<T = any>(json: string): T;
|
|
|
114
115
|
* one.
|
|
115
116
|
*/
|
|
116
117
|
export declare function sanitizeObject<T extends Record<string, unknown> | undefined>(obj: T): T;
|
|
118
|
+
/** Options for {@link sanitizeObjectCopy}. */
|
|
119
|
+
interface SanitizeCopyOptions {
|
|
120
|
+
/**
|
|
121
|
+
* Constructors whose instances the copy carries by reference, untouched.
|
|
122
|
+
* Everything the copy meets must be JSON-shaped — a plain object or an
|
|
123
|
+
* array — unless it is an instance (`instanceof`, so subclasses count) of
|
|
124
|
+
* one of these. By reference means unchanged *and* unsanitized: an instance
|
|
125
|
+
* listed here is trusted as the caller holds it.
|
|
126
|
+
*
|
|
127
|
+
* `instanceof` is bound to the realm the constructor came from, which is
|
|
128
|
+
* the realm the caller's own `Date` or `Map` belong to.
|
|
129
|
+
*/
|
|
130
|
+
allowlist?: Iterable<abstract new (...args: never[]) => object>;
|
|
131
|
+
}
|
|
117
132
|
/**
|
|
118
133
|
* Return a sanitized deep copy of `obj`. The input is never mutated.
|
|
119
134
|
*
|
|
120
135
|
* Dangerous keys (`__proto__`, `prototype`, `constructor`) are stripped from
|
|
121
136
|
* every array and plain object reached. A plain object is one rooted on
|
|
122
|
-
* `
|
|
123
|
-
* `
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
137
|
+
* `null` or on an `Object.prototype` — this realm's or another's, so a value
|
|
138
|
+
* parsed inside a `vm` context or a frame is rebuilt like one parsed here;
|
|
139
|
+
* copies of both are rooted on this realm's `Object.prototype`.
|
|
140
|
+
*
|
|
141
|
+
* Anything else — a `Date`, `Map`, `Set`, typed array, `RegExp` or class
|
|
142
|
+
* instance — is refused: such a value is not the JSON shape this copy exists
|
|
143
|
+
* for, and carrying it through silently would report a sanitized copy that
|
|
144
|
+
* holds an unexamined object. Name the constructors you trust in
|
|
145
|
+
* `allowlist`, and their instances are carried into the copy by reference,
|
|
146
|
+
* or use {@link sanitizeObject} to walk a live object graph in place.
|
|
147
|
+
* Functions are values, not containers, and are carried by reference.
|
|
128
148
|
*
|
|
129
149
|
* Only own enumerable data properties are copied — the JSON shape. Accessors
|
|
130
150
|
* are skipped rather than invoked, so the copy never runs caller code; an
|
|
@@ -135,10 +155,14 @@ export declare function sanitizeObject<T extends Record<string, unknown> | undef
|
|
|
135
155
|
* Cycle-safe: circular references in the input are preserved in the output
|
|
136
156
|
* (pointing at the copied node, not the original reference).
|
|
137
157
|
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
158
|
+
* Primitives and `undefined` are returned unchanged.
|
|
159
|
+
*
|
|
160
|
+
* @throws {UnsecureError} `INVALID_TYPE` if an object that is neither plain,
|
|
161
|
+
* an array nor an instance of an `allowlist` entry is
|
|
162
|
+
* met at any depth, or if `allowlist` holds something
|
|
163
|
+
* that is not a constructor.
|
|
140
164
|
*/
|
|
141
|
-
export declare function sanitizeObjectCopy<T
|
|
165
|
+
export declare function sanitizeObjectCopy<T>(obj: T, options?: SanitizeCopyOptions): T;
|
|
142
166
|
/**
|
|
143
167
|
* Shared UTF-8 `TextEncoder`. Encoding holds no state between calls, so a
|
|
144
168
|
* single instance serves every caller.
|
|
@@ -156,18 +180,4 @@ export type BytesSource = ArrayBufferLike | ArrayBufferView<ArrayBufferLike>;
|
|
|
156
180
|
* payload, not a signature to swallow.
|
|
157
181
|
*/
|
|
158
182
|
export declare const textDecoder: TextDecoder;
|
|
159
|
-
|
|
160
|
-
* Decoder output shape. Omit to mirror the input (`string` in → `string` out,
|
|
161
|
-
* `Uint8Array` in → `Uint8Array` out). `"bytes"` aliases `"uint8array"`.
|
|
162
|
-
*/
|
|
163
|
-
export type DecodeReturnAs = "string" | "uint8array" | "bytes";
|
|
164
|
-
/** Shared `parse` options. */
|
|
165
|
-
export interface DecodeOptions {
|
|
166
|
-
/** Output shape. Mirrors the input type when omitted. */
|
|
167
|
-
returnAs?: DecodeReturnAs;
|
|
168
|
-
/**
|
|
169
|
-
* Tolerate malformed input (skip/normalize) instead of throwing.
|
|
170
|
-
* @default false
|
|
171
|
-
*/
|
|
172
|
-
loose?: boolean;
|
|
173
|
-
}
|
|
183
|
+
export {};
|