unjwt 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/THIRD-PARTY-LICENSES.md.gz +0 -0
- package/dist/_chunks/_codec-jwe.d.mts +170 -0
- package/dist/_chunks/_codec-jws.mjs +878 -0
- package/dist/_chunks/_internal.d.mts +7 -0
- package/dist/_chunks/_jwt-internal.mjs +388 -0
- package/dist/_chunks/algorithms.mjs +186 -0
- package/dist/_chunks/index.d.mts +512 -818
- package/dist/_chunks/index2.d.mts +11 -0
- package/dist/_chunks/index3.d.mts +108 -0
- package/dist/_chunks/jwe.d.mts +43 -66
- package/dist/_chunks/jwe.mjs +384 -252
- package/dist/_chunks/jwk.mjs +1390 -622
- package/dist/_chunks/jws.d.mts +48 -103
- package/dist/_chunks/jws.mjs +372 -184
- package/dist/_chunks/jwt.d.mts +56 -0
- package/dist/_chunks/libs/unsecure.d.mts +164 -85
- package/dist/_chunks/libs/unsecure.mjs +390 -123
- package/dist/adapters/elysia/index.d.mts +33 -171
- package/dist/adapters/elysia/index.mjs +106 -575
- package/dist/adapters/h3v1/index.d.mts +59 -344
- package/dist/adapters/h3v1/index.mjs +114 -619
- package/dist/adapters/h3v2/index.d.mts +58 -326
- package/dist/adapters/h3v2/index.mjs +108 -611
- package/dist/core/internal.d.mts +2 -0
- package/dist/core/internal.mjs +1 -0
- package/dist/core/jwe.d.mts +4 -2
- package/dist/core/jwe.mjs +4 -2
- package/dist/core/jwk.d.mts +183 -371
- package/dist/core/jwk.mjs +4 -3
- package/dist/core/jws.d.mts +4 -2
- package/dist/core/jws.mjs +4 -2
- package/dist/core/utils/index.d.mts +5 -155
- package/dist/core/utils/index.mjs +12 -3
- package/dist/index.d.mts +8 -4
- package/dist/index.mjs +6 -3
- package/package.json +39 -36
- package/dist/_chunks/_internal.mjs +0 -88
- package/dist/_chunks/utils.mjs +0 -390
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { JOSELimits } from "./index.mjs";
|
|
2
|
+
import "./index2.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* Default per-call work limits: 16 signatures, 16 recipients, 8 JWK Set candidates,
|
|
5
|
+
* and PBKDF2 iterations for four default-cost PBES2 attempts in total.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_JOSE_LIMITS: Readonly<Required<JOSELimits>>;
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { UnsecureError, base64Parse as base64Parse$1, base64Stringify as base64Stringify$1, safeJsonParse, textEncoder } from "./libs/unsecure.mjs";
|
|
2
|
+
var JWTError = class extends Error {
|
|
3
|
+
code;
|
|
4
|
+
cause;
|
|
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;
|
|
15
|
+
}
|
|
16
|
+
function isUnsecureError(error, code) {
|
|
17
|
+
if (!(error instanceof UnsecureError)) return false;
|
|
18
|
+
return code === void 0 || error.code === code;
|
|
19
|
+
}
|
|
20
|
+
function base64UrlStringify(data) {
|
|
21
|
+
return base64Stringify$1(data, { alphabet: "base64url" });
|
|
22
|
+
}
|
|
23
|
+
function base64UrlParse(input, options) {
|
|
24
|
+
const parseOptions = {
|
|
25
|
+
...options,
|
|
26
|
+
alphabet: "base64url",
|
|
27
|
+
padding: false
|
|
28
|
+
};
|
|
29
|
+
return typeof input === "string" ? base64Parse$1(input, parseOptions) : base64Parse$1(input, parseOptions);
|
|
30
|
+
}
|
|
31
|
+
function base64Stringify(data) {
|
|
32
|
+
return base64Stringify$1(data);
|
|
33
|
+
}
|
|
34
|
+
function base64Parse(input, options) {
|
|
35
|
+
const parseOptions = {
|
|
36
|
+
...options,
|
|
37
|
+
alphabet: "base64",
|
|
38
|
+
padding: true
|
|
39
|
+
};
|
|
40
|
+
return typeof input === "string" ? base64Parse$1(input, parseOptions) : base64Parse$1(input, parseOptions);
|
|
41
|
+
}
|
|
42
|
+
const base64UrlEncode = base64UrlStringify;
|
|
43
|
+
const base64UrlDecode = base64UrlParse;
|
|
44
|
+
const base64Encode = base64Stringify;
|
|
45
|
+
const base64Decode = base64Parse;
|
|
46
|
+
function isJWK(key) {
|
|
47
|
+
return typeof key === "object" && key !== null && "kty" in key && (key.kty === "oct" || key.kty === "RSA" || key.kty === "EC" || key.kty === "OKP");
|
|
48
|
+
}
|
|
49
|
+
function isJWKSet(key) {
|
|
50
|
+
return typeof key === "object" && key !== null && "keys" in key && Array.isArray(key.keys);
|
|
51
|
+
}
|
|
52
|
+
function isPlainObject(value) {
|
|
53
|
+
if (typeof value !== "object" || value === null) return false;
|
|
54
|
+
const prototype = Object.getPrototypeOf(value);
|
|
55
|
+
return prototype === Object.prototype || prototype === null;
|
|
56
|
+
}
|
|
57
|
+
function assertPlainObject(value, name) {
|
|
58
|
+
if (!isPlainObject(value)) throw new JWTError(`"${name}" must be an object.`, "ERR_INVALID_OPTION");
|
|
59
|
+
}
|
|
60
|
+
function assertCryptoKey(key) {
|
|
61
|
+
if (!isCryptoKey(key)) throw new JWTError("CryptoKey instance expected", "ERR_JWK_INVALID");
|
|
62
|
+
}
|
|
63
|
+
function isCryptoKey(key) {
|
|
64
|
+
return key?.[Symbol.toStringTag] === "CryptoKey";
|
|
65
|
+
}
|
|
66
|
+
const isCryptoKeyPair = (key) => typeof key === "object" && key !== null && "publicKey" in key && "privateKey" in key && isCryptoKey(key.publicKey) && isCryptoKey(key.privateKey);
|
|
67
|
+
function isSymmetricJWK(key) {
|
|
68
|
+
return isJWK(key) && key.kty === "oct" && typeof key.k === "string";
|
|
69
|
+
}
|
|
70
|
+
function isAsymmetricJWK(key) {
|
|
71
|
+
return !isSymmetricJWK(key);
|
|
72
|
+
}
|
|
73
|
+
function isPrivateJWK(key) {
|
|
74
|
+
if (!isJWK(key) || key.kty === "oct") return false;
|
|
75
|
+
return "d" in key && typeof key.d === "string";
|
|
76
|
+
}
|
|
77
|
+
function isPublicJWK(key) {
|
|
78
|
+
if (!isJWK(key) || key.kty === "oct" || "d" in key && typeof key.d === "string") return false;
|
|
79
|
+
if (key.kty === "EC") return typeof key.x === "string" && typeof key.y === "string";
|
|
80
|
+
if (key.kty === "OKP") return typeof key.x === "string";
|
|
81
|
+
return typeof key.n === "string" && typeof key.e === "string";
|
|
82
|
+
}
|
|
83
|
+
function concatUint8Arrays(...arrays) {
|
|
84
|
+
const totalLength = arrays.reduce((length, arr) => length + arr.length, 0);
|
|
85
|
+
const result = new Uint8Array(totalLength);
|
|
86
|
+
let offset = 0;
|
|
87
|
+
for (const arr of arrays) {
|
|
88
|
+
result.set(arr, offset);
|
|
89
|
+
offset += arr.length;
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
function uint32be(value) {
|
|
94
|
+
if (value < 0 || value >= 2 ** 32) throw new JWTError(`value must be >= 0 and <= ${2 ** 32 - 1}. Received ${value}`, "ERR_INVALID_OPTION");
|
|
95
|
+
return new Uint8Array([
|
|
96
|
+
value >>> 24,
|
|
97
|
+
value >>> 16,
|
|
98
|
+
value >>> 8,
|
|
99
|
+
value & 255
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
function maybeArray(item) {
|
|
103
|
+
return Array.isArray(item) ? item : [item];
|
|
104
|
+
}
|
|
105
|
+
function isJWTContent(header) {
|
|
106
|
+
if (!header) return false;
|
|
107
|
+
const typ = header.typ;
|
|
108
|
+
if (typeof typ === "string" && typ.toLowerCase().includes("jwt")) return true;
|
|
109
|
+
const declared = header.cty;
|
|
110
|
+
if (typeof declared !== "string") return false;
|
|
111
|
+
const cty = declared.toLowerCase();
|
|
112
|
+
return cty === "json" || cty === "application/json" || cty.endsWith("+json");
|
|
113
|
+
}
|
|
114
|
+
const TIME_CONSTANTS = Object.freeze({
|
|
115
|
+
s: 1,
|
|
116
|
+
second: 1,
|
|
117
|
+
seconds: 1,
|
|
118
|
+
m: 60,
|
|
119
|
+
minute: 60,
|
|
120
|
+
minutes: 60,
|
|
121
|
+
h: 3600,
|
|
122
|
+
hour: 3600,
|
|
123
|
+
hours: 3600,
|
|
124
|
+
D: 86400,
|
|
125
|
+
day: 86400,
|
|
126
|
+
days: 86400,
|
|
127
|
+
W: 604800,
|
|
128
|
+
week: 604800,
|
|
129
|
+
weeks: 604800,
|
|
130
|
+
M: 2592e3,
|
|
131
|
+
month: 2592e3,
|
|
132
|
+
months: 2592e3,
|
|
133
|
+
Y: 31536e3,
|
|
134
|
+
year: 31536e3,
|
|
135
|
+
years: 31536e3
|
|
136
|
+
});
|
|
137
|
+
const DURATION_REGEX = /^(\d+)(s|second|seconds|m|minute|minutes|h|hour|hours|D|day|days|W|week|weeks|M|month|months|Y|year|years)?$/;
|
|
138
|
+
function parseDurationSeconds(value) {
|
|
139
|
+
if (typeof value === "number") {
|
|
140
|
+
if (!Number.isInteger(value)) throw new JWTError("Duration must be an integer number of seconds.", "ERR_INVALID_OPTION");
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
if (typeof value === "string") {
|
|
144
|
+
const match = value.match(DURATION_REGEX);
|
|
145
|
+
if (!match || !match[1]) throw new JWTError("Invalid duration format. Must be an integer or a string like '10m', '2h', '7D', etc.", "ERR_INVALID_OPTION");
|
|
146
|
+
const n = Number.parseInt(match[1], 10);
|
|
147
|
+
const unit = match[2] || "s";
|
|
148
|
+
return n * TIME_CONSTANTS[unit];
|
|
149
|
+
}
|
|
150
|
+
throw new JWTError("Duration must be a number or a string representing time.", "ERR_INVALID_OPTION");
|
|
151
|
+
}
|
|
152
|
+
function computeDurationInSeconds(duration) {
|
|
153
|
+
const seconds = parseDurationSeconds(duration);
|
|
154
|
+
if (seconds <= 0) throw new JWTError("Duration must be a positive integer number of seconds.", "ERR_INVALID_OPTION");
|
|
155
|
+
return seconds;
|
|
156
|
+
}
|
|
157
|
+
function expiredClaimMessage(exp) {
|
|
158
|
+
return `JWT "exp" (Expiration Time) Claim validation failed: Token has expired (exp: ${(/* @__PURE__ */ new Date(exp * 1e3)).toISOString()})`;
|
|
159
|
+
}
|
|
160
|
+
function validateJwtClaims(jwtClaims, options = {}) {
|
|
161
|
+
assertClaimValidationOptions(options);
|
|
162
|
+
const clockTolerance = _normalizeClockTolerance(options.clockTolerance);
|
|
163
|
+
const currentTime = Math.round((options.currentDate ?? /* @__PURE__ */ new Date()).getTime() / 1e3);
|
|
164
|
+
if (options.requiredClaims !== void 0 || options.issuer !== void 0 || options.audience !== void 0 || options.subject !== void 0 || options.maxTokenAge !== void 0) {
|
|
165
|
+
const allRequiredClaims = new Set(options.requiredClaims);
|
|
166
|
+
if (options.issuer !== void 0) allRequiredClaims.add("iss");
|
|
167
|
+
if (options.audience !== void 0) allRequiredClaims.add("aud");
|
|
168
|
+
if (options.subject !== void 0) allRequiredClaims.add("sub");
|
|
169
|
+
if (options.maxTokenAge !== void 0) allRequiredClaims.add("iat");
|
|
170
|
+
const missingClaims = [];
|
|
171
|
+
for (const claimName of allRequiredClaims) if (!Object.hasOwn(jwtClaims, claimName)) missingClaims.push(claimName);
|
|
172
|
+
if (missingClaims.length > 0) throw new JWTError(`Missing required JWT Claims: ${missingClaims.join(", ")}`, "ERR_JWT_CLAIM_MISSING");
|
|
173
|
+
}
|
|
174
|
+
_assertNumericDate(jwtClaims.exp, "exp", "Expiration Time");
|
|
175
|
+
_assertNumericDate(jwtClaims.nbf, "nbf", "Not Before");
|
|
176
|
+
_assertNumericDate(jwtClaims.iat, "iat", "Issued At");
|
|
177
|
+
if (options.issuer !== void 0) {
|
|
178
|
+
const expectedIssuers = maybeArray(options.issuer);
|
|
179
|
+
if (!jwtClaims.iss || !expectedIssuers.includes(jwtClaims.iss)) throw new JWTError(`Invalid JWT "iss" (Issuer) Claim: Expected ${expectedIssuers.join(" or ")}, got ${jwtClaims.iss}`, "ERR_JWT_CLAIM_INVALID");
|
|
180
|
+
}
|
|
181
|
+
if (options.subject !== void 0 && jwtClaims.sub !== options.subject) throw new JWTError(`Invalid JWT "sub" (Subject) Claim: Expected ${options.subject}, got ${jwtClaims.sub}`, "ERR_JWT_CLAIM_INVALID");
|
|
182
|
+
if (options.audience !== void 0) {
|
|
183
|
+
const expectedAudiences = maybeArray(options.audience);
|
|
184
|
+
const claimAudience = maybeArray(jwtClaims.aud || []);
|
|
185
|
+
if (!claimAudience.some((aud) => expectedAudiences.includes(aud))) throw new JWTError(`Invalid JWT "aud" (Audience) Claim: Expected ${expectedAudiences.join(" or ")}, got ${claimAudience.join(", ")}`, "ERR_JWT_CLAIM_INVALID");
|
|
186
|
+
}
|
|
187
|
+
if (typeof jwtClaims.nbf === "number" && jwtClaims.nbf > currentTime + clockTolerance) throw new JWTError(`JWT "nbf" (Not Before) Claim validation failed: Token is not yet valid (nbf: ${(/* @__PURE__ */ new Date(jwtClaims.nbf * 1e3)).toISOString()})`, "ERR_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
|
+
});
|
|
193
|
+
if (options.maxTokenAge !== void 0) {
|
|
194
|
+
if (typeof jwtClaims.iat !== "number") throw new JWTError("JWT \"iat\" (Issued At) Claim must be a number when maxTokenAge is set.", "ERR_JWT_CLAIM_INVALID");
|
|
195
|
+
if (jwtClaims.iat > currentTime + clockTolerance) throw new JWTError(`JWT "iat" (Issued At) Claim validation failed: Token was issued in the future (iat: ${(/* @__PURE__ */ new Date(jwtClaims.iat * 1e3)).toISOString()})`, "ERR_JWT_CLAIM_INVALID");
|
|
196
|
+
if (jwtClaims.iat < currentTime - computeDurationInSeconds(options.maxTokenAge) - clockTolerance) throw new JWTError(`JWT "iat" (Issued At) Claim validation failed: Token is too old (maxTokenAge: ${options.maxTokenAge}, iat: ${(/* @__PURE__ */ new Date(jwtClaims.iat * 1e3)).toISOString()})`, "ERR_JWT_EXPIRED", {
|
|
197
|
+
jti: jwtClaims.jti,
|
|
198
|
+
iat: jwtClaims.iat,
|
|
199
|
+
exp: jwtClaims.exp
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function assertClaimValidationOptions(options) {
|
|
204
|
+
_assertExpectedValues(options.issuer, "issuer");
|
|
205
|
+
_assertExpectedValues(options.audience, "audience");
|
|
206
|
+
const subject = options.subject;
|
|
207
|
+
if (subject !== void 0 && !_isNonEmptyString(subject)) throw new JWTError("\"subject\" must be a non-empty string.", "ERR_INVALID_OPTION");
|
|
208
|
+
if (options.maxTokenAge !== void 0) computeDurationInSeconds(options.maxTokenAge);
|
|
209
|
+
assertStringList(options.requiredClaims, "requiredClaims");
|
|
210
|
+
_normalizeClockTolerance(options.clockTolerance);
|
|
211
|
+
}
|
|
212
|
+
function assertStringList(value, name) {
|
|
213
|
+
if (value === void 0) return;
|
|
214
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) return;
|
|
215
|
+
throw new JWTError(`"${name}" must be an array of strings.`, "ERR_INVALID_OPTION");
|
|
216
|
+
}
|
|
217
|
+
function _normalizeClockTolerance(value) {
|
|
218
|
+
if (value === void 0) return 0;
|
|
219
|
+
let seconds;
|
|
220
|
+
if (typeof value === "number") seconds = value;
|
|
221
|
+
else if (typeof value === "string") try {
|
|
222
|
+
seconds = computeDurationInSeconds(value);
|
|
223
|
+
} catch {
|
|
224
|
+
throw _invalidClockTolerance();
|
|
225
|
+
}
|
|
226
|
+
else throw _invalidClockTolerance();
|
|
227
|
+
if (!Number.isFinite(seconds) || seconds < 0) throw _invalidClockTolerance();
|
|
228
|
+
return seconds;
|
|
229
|
+
}
|
|
230
|
+
function _invalidClockTolerance() {
|
|
231
|
+
return new JWTError("clockTolerance must be a non-negative number of seconds or a Duration.", "ERR_INVALID_OPTION");
|
|
232
|
+
}
|
|
233
|
+
function _assertExpectedValues(value, name) {
|
|
234
|
+
if (value === void 0 || _isNonEmptyString(value)) return;
|
|
235
|
+
if (Array.isArray(value) && value.every((entry) => _isNonEmptyString(entry))) return;
|
|
236
|
+
throw new JWTError(`"${name}" must be a non-empty string or an array of non-empty strings.`, "ERR_INVALID_OPTION");
|
|
237
|
+
}
|
|
238
|
+
function _isNonEmptyString(value) {
|
|
239
|
+
return typeof value === "string" && value !== "";
|
|
240
|
+
}
|
|
241
|
+
function _assertNumericDate(value, claim, label) {
|
|
242
|
+
if (value === void 0) return;
|
|
243
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new JWTError(`JWT "${claim}" (${label}) Claim must be a number.`, "ERR_JWT_CLAIM_INVALID");
|
|
244
|
+
if (value < 0) throw new JWTError(`JWT "${claim}" (${label}) Claim must be a non-negative NumericDate.`, "ERR_JWT_CLAIM_INVALID");
|
|
245
|
+
}
|
|
246
|
+
function applyTypDefault(header, payload) {
|
|
247
|
+
const declared = header.typ;
|
|
248
|
+
if (declared !== void 0 && typeof declared !== "string") throw new JWTError("\"typ\" must be a string.", "ERR_INVALID_OPTION");
|
|
249
|
+
if (declared === void 0 && typeof payload === "object" && payload !== null && !(payload instanceof Uint8Array)) header.typ = "JWT";
|
|
250
|
+
return header;
|
|
251
|
+
}
|
|
252
|
+
function parsePayloadBytes(bytes, options) {
|
|
253
|
+
const mode = options?.payload ?? "json";
|
|
254
|
+
if (mode === "bytes") return bytes;
|
|
255
|
+
const family = options?.family ?? "JWS";
|
|
256
|
+
const text = parsePayloadText(bytes, { family });
|
|
257
|
+
if (mode === "string") return text;
|
|
258
|
+
return _parsePayloadObject(text, family);
|
|
259
|
+
}
|
|
260
|
+
function parsePayloadText(bytes, options) {
|
|
261
|
+
const family = options?.family ?? "JWS";
|
|
262
|
+
try {
|
|
263
|
+
return _utf8Decoder.decode(bytes);
|
|
264
|
+
} catch (error) {
|
|
265
|
+
throw new JWTError(`Invalid ${family}: Payload is not valid UTF-8.`, `ERR_${family}_INVALID`, error);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function parsePayloadSegment(payloadEncoded, options) {
|
|
269
|
+
return parsePayloadBytes(base64UrlParse(payloadEncoded, { returnAs: "uint8array" }), options);
|
|
270
|
+
}
|
|
271
|
+
function getPlaintextBytes(payload) {
|
|
272
|
+
if (payload instanceof Uint8Array) return payload;
|
|
273
|
+
if (typeof payload === "string") return textEncoder.encode(payload);
|
|
274
|
+
if (typeof payload === "object" && payload !== null) {
|
|
275
|
+
let json;
|
|
276
|
+
try {
|
|
277
|
+
json = JSON.stringify(payload);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
throw new JWTError("Payload is not JSON-serializable.", "ERR_INVALID_OPTION", error);
|
|
280
|
+
}
|
|
281
|
+
return textEncoder.encode(json);
|
|
282
|
+
}
|
|
283
|
+
throw new JWTError("Plaintext must be a string, Uint8Array, or a JSON-serializable object.", "ERR_INVALID_OPTION");
|
|
284
|
+
}
|
|
285
|
+
function computeJwtTimeClaims(payload, options) {
|
|
286
|
+
const { currentDate, expiresIn, expiresAt, notBeforeIn, notBeforeAt } = options ?? {};
|
|
287
|
+
if (expiresIn !== void 0 && expiresAt !== void 0) throw new JWTError("\"expiresIn\" and \"expiresAt\" are mutually exclusive; pass only one.", "ERR_JWT_CLAIM_INVALID");
|
|
288
|
+
if (notBeforeIn !== void 0 && notBeforeAt !== void 0) throw new JWTError("\"notBeforeIn\" and \"notBeforeAt\" are mutually exclusive; pass only one.", "ERR_JWT_CLAIM_INVALID");
|
|
289
|
+
const hasExpHint = expiresIn !== void 0 || expiresAt !== void 0;
|
|
290
|
+
const hasNbfHint = notBeforeIn !== void 0 || notBeforeAt !== void 0;
|
|
291
|
+
if (!hasExpHint && !hasNbfHint) return void 0;
|
|
292
|
+
if (!payload || typeof payload !== "object" || payload instanceof Uint8Array) return void 0;
|
|
293
|
+
const wantsExp = hasExpHint && (!("exp" in payload) || payload.exp === void 0);
|
|
294
|
+
const wantsNbf = hasNbfHint && (!("nbf" in payload) || payload.nbf === void 0);
|
|
295
|
+
if (!wantsExp && !wantsNbf) return void 0;
|
|
296
|
+
const now = Math.round((currentDate ?? /* @__PURE__ */ new Date()).getTime() / 1e3);
|
|
297
|
+
const claims = { ...payload };
|
|
298
|
+
claims.iat ||= now;
|
|
299
|
+
if (wantsExp) {
|
|
300
|
+
if (expiresAt !== void 0) claims.exp = Math.floor(_instantOf(expiresAt, "expiresAt") / 1e3);
|
|
301
|
+
else if (expiresIn !== void 0) claims.exp = claims.iat + computeDurationInSeconds(expiresIn);
|
|
302
|
+
}
|
|
303
|
+
if (wantsNbf) {
|
|
304
|
+
if (notBeforeAt !== void 0) claims.nbf = Math.floor(_instantOf(notBeforeAt, "notBeforeAt") / 1e3);
|
|
305
|
+
else if (notBeforeIn !== void 0) {
|
|
306
|
+
const offset = parseDurationSeconds(notBeforeIn);
|
|
307
|
+
if (offset < 0) throw new JWTError("\"notBeforeIn\" must be zero or a positive duration.", "ERR_JWT_CLAIM_INVALID");
|
|
308
|
+
claims.nbf = claims.iat + offset;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return claims;
|
|
312
|
+
}
|
|
313
|
+
const BASE_PROCESSED = /* @__PURE__ */ Object.freeze({
|
|
314
|
+
JWS: ["b64"],
|
|
315
|
+
JWE: []
|
|
316
|
+
});
|
|
317
|
+
function validateCriticalHeadersJWS(protectedHeader, recognizedHeaders) {
|
|
318
|
+
validateCriticalHeaders(protectedHeader, {
|
|
319
|
+
family: "JWS",
|
|
320
|
+
recognizedHeaders
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
function validateCriticalHeadersJWE(protectedHeader, recognizedHeaders) {
|
|
324
|
+
validateCriticalHeaders(protectedHeader, {
|
|
325
|
+
family: "JWE",
|
|
326
|
+
recognizedHeaders
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
const REGISTERED_HEADER_PARAMS = /* @__PURE__ */ new Set([
|
|
330
|
+
"alg",
|
|
331
|
+
"enc",
|
|
332
|
+
"zip",
|
|
333
|
+
"jku",
|
|
334
|
+
"jwk",
|
|
335
|
+
"kid",
|
|
336
|
+
"x5u",
|
|
337
|
+
"x5c",
|
|
338
|
+
"x5t",
|
|
339
|
+
"x5t#S256",
|
|
340
|
+
"typ",
|
|
341
|
+
"cty",
|
|
342
|
+
"crit",
|
|
343
|
+
"epk",
|
|
344
|
+
"apu",
|
|
345
|
+
"apv",
|
|
346
|
+
"iv",
|
|
347
|
+
"tag",
|
|
348
|
+
"p2s",
|
|
349
|
+
"p2c"
|
|
350
|
+
]);
|
|
351
|
+
function assertCritShape(crit, options) {
|
|
352
|
+
const { family } = options;
|
|
353
|
+
const code = options.code ?? `ERR_${family}_INVALID`;
|
|
354
|
+
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);
|
|
355
|
+
for (const param of crit) if (REGISTERED_HEADER_PARAMS.has(param)) throw new JWTError(`Invalid ${family}: "crit" must not list registered header parameters.`, code);
|
|
356
|
+
}
|
|
357
|
+
function _instantOf(value, name) {
|
|
358
|
+
const ms = value instanceof Date ? value.getTime() : NaN;
|
|
359
|
+
if (!Number.isFinite(ms)) throw new JWTError(`"${name}" must be a valid Date.`, "ERR_INVALID_OPTION");
|
|
360
|
+
return ms;
|
|
361
|
+
}
|
|
362
|
+
const _utf8Decoder = /* @__PURE__ */ new TextDecoder("utf-8", {
|
|
363
|
+
fatal: true,
|
|
364
|
+
ignoreBOM: true
|
|
365
|
+
});
|
|
366
|
+
function _parsePayloadObject(text, family) {
|
|
367
|
+
const code = `ERR_${family}_INVALID`;
|
|
368
|
+
const message = `Invalid ${family}: Payload is not a JSON object.`;
|
|
369
|
+
let parsed;
|
|
370
|
+
try {
|
|
371
|
+
parsed = safeJsonParse(text);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
throw new JWTError(message, code, error);
|
|
374
|
+
}
|
|
375
|
+
if (!isPlainObject(parsed)) throw new JWTError(message, code);
|
|
376
|
+
return parsed;
|
|
377
|
+
}
|
|
378
|
+
function validateCriticalHeaders(protectedHeader, options) {
|
|
379
|
+
const { crit } = protectedHeader;
|
|
380
|
+
if (crit === void 0) return;
|
|
381
|
+
const { family, recognizedHeaders = [] } = options;
|
|
382
|
+
assertCritShape(crit, { family });
|
|
383
|
+
for (const param of crit) {
|
|
384
|
+
if (!Object.hasOwn(protectedHeader, param)) throw new JWTError(`Critical header parameter "${param}" listed in "crit" but not present in the protected header.`, `ERR_${family}_INVALID`);
|
|
385
|
+
if (!recognizedHeaders.includes(param) && !BASE_PROCESSED[family].includes(param)) throw new JWTError(`Unprocessed critical header parameters: ${param}`, `ERR_${family}_INVALID`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
export { JWTError, applyTypDefault, assertClaimValidationOptions, assertCritShape, assertCryptoKey, assertPlainObject, assertStringList, base64Decode, base64Encode, base64Parse, base64Stringify, base64UrlDecode, base64UrlEncode, base64UrlParse, base64UrlStringify, computeDurationInSeconds, computeJwtTimeClaims, concatUint8Arrays, expiredClaimMessage, getPlaintextBytes, isAsymmetricJWK, isCryptoKey, isCryptoKeyPair, isJWK, isJWKSet, isJWTContent, isJWTError, isPlainObject, isPrivateJWK, isPublicJWK, isSymmetricJWK, isUnsecureError, maybeArray, parsePayloadBytes, parsePayloadSegment, parsePayloadText, uint32be, validateCriticalHeadersJWE, validateCriticalHeadersJWS, validateJwtClaims };
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { JWTError, isCryptoKey, isCryptoKeyPair, isJWK, isJWKSet } from "./_jwt-internal.mjs";
|
|
2
|
+
const ECDSA_CURVES = /* @__PURE__ */ Object.freeze({
|
|
3
|
+
ES256: "P-256",
|
|
4
|
+
ES384: "P-384",
|
|
5
|
+
ES512: "P-521"
|
|
6
|
+
});
|
|
7
|
+
function jwsHashOf(alg) {
|
|
8
|
+
return `SHA-${alg.slice(-3)}`;
|
|
9
|
+
}
|
|
10
|
+
function rsaOaepHashOf(alg) {
|
|
11
|
+
return `SHA-${Number.parseInt(alg.slice(9), 10) || 1}`;
|
|
12
|
+
}
|
|
13
|
+
function aesBitsOf(alg) {
|
|
14
|
+
return Number.parseInt(alg.slice(1, 4), 10);
|
|
15
|
+
}
|
|
16
|
+
function innerKeyWrapAlg(alg) {
|
|
17
|
+
return alg.slice(-6);
|
|
18
|
+
}
|
|
19
|
+
function namedCurveOf(algorithm) {
|
|
20
|
+
return "namedCurve" in algorithm && typeof algorithm.namedCurve === "string" ? algorithm.namedCurve : void 0;
|
|
21
|
+
}
|
|
22
|
+
function cryptoKeyCurve(key) {
|
|
23
|
+
const { algorithm } = key;
|
|
24
|
+
return namedCurveOf(algorithm) ?? (algorithm.name === "X25519" ? algorithm.name : void 0);
|
|
25
|
+
}
|
|
26
|
+
function hashNameOf(algorithm) {
|
|
27
|
+
if (!("hash" in algorithm)) return void 0;
|
|
28
|
+
const { hash } = algorithm;
|
|
29
|
+
return typeof hash === "object" && hash !== null && "name" in hash && typeof hash.name === "string" ? hash.name : void 0;
|
|
30
|
+
}
|
|
31
|
+
function lengthOf(algorithm) {
|
|
32
|
+
return "length" in algorithm && typeof algorithm.length === "number" ? algorithm.length : void 0;
|
|
33
|
+
}
|
|
34
|
+
function modulusLengthOf(algorithm) {
|
|
35
|
+
return "modulusLength" in algorithm && typeof algorithm.modulusLength === "number" ? algorithm.modulusLength : void 0;
|
|
36
|
+
}
|
|
37
|
+
function hashBitsOf(hash) {
|
|
38
|
+
return Number.parseInt(hash.name.slice(4), 10);
|
|
39
|
+
}
|
|
40
|
+
function isAlgorithm(algorithm, name) {
|
|
41
|
+
return algorithm.name === name;
|
|
42
|
+
}
|
|
43
|
+
function unusableKey(name, prop = "algorithm.name") {
|
|
44
|
+
return new JWTError(`CryptoKey does not support this operation, its ${prop} must be ${name}`, "ERR_JWK_INVALID");
|
|
45
|
+
}
|
|
46
|
+
function checkKeyUsage(key, usage) {
|
|
47
|
+
if (usage && !key.usages.includes(usage)) throw new JWTError(`CryptoKey does not support this operation, its usages must include ${usage}.`, "ERR_JWK_INVALID");
|
|
48
|
+
}
|
|
49
|
+
const PBES2_ALGORITHMS = [
|
|
50
|
+
"PBES2-HS256+A128KW",
|
|
51
|
+
"PBES2-HS384+A192KW",
|
|
52
|
+
"PBES2-HS512+A256KW"
|
|
53
|
+
];
|
|
54
|
+
const ECDH_ES_ALGORITHMS = [
|
|
55
|
+
"ECDH-ES",
|
|
56
|
+
"ECDH-ES+A128KW",
|
|
57
|
+
"ECDH-ES+A192KW",
|
|
58
|
+
"ECDH-ES+A256KW"
|
|
59
|
+
];
|
|
60
|
+
function inferJWSAllowedAlgorithms(key, options) {
|
|
61
|
+
if (!key || typeof key === "function") return void 0;
|
|
62
|
+
if (typeof key === "string" || key instanceof Uint8Array) return void 0;
|
|
63
|
+
if (typeof key !== "object") return void 0;
|
|
64
|
+
if (isCryptoKeyPair(key)) return inferJWSAllowedAlgorithms(key.publicKey);
|
|
65
|
+
if (isJWKSet(key)) return unionFromJWKSet(key.keys, jwsAlgsFromJWK, options);
|
|
66
|
+
if (isCryptoKey(key)) return jwsAlgsFromCryptoKey(key);
|
|
67
|
+
if (isJWK(key)) return jwsAlgsFromJWK(key);
|
|
68
|
+
}
|
|
69
|
+
function inferJWEAllowedAlgorithms(key, options) {
|
|
70
|
+
if (!key || typeof key === "function") return void 0;
|
|
71
|
+
if (typeof key === "string") return [...PBES2_ALGORITHMS];
|
|
72
|
+
if (key instanceof Uint8Array) return void 0;
|
|
73
|
+
if (typeof key !== "object") return void 0;
|
|
74
|
+
if (isCryptoKeyPair(key)) return inferJWEAllowedAlgorithms(key.privateKey);
|
|
75
|
+
if (isJWKSet(key)) return unionFromJWKSet(key.keys, jweAlgsFromJWK, options);
|
|
76
|
+
if (isCryptoKey(key)) return jweAlgsFromCryptoKey(key);
|
|
77
|
+
if (isJWK(key)) return jweAlgsFromJWK(key);
|
|
78
|
+
}
|
|
79
|
+
function unionFromJWKSet(keys, mapper, options) {
|
|
80
|
+
const algs = /* @__PURE__ */ new Set();
|
|
81
|
+
for (const k of keys) {
|
|
82
|
+
if (!isJWK(k)) continue;
|
|
83
|
+
if (k.kty === "oct" && !options?.allowSymmetric) continue;
|
|
84
|
+
const mapped = mapper(k);
|
|
85
|
+
if (!mapped) continue;
|
|
86
|
+
for (const a of mapped) algs.add(a);
|
|
87
|
+
}
|
|
88
|
+
return algs.size > 0 ? [...algs] : void 0;
|
|
89
|
+
}
|
|
90
|
+
function isEd25519Signature(alg) {
|
|
91
|
+
return alg === "EdDSA" || alg === "Ed25519";
|
|
92
|
+
}
|
|
93
|
+
function jwsAlgsFromJWK(jwk) {
|
|
94
|
+
if (jwk.alg && isEd25519Signature(jwk.alg)) return ["EdDSA", "Ed25519"];
|
|
95
|
+
if (jwk.alg) return declaredAlgorithm(jwk.alg);
|
|
96
|
+
if (jwk.kty === "EC") {
|
|
97
|
+
const crv = jwk.crv;
|
|
98
|
+
if (crv === "P-256") return ["ES256"];
|
|
99
|
+
if (crv === "P-384") return ["ES384"];
|
|
100
|
+
if (crv === "P-521") return ["ES512"];
|
|
101
|
+
}
|
|
102
|
+
if (jwk.kty === "OKP") {
|
|
103
|
+
if (jwk.crv === "Ed25519") return ["EdDSA", "Ed25519"];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function jweAlgsFromJWK(jwk) {
|
|
107
|
+
if (jwk.kty === "oct") return jweAlgsFromOctJWK(jwk);
|
|
108
|
+
if (jwk.alg) return declaredAlgorithm(jwk.alg);
|
|
109
|
+
if (jwk.kty === "EC") {
|
|
110
|
+
const crv = jwk.crv;
|
|
111
|
+
if (crv === "P-256" || crv === "P-384" || crv === "P-521") return [...ECDH_ES_ALGORITHMS];
|
|
112
|
+
}
|
|
113
|
+
if (jwk.kty === "OKP") {
|
|
114
|
+
if (jwk.crv === "X25519") return [...ECDH_ES_ALGORITHMS];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function jweAlgsFromOctJWK(jwk) {
|
|
118
|
+
const alg = jwk.alg;
|
|
119
|
+
if (!alg) return void 0;
|
|
120
|
+
if (isContentEncryptionAlgorithm(alg)) return ["dir"];
|
|
121
|
+
return declaredAlgorithm(alg);
|
|
122
|
+
}
|
|
123
|
+
function isContentEncryptionAlgorithm(alg) {
|
|
124
|
+
return CONTENT_ENCRYPTION_ALGORITHMS.has(alg);
|
|
125
|
+
}
|
|
126
|
+
const CONTENT_ENCRYPTION_ALGORITHMS = /* @__PURE__ */ new Set([
|
|
127
|
+
"A128GCM",
|
|
128
|
+
"A192GCM",
|
|
129
|
+
"A256GCM",
|
|
130
|
+
"A128CBC-HS256",
|
|
131
|
+
"A192CBC-HS384",
|
|
132
|
+
"A256CBC-HS512"
|
|
133
|
+
]);
|
|
134
|
+
function jwsAlgsFromCryptoKey(key) {
|
|
135
|
+
const name = key.algorithm.name;
|
|
136
|
+
const hashBits = jwsHashBits(hashNameOf(key.algorithm));
|
|
137
|
+
switch (name) {
|
|
138
|
+
case "HMAC": return hashBits ? [`HS${hashBits}`] : void 0;
|
|
139
|
+
case "RSASSA-PKCS1-v1_5": return hashBits ? [`RS${hashBits}`] : void 0;
|
|
140
|
+
case "RSA-PSS": return hashBits ? [`PS${hashBits}`] : void 0;
|
|
141
|
+
case "ECDSA": {
|
|
142
|
+
const curve = namedCurveOf(key.algorithm);
|
|
143
|
+
if (curve === "P-256") return ["ES256"];
|
|
144
|
+
if (curve === "P-384") return ["ES384"];
|
|
145
|
+
if (curve === "P-521") return ["ES512"];
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
case "Ed25519": return ["EdDSA", "Ed25519"];
|
|
149
|
+
default: return;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function jweAlgsFromCryptoKey(key) {
|
|
153
|
+
const name = key.algorithm.name;
|
|
154
|
+
const hashName = hashNameOf(key.algorithm);
|
|
155
|
+
const length = lengthOf(key.algorithm);
|
|
156
|
+
switch (name) {
|
|
157
|
+
case "AES-KW":
|
|
158
|
+
if (length === 128) return ["A128KW", "dir"];
|
|
159
|
+
if (length === 192) return ["A192KW", "dir"];
|
|
160
|
+
if (length === 256) return ["A256KW", "dir"];
|
|
161
|
+
return;
|
|
162
|
+
case "AES-GCM":
|
|
163
|
+
if (length === 128) return ["A128GCMKW", "dir"];
|
|
164
|
+
if (length === 192) return ["A192GCMKW", "dir"];
|
|
165
|
+
if (length === 256) return ["A256GCMKW", "dir"];
|
|
166
|
+
return;
|
|
167
|
+
case "RSA-OAEP":
|
|
168
|
+
if (hashName === "SHA-1") return ["RSA-OAEP"];
|
|
169
|
+
if (hashName === "SHA-256") return ["RSA-OAEP-256"];
|
|
170
|
+
if (hashName === "SHA-384") return ["RSA-OAEP-384"];
|
|
171
|
+
if (hashName === "SHA-512") return ["RSA-OAEP-512"];
|
|
172
|
+
return;
|
|
173
|
+
case "ECDH":
|
|
174
|
+
case "X25519": return [...ECDH_ES_ALGORITHMS];
|
|
175
|
+
default: return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function jwsHashBits(hashName) {
|
|
179
|
+
if (hashName === "SHA-256") return "256";
|
|
180
|
+
if (hashName === "SHA-384") return "384";
|
|
181
|
+
if (hashName === "SHA-512") return "512";
|
|
182
|
+
}
|
|
183
|
+
function declaredAlgorithm(alg) {
|
|
184
|
+
return [alg];
|
|
185
|
+
}
|
|
186
|
+
export { ECDSA_CURVES, PBES2_ALGORITHMS, aesBitsOf, checkKeyUsage, cryptoKeyCurve, hashBitsOf, inferJWEAllowedAlgorithms, inferJWSAllowedAlgorithms, innerKeyWrapAlg, isAlgorithm, isContentEncryptionAlgorithm, isEd25519Signature, jwsHashOf, lengthOf, modulusLengthOf, namedCurveOf, rsaOaepHashOf, unusableKey };
|