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,1005 +0,0 @@
1
- import { safeJsonParse } from "./libs/unsecure.mjs";
2
- import { JWTError, isJWTError } from "../error.mjs";
3
- import { computeDurationInSeconds, expiredClaimMessage, isCryptoKey, isJWK, isJWKSet, isPlainObject, isPrivateJWK, isPublicJWK, isSymmetricJWK, quoteTokenValue } from "./jwt.mjs";
4
- import { importKey } from "./algorithms.mjs";
5
- import { sign, verify } from "./jws.mjs";
6
- import { decrypt, encrypt } from "./jwe.mjs";
7
- import { toPublicJWK } from "./export.mjs";
8
- const RESERVED_CLAIMS = [
9
- "jti",
10
- "iat",
11
- "exp",
12
- "nbf"
13
- ];
14
- const SESSION_TYP = "session+jwt";
15
- const FORBIDDEN_KEYS = [
16
- "__proto__",
17
- "constructor",
18
- "prototype"
19
- ];
20
- const importedKeys = /* @__PURE__ */ new WeakMap();
21
- function invalidKeyError(message, cause) {
22
- return new JWTError(message, "ERR_SESSION_KEY_INVALID", cause);
23
- }
24
- function invalidConfigError(message, cause) {
25
- return new JWTError(message, "ERR_SESSION_CONFIG_INVALID", cause);
26
- }
27
- function invalidTokenError(message, cause) {
28
- return new JWTError(message, "ERR_SESSION_TOKEN_INVALID", cause);
29
- }
30
- function readOnlyEventError(message) {
31
- return new JWTError(message, "ERR_SESSION_READONLY_EVENT");
32
- }
33
- function expiredSessionError(options) {
34
- const { id, createdAt, expiresAt } = options;
35
- const exp = Math.floor(expiresAt / 1e3);
36
- const iat = createdAt === void 0 ? void 0 : Math.floor(createdAt / 1e3);
37
- return new JWTError(expiredClaimMessage(exp), "ERR_JWT_EXPIRED", {
38
- jti: id,
39
- iat,
40
- exp
41
- });
42
- }
43
- function isTokenFailure(error) {
44
- return !(isJWTError(error.cause, "ERR_JWK_KEY_NOT_FOUND") && error.cause.cause !== void 0);
45
- }
46
- function rethrowAsInvalidToken(error, options) {
47
- if (isJWTError(error, "ERR_JWT_EXPIRED")) throw error;
48
- throw invalidTokenError(`${options.prefix} Invalid session token: ${messageOf(error)}`, error);
49
- }
50
- const TOKEN = /^[!#$%&'*+\-.^_`|~\w]+$/;
51
- const CHUNK_SUFFIX = /\.\d+$/;
52
- function assertSessionName(name, options) {
53
- if (!isToken(name)) throw invalidConfigError(`${options.prefix} Session "name" must be a valid cookie name (got ${quoteTokenValue(name)}).`);
54
- if (CHUNK_SUFFIX.test(name)) throw invalidConfigError(`${options.prefix} Session "name" ${quoteTokenValue(name)} cannot end in a dot and digits.`);
55
- }
56
- function isToken(value) {
57
- return typeof value === "string" && TOKEN.test(value);
58
- }
59
- function assertCookiePrefix(name, attrs, options) {
60
- const lower = name.toLowerCase();
61
- const host = lower.startsWith("__host-");
62
- if (!host && !lower.startsWith("__secure-")) return;
63
- const rule = host ? "\"__Host-\" cookie must be Secure, with Path=/ and no Domain" : "\"__Secure-\" cookie must be Secure";
64
- if (attrs.secure === false || host && (attrs.domain !== void 0 || attrs.path !== void 0 && attrs.path !== "/")) throw invalidConfigError(`${options.prefix} Session ${quoteTokenValue(name)}: a ${rule}.`);
65
- }
66
- function assertRolling(config, options) {
67
- const { rolling } = config;
68
- if (rolling === void 0) return;
69
- if (config.maxAge === void 0) throw invalidConfigError(`${options.prefix} "rolling" needs a "maxAge" to extend.`);
70
- if (rolling === true) return;
71
- if (!isExpiresIn(rolling)) throw new JWTError(`${options.prefix} "rolling" must be true or a duration.`, "ERR_INVALID_OPTION");
72
- if (computeDurationInSeconds(rolling) >= computeDurationInSeconds(config.maxAge)) throw invalidConfigError(`${options.prefix} "rolling" must be shorter than "maxAge".`);
73
- }
74
- function assertKeyMaterial(key, options) {
75
- if (typeof key === "string") throw invalidKeyError(`${options.prefix} Session "key" cannot be a string, use deriveJWKFromPassword().`);
76
- if (!key) throw invalidKeyError(`${options.prefix} Session "key" is required.`);
77
- }
78
- async function assertImportableKey(key, options) {
79
- if (!isJWK(key)) return;
80
- const alg = key.alg ?? options.alg;
81
- if (key.kty !== "oct" && !alg) return;
82
- let imports = importedKeys.get(key);
83
- if (!imports) {
84
- imports = /* @__PURE__ */ new Map();
85
- importedKeys.set(key, imports);
86
- }
87
- const slot = alg ?? "";
88
- let pending = imports.get(slot);
89
- if (!pending) {
90
- pending = importKey(key, { alg }).then(() => void 0);
91
- imports.set(slot, pending);
92
- }
93
- try {
94
- await pending;
95
- } catch (error) {
96
- if (imports.get(slot) === pending) imports.delete(slot);
97
- throw invalidKeyError(`${options.prefix} Invalid session key: ${messageOf(error)}`, error);
98
- }
99
- }
100
- function sessionTyp(configured) {
101
- return typeof configured === "string" && configured ? configured : SESSION_TYP;
102
- }
103
- function resolveExpiresAt(createdAt, options) {
104
- return options.maxAge === void 0 ? void 0 : createdAt + computeDurationInSeconds(options.maxAge) * 1e3;
105
- }
106
- function sessionRequiredClaims(configured, options) {
107
- const required = /* @__PURE__ */ new Set([
108
- ...configured || [],
109
- "jti",
110
- "iat"
111
- ]);
112
- if (options.maxAge !== void 0) required.add("exp");
113
- return [...required];
114
- }
115
- function sanitizeSessionUpdate(update, options) {
116
- if (!isPlainObject(update)) throw new JWTError(`${options.prefix} Session data must be a plain object.`, "ERR_INVALID_OPTION");
117
- const sanitized = Object.create(null);
118
- for (const key of Object.keys(update)) {
119
- if (FORBIDDEN_KEYS.includes(key)) throw new JWTError(`${options.prefix} Session data cannot carry the key "${key}".`, "ERR_INVALID_OPTION");
120
- if (RESERVED_CLAIMS.includes(key)) continue;
121
- sanitized[key] = update[key];
122
- }
123
- return sanitized;
124
- }
125
- function messageOf(error) {
126
- return error instanceof Error ? error.message : String(error);
127
- }
128
- function isExpiresIn(value) {
129
- return typeof value === "number" || typeof value === "string";
130
- }
131
- function createSessionCodec(spec) {
132
- const { prefix } = spec;
133
- const alg = (config) => spec.issueOptions(config)?.alg;
134
- const keyFor = (config, args) => spec.lookup(config, args) ?? spec.openKey(config);
135
- const now = (config) => spec.issueOptions(config)?.currentDate?.getTime() ?? Date.now();
136
- const openWith = async (config, token, key) => {
137
- const payload = await spec.open(config, token, key).catch((error) => rethrowAsInvalidToken(error, { prefix }));
138
- const clock = {
139
- now: now(config),
140
- clockTolerance: spec.openOptions(config)?.clockTolerance
141
- };
142
- assertNotFromTheFuture(payload, {
143
- ...clock,
144
- prefix
145
- });
146
- const opened = openedSession(payload, { maxAge: config.maxAge });
147
- assertNotPastMaxAge(opened, clock);
148
- return opened;
149
- };
150
- const assertOpenKey = async (config) => {
151
- if (spec.lookup(config, {})) return;
152
- const key = spec.openKey(config);
153
- for (const candidate of spec.importable ? spec.importable(key) : [key]) await assertImportableKey(candidate, {
154
- alg: alg(config),
155
- prefix
156
- });
157
- };
158
- return {
159
- family: spec.family,
160
- defaultName: spec.defaultName,
161
- defaultCookie: spec.defaultCookie,
162
- now,
163
- issuer: (config) => {
164
- const key = spec.issueKey(config);
165
- return (state) => spec.issue(config, sessionPayload(state), key);
166
- },
167
- assertKeys: async (config) => {
168
- const key = spec.issueKey(config);
169
- await assertImportableKey(key, {
170
- alg: alg(config),
171
- prefix
172
- });
173
- if (!isSymmetricJWK(key)) await assertOpenKey(config);
174
- },
175
- opener: (config, args) => {
176
- const key = keyFor(config, args);
177
- return (token) => openWith(config, token, key);
178
- },
179
- open: async (config, token, args) => {
180
- await assertOpenKey(config);
181
- return openWith(config, token, keyFor(config, args));
182
- }
183
- };
184
- }
185
- function sessionIssueOptions(options) {
186
- return Object.assign({}, options, {
187
- expiresIn: void 0,
188
- expiresAt: void 0,
189
- notBeforeIn: void 0,
190
- notBeforeAt: void 0,
191
- protectedHeader: {
192
- ...options?.protectedHeader,
193
- typ: sessionTyp(options?.protectedHeader?.typ)
194
- }
195
- });
196
- }
197
- function sessionOpenOptions(options) {
198
- const { maxAge, requiredClaims, typ, alg } = options;
199
- return {
200
- requiredClaims: sessionRequiredClaims(requiredClaims, { maxAge }),
201
- typ: sessionTyp(typ),
202
- maxTokenAge: void 0,
203
- algorithms: alg ? [alg] : void 0,
204
- payload: "json",
205
- validateClaims: true
206
- };
207
- }
208
- function isKeyPair(key) {
209
- return "privateKey" in key || "publicKey" in key;
210
- }
211
- function assertKeyPair(pair, options) {
212
- const { prefix, family } = options;
213
- if (!isCryptoKey(pair.privateKey) && !isPrivateJWK(pair.privateKey)) throw invalidKeyError(`${prefix} Invalid ${family} key: "privateKey" must be a private JWK or a CryptoKey.`, pair);
214
- if (!options.isPublic(pair.publicKey)) throw invalidKeyError(`${prefix} Invalid ${family} key: "publicKey" must be ${options.publicHalf}.`, pair);
215
- }
216
- function assertPinnedCryptoKey(key, options) {
217
- if (isCryptoKey(key) && !options.pinned) throw invalidConfigError(`${options.prefix} A CryptoKey session key carries no algorithm, set ${options.hint}.`);
218
- }
219
- function sessionPayload(state) {
220
- const iat = Math.floor(state.createdAt / 1e3);
221
- const exp = state.expiresAt ? Math.floor(state.expiresAt / 1e3) : void 0;
222
- const payload = {};
223
- for (const key of Object.keys(state.data)) if (!RESERVED_CLAIMS.includes(key)) payload[key] = state.data[key];
224
- payload.jti = state.id;
225
- payload.iat = iat;
226
- if (exp) payload.exp = exp;
227
- return payload;
228
- }
229
- function assertNotFromTheFuture(payload, options) {
230
- if (payload.iat > Math.floor(options.now / 1e3) + toleranceSeconds(options.clockTolerance)) throw invalidTokenError(`${options.prefix} Invalid session token: "iat" is in the future (${(/* @__PURE__ */ new Date(payload.iat * 1e3)).toISOString()}).`);
231
- }
232
- function assertNotPastMaxAge(opened, options) {
233
- const { expiresAt } = opened;
234
- if (expiresAt === void 0) return;
235
- if (expiresAt <= options.now - toleranceSeconds(options.clockTolerance) * 1e3) throw expiredSessionError({
236
- id: opened.id,
237
- createdAt: opened.createdAt,
238
- expiresAt
239
- });
240
- }
241
- function toleranceSeconds(clockTolerance) {
242
- return clockTolerance === void 0 ? 0 : computeDurationInSeconds(clockTolerance);
243
- }
244
- function openedSession(payload, options) {
245
- const data = Object.create(null);
246
- for (const key of Object.keys(payload)) {
247
- if (RESERVED_CLAIMS.includes(key)) continue;
248
- data[key] = payload[key];
249
- }
250
- const createdAt = payload.iat * 1e3;
251
- const exp = payload.exp ? payload.exp * 1e3 : void 0;
252
- const bound = resolveExpiresAt(createdAt, options);
253
- return {
254
- id: payload.jti,
255
- createdAt,
256
- expiresAt: exp === void 0 || bound === void 0 ? exp ?? bound : Math.min(exp, bound),
257
- data
258
- };
259
- }
260
- const JWE_DEFAULT_COOKIE = {
261
- path: "/",
262
- secure: true,
263
- httpOnly: true,
264
- sameSite: "lax"
265
- };
266
- function createJWECodec(options) {
267
- const { prefix, defaultName } = options;
268
- return createSessionCodec({
269
- family: "jwe",
270
- prefix,
271
- defaultName,
272
- defaultCookie: JWE_DEFAULT_COOKIE,
273
- issueOptions: (config) => config.jwe?.encryptOptions,
274
- openOptions: (config) => config.jwe?.decryptOptions,
275
- issueKey: (config) => getEncryptKey(config, prefix),
276
- openKey: (config) => getDecryptKey(config, prefix),
277
- lookup: (config, args) => {
278
- const hooks = config.hooks;
279
- if (!hooks?.onUnsealKeyLookup) return void 0;
280
- return (header) => hooks.onUnsealKeyLookup?.({
281
- header,
282
- ...args,
283
- config
284
- });
285
- },
286
- issue: (config, payload, key) => encrypt(payload, key, sessionIssueOptions(config.jwe?.encryptOptions)),
287
- open: async (config, sealed, key) => {
288
- const { decryptOptions, encryptOptions } = config.jwe ?? {};
289
- const enc = encryptOptions?.enc;
290
- const { payload } = await decrypt(sealed, key, {
291
- ...decryptOptions,
292
- maxIterations: sessionMaxIterations(config),
293
- ...sessionOpenOptions({
294
- maxAge: config.maxAge,
295
- requiredClaims: decryptOptions?.requiredClaims,
296
- typ: encryptOptions?.protectedHeader?.typ,
297
- alg: encryptOptions?.alg
298
- }),
299
- encryptionAlgorithms: enc ? [enc] : void 0
300
- });
301
- return payload;
302
- }
303
- });
304
- }
305
- function sessionMaxIterations(config) {
306
- const configured = config.jwe?.decryptOptions?.maxIterations;
307
- if (configured !== void 0) return configured;
308
- const alg = config.jwe?.encryptOptions?.alg ?? (isJWK(config.key) ? config.key.alg : void 0);
309
- return typeof alg === "string" && alg.startsWith("PBES2") ? config.jwe?.encryptOptions?.p2c ?? 6e5 : void 0;
310
- }
311
- function getEncryptKey(config, prefix) {
312
- const key = config.key;
313
- assertKeyMaterial(key, { prefix });
314
- let resolved;
315
- if (isCryptoKey(key) || isSymmetricJWK(key)) resolved = key;
316
- else if (isKeyPair(key)) resolved = jweKeyPair(key, prefix).publicKey;
317
- if (!resolved) throw invalidKeyError(`${prefix} Invalid JWE key, expected a JWK or a CryptoKey.`, key);
318
- assertPinnedAlgEnc(resolved, config, prefix);
319
- return resolved;
320
- }
321
- function getDecryptKey(config, prefix) {
322
- const key = config.key;
323
- assertKeyMaterial(key, { prefix });
324
- let resolved;
325
- if (isCryptoKey(key) || isSymmetricJWK(key)) resolved = key;
326
- else if (isKeyPair(key)) resolved = jweKeyPair(key, prefix).privateKey;
327
- if (!resolved) throw invalidKeyError(`${prefix} Invalid JWE key, expected a JWK, a private JWK or a CryptoKey.`, key);
328
- assertPinnedAlgEnc(resolved, config, prefix);
329
- return resolved;
330
- }
331
- function jweKeyPair(key, prefix) {
332
- const pair = {
333
- privateKey: key.privateKey,
334
- publicKey: key.publicKey ?? derivedPublicHalf(key.privateKey, prefix)
335
- };
336
- assertKeyPair(pair, {
337
- prefix,
338
- family: "JWE",
339
- isPublic: (half) => isCryptoKey(half) || isPublicJWK(half),
340
- publicHalf: "a public JWK or a CryptoKey"
341
- });
342
- return pair;
343
- }
344
- function derivedPublicHalf(privateKey, prefix) {
345
- if (isPrivateJWK(privateKey)) return toPublicJWK(privateKey);
346
- throw invalidKeyError(`${prefix} Invalid JWE key: a CryptoKey "privateKey" needs its "publicKey".`, privateKey);
347
- }
348
- function assertPinnedAlgEnc(key, config, prefix) {
349
- const { alg, enc } = config.jwe?.encryptOptions ?? {};
350
- assertPinnedCryptoKey(key, {
351
- pinned: Boolean(alg && enc),
352
- prefix,
353
- hint: "\"jwe.encryptOptions.alg\" and \"enc\""
354
- });
355
- }
356
- const kSessionPending = Symbol("unjwt_session_pending");
357
- function sessionInstant(source) {
358
- const now = source.codec.now(source.config);
359
- return now - now % 1e3;
360
- }
361
- function mintSessionId(config) {
362
- return config.generateId?.() || crypto.randomUUID();
363
- }
364
- function emptySession(binding) {
365
- const createdAt = sessionInstant(binding);
366
- return typedSession({
367
- id: void 0,
368
- createdAt,
369
- expiresAt: resolveExpiresAt(createdAt, { maxAge: binding.config.maxAge }),
370
- data: Object.create(null),
371
- token: void 0
372
- });
373
- }
374
- function storedSession(store) {
375
- const state = store.get();
376
- return state === void 0 ? void 0 : typedSession(state);
377
- }
378
- function blankSession() {
379
- return typedSession({
380
- expiresAt: void 0,
381
- data: {}
382
- });
383
- }
384
- function openSessionToken(codec, config, options) {
385
- return codec.open(config, options.token, options.args).then(typedOpened);
386
- }
387
- function typedSession(state) {
388
- return state;
389
- }
390
- function typedOpened(opened) {
391
- return opened;
392
- }
393
- const CHUNK_MARKER = "__chunked__";
394
- const DEFAULT_CHUNK_MAX_LENGTH = 4e3;
395
- function readChunked(store, name) {
396
- const main = store.read(name);
397
- if (main === void 0) return void 0;
398
- if (!main.startsWith("__chunked__")) return main;
399
- const count = parseChunkCount(main);
400
- if (count === 0) return void 0;
401
- const parts = [];
402
- for (let index = 1; index <= count; index++) {
403
- const part = store.read(chunkName(name, index));
404
- if (part === void 0) return void 0;
405
- parts.push(part);
406
- }
407
- return parts.join("");
408
- }
409
- function writeChunked(store, name, value, options) {
410
- const { attrs, chunkMaxLength, prefix } = options;
411
- const max = chunkMaxLength && chunkMaxLength > 0 ? chunkMaxLength : DEFAULT_CHUNK_MAX_LENGTH;
412
- const chunkCount = Math.ceil(value.length / max);
413
- if (chunkCount > 16) throw invalidConfigError(`${prefix} Session token requires ${chunkCount} cookie chunks, exceeding the limit of 16.`);
414
- const previousCount = parseChunkCount(store.read(name));
415
- const firstSurplus = chunkCount <= 1 ? 1 : chunkCount + 1;
416
- for (let index = firstSurplus; index <= previousCount; index++) expire(store, chunkName(name, index), attrs);
417
- if (chunkCount <= 1) {
418
- store.write(name, value, attrs);
419
- return;
420
- }
421
- store.write(name, `${CHUNK_MARKER}${chunkCount}`, attrs);
422
- for (let index = 1; index <= chunkCount; index++) store.write(chunkName(name, index), value.slice((index - 1) * max, index * max), attrs);
423
- }
424
- function removeChunked(store, name, options) {
425
- const count = parseChunkCount(store.read(name));
426
- expire(store, name, options.attrs);
427
- for (let index = 1; index <= count; index++) expire(store, chunkName(name, index), options.attrs);
428
- }
429
- function setCookieValues(setCookies) {
430
- const cookies = Object.create(null);
431
- for (const setCookie of setCookies) {
432
- const pair = setCookiePair(setCookie);
433
- if (pair) cookies[pair.name] = decodeValue(pair.value);
434
- }
435
- return cookies;
436
- }
437
- function setCookieName(setCookie) {
438
- return setCookiePair(setCookie)?.name;
439
- }
440
- function mirrorSetCookie(name, headers) {
441
- const { from, to } = headers;
442
- const isThisCookie = (line) => setCookieName(line) === name;
443
- const lines = [...to.getSetCookie().filter((line) => !isThisCookie(line)), ...from.getSetCookie().filter((line) => isThisCookie(line))];
444
- to.delete("set-cookie");
445
- for (const line of lines) to.append("set-cookie", line);
446
- }
447
- function chunkName(name, index) {
448
- return `${name}.${index}`;
449
- }
450
- function parseChunkCount(main) {
451
- if (main === void 0 || !main.startsWith("__chunked__")) return 0;
452
- const suffix = main.slice(11);
453
- if (!/^\d+$/.test(suffix)) return 0;
454
- const count = Number.parseInt(suffix, 10);
455
- if (count < 1 || count > 16) return 0;
456
- return count;
457
- }
458
- function expire(store, name, attrs) {
459
- store.write(name, "", {
460
- ...attrs,
461
- maxAge: 0,
462
- expires: /* @__PURE__ */ new Date(0)
463
- });
464
- }
465
- function setCookiePair(setCookie) {
466
- const [pair = ""] = setCookie.split(";", 1);
467
- const separator = pair.indexOf("=");
468
- if (separator === -1) return void 0;
469
- return {
470
- name: pair.slice(0, separator).trim(),
471
- value: pair.slice(separator + 1).trim()
472
- };
473
- }
474
- function decodeValue(value) {
475
- if (!value.includes("%")) return value;
476
- try {
477
- return decodeURIComponent(value);
478
- } catch {
479
- return value;
480
- }
481
- }
482
- function resolveTransport(driver, event, options) {
483
- const handed = driver.handed?.(event);
484
- if (handed) return {
485
- source: "handed",
486
- read: () => handed.token,
487
- readonly: false,
488
- writable: () => true
489
- };
490
- const { config, name } = options;
491
- const header = presentedHeader(driver, event, options);
492
- if (header || config.cookie === false) return {
493
- source: "header",
494
- read: config.sessionHeader === false ? void 0 : () => header?.token,
495
- malformed: header?.malformed,
496
- readonly: false,
497
- writable: () => true
498
- };
499
- const jar = driver.cookies(event);
500
- const store = responseStore(jar);
501
- const transport = {
502
- source: "cookie",
503
- read: () => cookieToken(jar, name),
504
- readonly: !store,
505
- writable: () => driver.writable?.(event) ?? true
506
- };
507
- if (!store) return transport;
508
- transport.write = (token, { attrs, chunkMaxLength }) => {
509
- writeChunked(store, name, token, {
510
- attrs,
511
- chunkMaxLength,
512
- prefix: driver.prefix
513
- });
514
- };
515
- transport.drop = ({ attrs }) => {
516
- removeChunked(store, name, { attrs });
517
- };
518
- return transport;
519
- }
520
- function assertTokenSource(transport, options) {
521
- if (!transport.read) throw invalidConfigError(`${options.prefix} Session has no token source, enable "cookie" or "sessionHeader".`);
522
- }
523
- function assertWritable(transport, options) {
524
- const { operation, prefix } = options;
525
- if (operation === "update" && transport.readonly) throw readOnlyEventError(`${prefix} Cannot update session on read-only event.`);
526
- if (!transport.writable()) throw readOnlyEventError(`${prefix} Cannot update/clear session: the response was already sent.`);
527
- }
528
- function assertSessionHeader(header, options) {
529
- if (header === void 0 || typeof header === "boolean") return;
530
- if (isToken(header)) return;
531
- throw invalidConfigError(`${options.prefix} Session "sessionHeader" must be true, false or a header name (got ${quoteTokenValue(header)}).`);
532
- }
533
- function sessionHeaderName(sessionName, configured) {
534
- if (typeof configured === "string") return configured.toLowerCase();
535
- return `x-${sessionName.replace(/^__(?:host|secure)-/i, "").toLowerCase()}-session`;
536
- }
537
- function headerToken(value) {
538
- if (typeof value !== "string") return void 0;
539
- const words = value.trim().split(/\s+/);
540
- const [first = "", second] = words;
541
- if (/^bearer$/i.test(first)) return words.length === 2 ? second : void 0;
542
- return words.length === 1 && first ? first : void 0;
543
- }
544
- function presentedHeader(driver, event, options) {
545
- const { config, name } = options;
546
- if (config.sessionHeader === false) return void 0;
547
- const header = sessionHeaderName(name, config.sessionHeader);
548
- const value = driver.header(event, header);
549
- if (typeof value !== "string") return void 0;
550
- const token = headerToken(value);
551
- if (token) return { token };
552
- return {
553
- token: void 0,
554
- malformed: invalidTokenError(`${driver.prefix} Invalid session token: the "${header}" header holds neither a token nor "Bearer <token>".`)
555
- };
556
- }
557
- function cookieToken(jar, name) {
558
- const written = jar.written ? readChunked({ read: (key) => jar.written?.(key) }, name) : void 0;
559
- if (written === "") return void 0;
560
- return (written ?? readChunked(jar, name)) || void 0;
561
- }
562
- function responseStore(jar) {
563
- const write = jar.write?.bind(jar);
564
- if (!write) return void 0;
565
- return {
566
- read: (name) => jar.written?.(name) ?? jar.read(name),
567
- write
568
- };
569
- }
570
- const kSessionNames = Symbol("unjwt_session_names");
571
- function bindSession(event, config, options) {
572
- const { driver, codec, actions, hookArgs } = options;
573
- const { prefix } = driver;
574
- const name = config.name || codec.defaultName;
575
- assertSessionName(name, { prefix });
576
- assertSessionHeader(config.sessionHeader, { prefix });
577
- if (config.cookie !== false) assertCookiePrefix(name, {
578
- ...codec.defaultCookie,
579
- ...config.cookie
580
- }, { prefix });
581
- assertRolling(config, { prefix });
582
- const record = driver.state(event);
583
- claimSessionName(record, {
584
- name,
585
- family: codec.family,
586
- prefix
587
- });
588
- return {
589
- event,
590
- config,
591
- driver,
592
- codec,
593
- actions,
594
- name,
595
- args: hookArgs(event),
596
- store: recordSlotStore(record, `${codec.family}:${name}`),
597
- transport: resolveTransport(driver, event, {
598
- config,
599
- name
600
- })
601
- };
602
- }
603
- function sessionToken(binding) {
604
- const { transport } = binding;
605
- assertTokenSource(transport, { prefix: binding.driver.prefix });
606
- const session = storedSession(binding.store);
607
- return session ? session.token : transport.read();
608
- }
609
- function recordSlotStore(record, slot) {
610
- return {
611
- get: () => record[slot],
612
- set: (state) => {
613
- record[slot] = state;
614
- }
615
- };
616
- }
617
- function sessionCookie(binding, options = {}) {
618
- const { chunkMaxLength, ...configured } = binding.config.cookie || {};
619
- const attrs = {
620
- ...binding.codec.defaultCookie,
621
- ...configured
622
- };
623
- if (options.expiresAt !== void 0) {
624
- attrs.expires = new Date(options.expiresAt);
625
- attrs.maxAge = void 0;
626
- }
627
- return {
628
- attrs,
629
- chunkMaxLength
630
- };
631
- }
632
- function dropSessionCookie(binding) {
633
- const { transport } = binding;
634
- if (!transport.drop || !transport.writable()) return;
635
- transport.drop(sessionCookie(binding));
636
- }
637
- function claimSessionName(record, options) {
638
- const { name, family, prefix } = options;
639
- const holder = record;
640
- const registry = holder[kSessionNames] ||= /* @__PURE__ */ new Map();
641
- const held = registry.get(name);
642
- if (held === void 0) {
643
- registry.set(name, family);
644
- return;
645
- }
646
- if (held === family) return;
647
- throw invalidConfigError(`${prefix} Session name "${name}" is already used by a ${held.toUpperCase()} session on this request.`);
648
- }
649
- async function loadSession(binding) {
650
- const { config, codec, store, args, transport } = binding;
651
- await codec.assertKeys(config);
652
- const open = codec.opener(config, args);
653
- assertTokenSource(transport, { prefix: binding.driver.prefix });
654
- const existing = storedSession(store);
655
- if (existing) {
656
- const session = existing[kSessionPending] ? await existing[kSessionPending] : existing;
657
- if (session.token !== void 0 && session.expiresAt !== void 0 && session.expiresAt < codec.now(config)) {
658
- dropSessionCookie(binding);
659
- const freshSession = emptySession(binding);
660
- store.set(freshSession);
661
- await fireExpire(binding, {
662
- session: {
663
- id: session.id,
664
- createdAt: session.createdAt,
665
- expiresAt: session.expiresAt,
666
- token: session.token
667
- },
668
- error: expiredSessionError({
669
- id: session.id,
670
- createdAt: session.createdAt,
671
- expiresAt: session.expiresAt
672
- })
673
- });
674
- return freshSession;
675
- }
676
- return session;
677
- }
678
- const session = emptySession(binding);
679
- store.set(session);
680
- if (transport.malformed) {
681
- await fireError(binding, transport.malformed);
682
- return session;
683
- }
684
- const token = transport.read();
685
- if (!token) return session;
686
- session.token = token;
687
- const pending = open(token).then((opened) => {
688
- Object.assign(session, opened);
689
- return { failure: void 0 };
690
- }, (error) => {
691
- session.token = void 0;
692
- return { failure: { error } };
693
- }).then((outcome) => {
694
- delete session[kSessionPending];
695
- return outcome;
696
- });
697
- session[kSessionPending] = pending.then(() => session);
698
- const { failure } = await pending;
699
- if (failure) {
700
- if (isJWTError(failure.error, "ERR_JWT_EXPIRED")) {
701
- const { jti, iat, exp } = failure.error.cause;
702
- dropSessionCookie(binding);
703
- await fireExpire(binding, {
704
- session: {
705
- id: jti,
706
- createdAt: iat ? iat * 1e3 : void 0,
707
- expiresAt: exp ? exp * 1e3 : void 0,
708
- token
709
- },
710
- error: failure.error
711
- });
712
- } else {
713
- if (isJWTError(failure.error, "ERR_SESSION_TOKEN_INVALID") && isTokenFailure(failure.error)) dropSessionCookie(binding);
714
- await fireError(binding, failure.error);
715
- }
716
- return session;
717
- }
718
- await fireRead(binding, session);
719
- await rollSession(binding, session);
720
- return session;
721
- }
722
- async function fireRead(binding, session) {
723
- if (session.id === void 0 || session.token === void 0) return;
724
- await binding.config.hooks?.onRead?.({
725
- session: binding.actions.manager(binding),
726
- ...binding.args,
727
- config: binding.config
728
- });
729
- }
730
- async function rollSession(binding, session) {
731
- const { config, codec, transport, store } = binding;
732
- const { rolling } = config;
733
- if (rolling === void 0 || store.get() !== session) return;
734
- if (session.token === void 0 || session.expiresAt === void 0) return;
735
- if (rolling !== true) {
736
- if (codec.now(config) - session.createdAt < computeDurationInSeconds(rolling) * 1e3) return;
737
- }
738
- if (transport.readonly || !transport.writable()) return;
739
- await binding.actions.roll(binding);
740
- }
741
- async function fireError(binding, error) {
742
- await binding.config.hooks?.onError?.({
743
- session: binding.actions.manager(binding),
744
- ...binding.args,
745
- error,
746
- config: binding.config
747
- });
748
- }
749
- async function fireExpire(binding, expired) {
750
- await binding.config.hooks?.onExpire?.({
751
- session: expired.session,
752
- ...binding.args,
753
- error: expired.error,
754
- config: binding.config
755
- });
756
- }
757
- async function encodeSession(binding) {
758
- const { config, codec, store, transport } = binding;
759
- await codec.assertKeys(config);
760
- const issue = codec.issuer(config);
761
- const session = storedSession(store) || (transport.read ? await loadSession(binding) : emptySession(binding));
762
- return issue(session.id === void 0 ? {
763
- ...session,
764
- id: mintSessionId(config)
765
- } : session);
766
- }
767
- async function issueSession(binding, update, options, trigger = "update") {
768
- const { config, driver, store, args, transport } = binding;
769
- assertWritable(transport, {
770
- operation: "update",
771
- prefix: driver.prefix
772
- });
773
- const session = storedSession(store) || await loadSession(binding);
774
- const oldSession = snapshotSession(session);
775
- const resolved = typeof update === "function" ? update(session.data) : update;
776
- if (resolved) Object.assign(session.data, sanitizeSessionUpdate(resolved, { prefix: driver.prefix }));
777
- for (const claim of RESERVED_CLAIMS) delete session.data[claim];
778
- const maxAge = options?.maxAge ?? config.maxAge;
779
- const createdAt = sessionInstant(binding);
780
- const id = mintSessionId(config);
781
- Object.assign(session, {
782
- id,
783
- createdAt,
784
- expiresAt: resolveExpiresAt(createdAt, { maxAge })
785
- });
786
- let issued;
787
- try {
788
- const token = await encodeSession(binding);
789
- transport.write?.(token, sessionCookie(binding, { expiresAt: session.expiresAt }));
790
- issued = Object.assign(session, {
791
- id,
792
- token
793
- });
794
- } catch (error_) {
795
- Object.assign(session, {
796
- id: oldSession.id,
797
- createdAt: oldSession.createdAt,
798
- expiresAt: oldSession.expiresAt,
799
- data: oldSession.data
800
- });
801
- session.token = oldSession.token;
802
- await config.hooks?.onError?.({
803
- session: binding.actions.manager(binding),
804
- ...args,
805
- error: error_,
806
- config
807
- });
808
- throw error_;
809
- }
810
- await config.hooks?.onUpdate?.({
811
- session: binding.actions.manager(binding),
812
- oldSession,
813
- trigger,
814
- ...args,
815
- config
816
- });
817
- return issued;
818
- }
819
- async function dropSession(binding) {
820
- const { config, driver, store, args, transport } = binding;
821
- assertTokenSource(transport, { prefix: driver.prefix });
822
- assertWritable(transport, {
823
- operation: "clear",
824
- prefix: driver.prefix
825
- });
826
- let session = storedSession(store);
827
- if (session && session[kSessionPending]) session = await session[kSessionPending];
828
- const oldSession = session && (session.id !== void 0 || session.token !== void 0) ? snapshotSession(session) : void 0;
829
- store.set(emptySession(binding));
830
- transport.drop?.(sessionCookie(binding));
831
- await config.hooks?.onClear?.({
832
- oldSession,
833
- ...args,
834
- config
835
- });
836
- }
837
- function sessionManager(binding) {
838
- const { store } = binding;
839
- const state = () => storedSession(store);
840
- const manager = {
841
- get id() {
842
- return state()?.id;
843
- },
844
- get createdAt() {
845
- return state()?.createdAt ?? Date.now();
846
- },
847
- get expiresAt() {
848
- return (state() ?? blankSession()).expiresAt;
849
- },
850
- get data() {
851
- return (state() ?? blankSession()).data;
852
- },
853
- get token() {
854
- return sessionToken(binding);
855
- },
856
- update: async (update, options) => {
857
- await issueSession(binding, update, options);
858
- return manager;
859
- },
860
- clear: async () => {
861
- await dropSession(binding);
862
- return manager;
863
- }
864
- };
865
- return manager;
866
- }
867
- function sessionActions() {
868
- return {
869
- manager: (binding) => sessionManager(binding),
870
- roll: async (binding) => {
871
- await issueSession(binding, void 0, void 0, "rolling");
872
- }
873
- };
874
- }
875
- function snapshotSession(session) {
876
- return {
877
- ...session,
878
- data: safeJsonParse(JSON.stringify(session.data))
879
- };
880
- }
881
- function defineSession(config, options) {
882
- const { driver, codec, hookArgs } = options;
883
- const actions = sessionActions();
884
- const bind = (event) => bindSession(event, config, {
885
- driver,
886
- codec,
887
- actions,
888
- hookArgs
889
- });
890
- return {
891
- use: async (event) => {
892
- const binding = bind(event);
893
- await loadSession(binding);
894
- return sessionManager(binding);
895
- },
896
- token: (event) => sessionToken(bind(event)),
897
- issue: async (data, issueOptions) => {
898
- await codec.assertKeys(config);
899
- const issue = codec.issuer(config);
900
- const createdAt = sessionInstant({
901
- codec,
902
- config
903
- });
904
- return issue({
905
- id: mintSessionId(config),
906
- createdAt,
907
- expiresAt: resolveExpiresAt(createdAt, { maxAge: issueOptions?.maxAge ?? config.maxAge }),
908
- data: sanitizeSessionUpdate(data ?? {}, { prefix: driver.prefix }),
909
- token: void 0
910
- });
911
- },
912
- open: (token) => openSessionToken(codec, config, {
913
- token,
914
- args: {}
915
- })
916
- };
917
- }
918
- const JWS_DEFAULT_COOKIE = {
919
- path: "/",
920
- secure: true,
921
- httpOnly: false,
922
- sameSite: "lax"
923
- };
924
- function createJWSCodec(options) {
925
- const { prefix, defaultName } = options;
926
- return createSessionCodec({
927
- family: "jws",
928
- prefix,
929
- defaultName,
930
- defaultCookie: JWS_DEFAULT_COOKIE,
931
- issueOptions: (config) => config.jws?.signOptions,
932
- openOptions: (config) => config.jws?.verifyOptions,
933
- issueKey: (config) => getSignKey(config, prefix),
934
- openKey: (config) => getVerifyKey(config, prefix),
935
- lookup: (config, args) => {
936
- const hooks = config.hooks;
937
- if (!hooks?.onVerifyKeyLookup) return void 0;
938
- return (header) => hooks.onVerifyKeyLookup?.({
939
- header,
940
- ...args,
941
- config
942
- });
943
- },
944
- importable: (key) => isJWKSet(key) ? key.keys : [key],
945
- issue: (config, payload, key) => sign(payload, key, sessionIssueOptions(config.jws?.signOptions)),
946
- open: async (config, token, key) => {
947
- const { verifyOptions, signOptions } = config.jws ?? {};
948
- const { payload } = await verify(token, key, {
949
- ...verifyOptions,
950
- ...sessionOpenOptions({
951
- maxAge: config.maxAge,
952
- requiredClaims: verifyOptions?.requiredClaims,
953
- typ: signOptions?.protectedHeader?.typ,
954
- alg: signOptions?.alg
955
- })
956
- });
957
- return payload;
958
- }
959
- });
960
- }
961
- function getSignKey(config, prefix) {
962
- const key = config.key;
963
- assertKeyMaterial(key, { prefix });
964
- let resolved;
965
- if (isCryptoKey(key) || isSymmetricJWK(key)) resolved = key;
966
- else if (isKeyPair(key)) {
967
- assertJWSKeyPair(key, prefix);
968
- resolved = key.privateKey;
969
- }
970
- if (!resolved) throw invalidKeyError(`${prefix} Invalid JWS key, expected a symmetric JWK, a private JWK or a CryptoKey.`, key);
971
- assertPinnedAlg(resolved, config, prefix);
972
- return resolved;
973
- }
974
- function getVerifyKey(config, prefix) {
975
- const key = config.key;
976
- assertKeyMaterial(key, { prefix });
977
- let resolved;
978
- if (isCryptoKey(key) || isSymmetricJWK(key)) resolved = key;
979
- else if (isKeyPair(key)) {
980
- assertJWSKeyPair(key, prefix);
981
- resolved = Array.isArray(key.publicKey) ? { keys: key.publicKey } : key.publicKey;
982
- }
983
- if (!resolved) throw invalidKeyError(`${prefix} Invalid JWS key, expected a symmetric JWK, a public JWK/set or a CryptoKey.`, key);
984
- assertPinnedAlg(resolved, config, prefix);
985
- return resolved;
986
- }
987
- function assertJWSKeyPair(key, prefix) {
988
- assertKeyPair(key, {
989
- prefix,
990
- family: "JWS",
991
- isPublic: (half) => isCryptoKey(half) || isPublicJWK(half) || isPublicKeyList(Array.isArray(half) ? half : isJWKSet(half) ? half.keys : void 0),
992
- publicHalf: "a public JWK, a non-empty JWK set or a CryptoKey"
993
- });
994
- }
995
- function isPublicKeyList(keys) {
996
- return keys !== void 0 && keys.length > 0 && keys.every((candidate) => isPublicJWK(candidate));
997
- }
998
- function assertPinnedAlg(key, config, prefix) {
999
- assertPinnedCryptoKey(key, {
1000
- pinned: Boolean(config.jws?.signOptions?.alg),
1001
- prefix,
1002
- hint: "\"jws.signOptions.alg\""
1003
- });
1004
- }
1005
- export { bindSession, createJWECodec, createJWSCodec, defineSession, dropSession, encodeSession, invalidConfigError, issueSession, loadSession, mirrorSetCookie, openSessionToken, sessionActions, setCookieName, setCookieValues };