unjwt 0.7.3 → 0.8.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 (39) hide show
  1. package/README.md +4 -4
  2. package/dist/THIRD-PARTY-LICENSES.md.gz +0 -0
  3. package/dist/_chunks/_codec-jwe.d.mts +170 -0
  4. package/dist/_chunks/_codec-jws.mjs +878 -0
  5. package/dist/_chunks/_internal.d.mts +7 -0
  6. package/dist/_chunks/_jwt-internal.mjs +388 -0
  7. package/dist/_chunks/algorithms.mjs +186 -0
  8. package/dist/_chunks/index.d.mts +500 -808
  9. package/dist/_chunks/index2.d.mts +11 -0
  10. package/dist/_chunks/index3.d.mts +108 -0
  11. package/dist/_chunks/jwe.d.mts +43 -66
  12. package/dist/_chunks/jwe.mjs +384 -252
  13. package/dist/_chunks/jwk.mjs +1371 -607
  14. package/dist/_chunks/jws.d.mts +48 -103
  15. package/dist/_chunks/jws.mjs +372 -184
  16. package/dist/_chunks/jwt.d.mts +56 -0
  17. package/dist/_chunks/libs/unsecure.d.mts +164 -85
  18. package/dist/_chunks/libs/unsecure.mjs +390 -123
  19. package/dist/adapters/elysia/index.d.mts +33 -171
  20. package/dist/adapters/elysia/index.mjs +106 -575
  21. package/dist/adapters/h3v1/index.d.mts +59 -344
  22. package/dist/adapters/h3v1/index.mjs +114 -619
  23. package/dist/adapters/h3v2/index.d.mts +58 -326
  24. package/dist/adapters/h3v2/index.mjs +108 -611
  25. package/dist/core/internal.d.mts +2 -0
  26. package/dist/core/internal.mjs +1 -0
  27. package/dist/core/jwe.d.mts +4 -2
  28. package/dist/core/jwe.mjs +4 -2
  29. package/dist/core/jwk.d.mts +183 -371
  30. package/dist/core/jwk.mjs +4 -3
  31. package/dist/core/jws.d.mts +4 -2
  32. package/dist/core/jws.mjs +4 -2
  33. package/dist/core/utils/index.d.mts +5 -158
  34. package/dist/core/utils/index.mjs +12 -3
  35. package/dist/index.d.mts +8 -4
  36. package/dist/index.mjs +6 -3
  37. package/package.json +30 -30
  38. package/dist/_chunks/_internal.mjs +0 -88
  39. package/dist/_chunks/utils.mjs +0 -392
@@ -1,608 +1,139 @@
1
- import { computeDurationInSeconds, isJWTError, isPrivateJWK, isPublicJWK, isSymmetricJWK } from "../../_chunks/utils.mjs";
2
- import { sanitizeObjectCopy } from "../../_chunks/libs/unsecure.mjs";
3
- import { deriveJWKFromPassword, exportPEM, generateJWK, importPEM } from "../../_chunks/jwk.mjs";
4
- import { sign, verify } from "../../_chunks/jws.mjs";
5
- import { decrypt, encrypt } from "../../_chunks/jwe.mjs";
1
+ import { deriveJWKFromPassword, exportPEM, generateJWK, importPEM, thumbprint } from "../../_chunks/jwk.mjs";
2
+ import { bindSession, createJWECodec, createJWSCodec, invalidConfigError, loadSession, readChunked, recordSlotStore, removeChunked, resolveSessionToken, sessionActions, sessionManager, writeChunked } from "../../_chunks/_codec-jws.mjs";
6
3
  import { Elysia } from "elysia";
7
- function guardName(contextKey) {
8
- return `require${contextKey.charAt(0).toUpperCase()}${contextKey.slice(1)}`;
9
- }
10
- const CHUNK_MARKER = "__chunked__";
11
- const DEFAULT_CHUNK_MAX_LENGTH = 4e3;
12
- const MAX_CHUNK_COUNT = 100;
13
- function readChunkedCookie(jar, name) {
14
- const main = asString(jar[name]?.value);
15
- if (main === void 0) return void 0;
16
- if (!main.startsWith(CHUNK_MARKER)) return main;
17
- const count = parseChunkCount(main);
18
- if (count === 0) return void 0;
19
- const parts = [];
20
- for (let i = 1; i <= count; i++) {
21
- const part = asString(jar[chunkName(name, i)]?.value);
22
- if (part === void 0) return void 0;
23
- parts.push(part);
24
- }
25
- return parts.join("");
26
- }
27
- function writeChunkedCookie(jar, name, value, options = {}) {
28
- const { chunkMaxLength, ...attrs } = options;
29
- const max = chunkMaxLength && chunkMaxLength > 0 ? chunkMaxLength : DEFAULT_CHUNK_MAX_LENGTH;
30
- const chunkCount = Math.ceil(value.length / max);
31
- if (chunkCount > MAX_CHUNK_COUNT) throw new Error(`[unjwt/elysia] Session token requires ${chunkCount} cookie chunks, exceeding the limit of ${MAX_CHUNK_COUNT}.`);
32
- const previousCount = parseChunkCount(asString(jar[name]?.value));
33
- const firstSurplus = chunkCount <= 1 ? 1 : chunkCount + 1;
34
- for (let i = firstSurplus; i <= previousCount; i++) expireCookie(jar, chunkName(name, i), attrs);
35
- if (chunkCount <= 1) {
36
- jar[name].set({
37
- ...attrs,
38
- value
39
- });
40
- return;
41
- }
42
- jar[name].set({
43
- ...attrs,
44
- value: `${CHUNK_MARKER}${chunkCount}`
45
- });
46
- for (let i = 1; i <= chunkCount; i++) {
47
- const slice = value.slice((i - 1) * max, i * max);
48
- jar[chunkName(name, i)].set({
49
- ...attrs,
50
- value: slice
51
- });
52
- }
53
- }
54
- function removeChunkedCookie(jar, name, attrs = {}) {
55
- const count = parseChunkCount(asString(jar[name]?.value));
56
- expireCookie(jar, name, attrs);
57
- for (let i = 1; i <= count; i++) expireCookie(jar, chunkName(name, i), attrs);
58
- }
59
- function expireCookie(jar, fullName, attrs) {
60
- jar[fullName].set({
61
- ...attrs,
62
- value: "",
63
- maxAge: 0,
64
- expires: /* @__PURE__ */ new Date(0)
65
- });
66
- }
67
- function chunkName(name, index) {
68
- return `${name}.${index}`;
69
- }
70
- function parseChunkCount(main) {
71
- if (main === void 0 || !main.startsWith(CHUNK_MARKER)) return 0;
72
- const suffix = main.slice(11);
73
- if (!/^\d+$/.test(suffix)) return 0;
74
- const count = Number.parseInt(suffix, 10);
75
- if (count < 1 || count > MAX_CHUNK_COUNT) return 0;
76
- return count;
77
- }
78
- function asString(value) {
79
- return typeof value === "string" ? value : void 0;
80
- }
81
- const DEFAULT_NAME$1 = "elysia-jws";
82
- const DEFAULT_COOKIE$1 = {
83
- path: "/",
84
- secure: true,
85
- httpOnly: false
86
- };
87
- async function createJWSSession(context, config) {
88
- const state = emptySession$1(config);
89
- await initFromToken$1(context, config, state);
90
- return buildManager$1(context, config, state);
91
- }
92
- function jwsSession(config) {
93
- const contextKey = config.contextKey ?? "session";
4
+ function sessionPlugin(options) {
5
+ const contextKey = options.contextKey ?? "session";
6
+ const seed = `${options.sessionName}:${contextKey}`;
94
7
  return new Elysia({
95
- name: "unjwt/elysia-jws",
96
- seed: contextKey
8
+ name: options.name,
9
+ seed
97
10
  }).resolve({ as: "scoped" }, async ({ cookie, request }) => {
98
- const session = await createJWSSession({
11
+ const session = await options.create({
99
12
  cookie,
100
13
  request
101
- }, config);
14
+ });
102
15
  return { [contextKey]: session };
103
16
  }).macro({ [guardName(contextKey)]: { resolve(ctx) {
104
- if (!ctx[contextKey]?.id) return ctx.status(401, "Unauthorized");
17
+ if (!hasSessionId(ctx[contextKey])) return ctx.status(401, "Unauthorized");
105
18
  return {};
106
19
  } } });
107
20
  }
108
- function buildManager$1(context, config, state) {
109
- const manager = {
110
- get id() {
111
- return state.id;
112
- },
113
- get createdAt() {
114
- return state.createdAt;
115
- },
116
- get expiresAt() {
117
- return state.expiresAt;
118
- },
119
- get data() {
120
- return state.data;
121
- },
122
- get token() {
123
- return state.token;
124
- },
125
- update: async (update) => {
126
- await updateSession$1(context, config, state, update);
127
- return manager;
128
- },
129
- clear: async () => {
130
- await clearSession$1(context, config, state);
131
- return manager;
132
- }
133
- };
134
- return manager;
135
- }
136
- async function initFromToken$1(context, config, state) {
137
- const token = readToken$1(context, config);
138
- if (!token) return;
139
- state.token = token;
140
- let exclusiveHookFired = false;
141
- try {
142
- const unsealed = await verifySession(context, config, token);
143
- Object.assign(state, unsealed);
144
- } catch (error_) {
145
- exclusiveHookFired = true;
146
- if (isJWTError(error_, "ERR_JWT_EXPIRED")) await config.hooks?.onExpire?.({
147
- session: {
148
- id: error_.cause.jti,
149
- createdAt: error_.cause.iat ? error_.cause.iat * 1e3 : void 0,
150
- expiresAt: error_.cause.exp ? error_.cause.exp * 1e3 : void 0,
151
- token
152
- },
153
- context,
154
- error: error_,
155
- config
156
- });
157
- else await config.hooks?.onError?.({
158
- session: state,
159
- context,
160
- error: error_,
161
- config
162
- });
163
- }
164
- if (!exclusiveHookFired && state.id !== void 0 && state.token !== void 0) await config.hooks?.onRead?.({
165
- session: state,
166
- context,
167
- config
168
- });
21
+ function guardName(contextKey) {
22
+ return `require${contextKey.charAt(0).toUpperCase()}${contextKey.slice(1)}`;
169
23
  }
170
- async function updateSession$1(context, config, state, update) {
171
- const oldSession = {
172
- ...state,
173
- data: sanitizeObjectCopy(state.data)
174
- };
175
- const resolvedUpdate = typeof update === "function" ? update(state.data) : update;
176
- if (resolvedUpdate) Object.assign(state.data, resolvedUpdate);
177
- const now = config.jws?.signOptions?.currentDate?.getTime() ?? Date.now();
178
- const createdAt = now - now % 1e3;
179
- state.id = config.generateId?.() || crypto.randomUUID();
180
- state.createdAt = createdAt;
181
- state.expiresAt = config.maxAge === void 0 ? void 0 : createdAt + computeDurationInSeconds(config.maxAge) * 1e3;
182
- let token;
183
- try {
184
- token = await signSession(config, state);
185
- } catch (error_) {
186
- state.id = oldSession.id;
187
- state.createdAt = oldSession.createdAt;
188
- state.expiresAt = oldSession.expiresAt;
189
- state.data = oldSession.data;
190
- state.token = oldSession.token;
191
- await config.hooks?.onError?.({
192
- session: state,
193
- context,
194
- error: error_,
195
- config
196
- });
197
- throw error_;
198
- }
199
- state.token = token;
200
- if (config.cookie !== false) {
201
- const { chunkMaxLength, ...attrs } = {
202
- ...DEFAULT_COOKIE$1,
203
- ...config.cookie
204
- };
205
- writeChunkedCookie(context.cookie, config.name || DEFAULT_NAME$1, token, {
206
- ...attrs,
207
- chunkMaxLength,
208
- expires: config.maxAge === void 0 ? void 0 : new Date(createdAt + computeDurationInSeconds(config.maxAge) * 1e3)
209
- });
210
- }
211
- await config.hooks?.onUpdate?.({
212
- session: state,
213
- oldSession,
214
- context,
215
- config
216
- });
24
+ function hasSessionId(session) {
25
+ return typeof session === "object" && session !== null && "id" in session && Boolean(session.id);
217
26
  }
218
- async function clearSession$1(context, config, state) {
219
- const oldSession = state.id !== void 0 || state.token !== void 0 ? {
220
- ...state,
221
- data: sanitizeObjectCopy(state.data)
222
- } : void 0;
223
- const fresh = emptySession$1(config);
224
- state.id = fresh.id;
225
- state.createdAt = fresh.createdAt;
226
- state.expiresAt = fresh.expiresAt;
227
- state.data = fresh.data;
228
- state.token = fresh.token;
229
- if (config.cookie !== false) {
230
- const { chunkMaxLength: _chunkMaxLength, ...attrs } = {
231
- ...DEFAULT_COOKIE$1,
232
- ...config.cookie
233
- };
234
- removeChunkedCookie(context.cookie, config.name || DEFAULT_NAME$1, attrs);
235
- }
236
- await config.hooks?.onClear?.({
237
- oldSession,
238
- context,
239
- config
240
- });
27
+ const PREFIX$1 = "[unjwt/elysia]";
28
+ function readChunkedCookie(jar, name) {
29
+ return readChunked(cookieStore(jar), name);
241
30
  }
242
- async function signSession(config, state) {
243
- const key = getSignKey(config.key);
244
- const iat = Math.floor(state.createdAt / 1e3);
245
- const exp = state.expiresAt ? Math.floor(state.expiresAt / 1e3) : void 0;
246
- const payload = {
247
- ...state.data,
248
- jti: state.id,
249
- iat
250
- };
251
- if (exp) payload.exp = exp;
252
- return sign(payload, key, {
253
- ...config.jws?.signOptions,
254
- expiresIn: void 0,
255
- protectedHeader: {
256
- ...config.jws?.signOptions?.protectedHeader,
257
- kid: "kid" in key ? key.kid : void 0,
258
- typ: resolveTyp$1(config) || "JWT",
259
- cty: "application/json"
260
- }
31
+ function writeChunkedCookie(jar, name, value, options = {}) {
32
+ const { chunkMaxLength, ...attrs } = options;
33
+ writeChunked(cookieStore(jar), name, value, {
34
+ attrs,
35
+ chunkMaxLength,
36
+ prefix: PREFIX$1
261
37
  });
262
38
  }
263
- async function verifySession(context, config, token) {
264
- const alg = config.jws?.signOptions?.alg;
265
- const { payload } = await verify(token, config.hooks?.onVerifyKeyLookup ? (header) => config.hooks.onVerifyKeyLookup({
266
- header,
267
- context,
268
- config
269
- }) : getVerifyKey(config.key), {
270
- ...config.jws?.verifyOptions,
271
- requiredClaims: [.../* @__PURE__ */ new Set([
272
- ...config.jws?.verifyOptions?.requiredClaims || [],
273
- "jti",
274
- "iat"
275
- ])],
276
- typ: resolveTyp$1(config) || "JWT",
277
- algorithms: alg ? [alg] : void 0,
278
- forceUint8Array: false,
279
- validateClaims: true
280
- }).catch((error_) => {
281
- if (isJWTError(error_, "ERR_JWT_EXPIRED")) throw error_;
282
- const message = error_ instanceof Error ? error_.message : String(error_);
283
- throw new Error(`Invalid session token: ${message}`);
284
- });
285
- const { jti, iat, exp, ...data } = payload;
286
- return {
287
- id: jti,
288
- createdAt: iat * 1e3,
289
- expiresAt: exp ? exp * 1e3 : void 0,
290
- data: data && typeof data === "object" ? data : emptyData$1()
291
- };
39
+ function removeChunkedCookie(jar, name, attrs = {}) {
40
+ removeChunked(cookieStore(jar), name, { attrs });
292
41
  }
293
- function emptySession$1(config) {
294
- const now = config.jws?.signOptions?.currentDate?.getTime() ?? Date.now();
295
- const createdAt = now - now % 1e3;
42
+ function cookieStore(jar) {
296
43
  return {
297
- id: void 0,
298
- createdAt,
299
- expiresAt: config.maxAge === void 0 ? void 0 : createdAt + computeDurationInSeconds(config.maxAge) * 1e3,
300
- data: emptyData$1(),
301
- token: void 0
302
- };
303
- }
304
- function emptyData$1() {
305
- return Object.create(null);
306
- }
307
- function readToken$1(context, config) {
308
- const sessionName = config.name || DEFAULT_NAME$1;
309
- if (config.sessionHeader !== false) {
310
- const headerName = typeof config.sessionHeader === "string" ? config.sessionHeader.toLowerCase() : `x-${sessionName.toLowerCase()}-session`;
311
- const headerValue = context.request.headers.get(headerName);
312
- if (typeof headerValue === "string") return headerValue.startsWith("Bearer ") ? headerValue.slice(7).trim() : headerValue;
313
- }
314
- if (config.cookie !== false) return readChunkedCookie(context.cookie, sessionName);
315
- }
316
- function resolveTyp$1(config) {
317
- const typ = config.jws?.signOptions?.protectedHeader?.typ;
318
- if (typeof typ === "string" && typ.toLowerCase().includes("jwt")) return typ;
319
- }
320
- function getSignKey(key) {
321
- if (!key) throw new Error("[unjwt/elysia] JWS key is required.");
322
- let _key;
323
- if (isSymmetricJWK(key)) _key = key;
324
- else if ("privateKey" in key && isPrivateJWK(key.privateKey)) _key = key.privateKey;
325
- if (!_key) throw new Error("[unjwt/elysia] Invalid JWS key. It must be a symmetric JWK or a private JWK.", { cause: key });
326
- return _key;
327
- }
328
- function getVerifyKey(key) {
329
- if (!key) throw new Error("[unjwt/elysia] JWS key is required.");
330
- let _key;
331
- if (isSymmetricJWK(key)) _key = key;
332
- else if ("publicKey" in key) {
333
- const publicKey = key.publicKey;
334
- if (isPublicJWK(publicKey)) _key = publicKey;
335
- else if (Array.isArray(publicKey)) {
336
- const keys = publicKey.filter((candidate) => isPublicJWK(candidate));
337
- if (keys.length > 0) _key = { keys };
338
- } else if (publicKey && typeof publicKey === "object" && Array.isArray(publicKey.keys)) {
339
- const keys = publicKey.keys.filter((candidate) => isPublicJWK(candidate));
340
- if (keys.length > 0) _key = { keys };
341
- }
342
- }
343
- if (!_key) throw new Error("[unjwt/elysia] Invalid JWS key. It must be a symmetric JWK or a public JWK/set.", { cause: key });
344
- return _key;
345
- }
346
- const DEFAULT_NAME = "elysia-jwe";
347
- const DEFAULT_COOKIE = {
348
- path: "/",
349
- secure: true,
350
- httpOnly: true
351
- };
352
- async function createJWESession(context, config) {
353
- const state = emptySession(config);
354
- await initFromToken(context, config, state);
355
- return buildManager(context, config, state);
356
- }
357
- function jweSession(config) {
358
- const contextKey = config.contextKey ?? "session";
359
- return new Elysia({
360
- name: "unjwt/elysia-jwe",
361
- seed: contextKey
362
- }).resolve({ as: "scoped" }, async ({ cookie, request }) => {
363
- const session = await createJWESession({
364
- cookie,
365
- request
366
- }, config);
367
- return { [contextKey]: session };
368
- }).macro({ [guardName(contextKey)]: { resolve(ctx) {
369
- if (!ctx[contextKey]?.id) return ctx.status(401, "Unauthorized");
370
- return {};
371
- } } });
372
- }
373
- function buildManager(context, config, state) {
374
- const manager = {
375
- get id() {
376
- return state.id;
377
- },
378
- get createdAt() {
379
- return state.createdAt;
380
- },
381
- get expiresAt() {
382
- return state.expiresAt;
383
- },
384
- get data() {
385
- return state.data;
44
+ read: (name) => {
45
+ const value = jar[name]?.value;
46
+ return typeof value === "string" ? value : void 0;
386
47
  },
387
- get token() {
388
- return state.token;
389
- },
390
- update: async (update) => {
391
- await updateSession(context, config, state, update);
392
- return manager;
393
- },
394
- clear: async () => {
395
- await clearSession(context, config, state);
396
- return manager;
397
- }
48
+ write: (name, value, attrs) => reactiveCookie(jar, name).set({
49
+ ...attrs,
50
+ value
51
+ })
398
52
  };
399
- return manager;
400
- }
401
- async function initFromToken(context, config, state) {
402
- const token = readToken(context, config);
403
- if (!token) return;
404
- state.token = token;
405
- let exclusiveHookFired = false;
406
- try {
407
- const unsealed = await unsealSession(context, config, token);
408
- Object.assign(state, unsealed);
409
- } catch (error_) {
410
- exclusiveHookFired = true;
411
- if (isJWTError(error_, "ERR_JWT_EXPIRED")) await config.hooks?.onExpire?.({
412
- session: {
413
- id: error_.cause.jti,
414
- createdAt: error_.cause.iat ? error_.cause.iat * 1e3 : void 0,
415
- expiresAt: error_.cause.exp ? error_.cause.exp * 1e3 : void 0,
416
- token
417
- },
418
- context,
419
- error: error_,
420
- config
421
- });
422
- else await config.hooks?.onError?.({
423
- session: state,
424
- context,
425
- error: error_,
426
- config
427
- });
428
- }
429
- if (!exclusiveHookFired && state.id !== void 0 && state.token !== void 0) await config.hooks?.onRead?.({
430
- session: state,
431
- context,
432
- config
433
- });
434
53
  }
435
- async function updateSession(context, config, state, update) {
436
- const oldSession = {
437
- ...state,
438
- data: sanitizeObjectCopy(state.data)
439
- };
440
- const resolvedUpdate = typeof update === "function" ? update(state.data) : update;
441
- if (resolvedUpdate) Object.assign(state.data, resolvedUpdate);
442
- const now = config.jwe?.encryptOptions?.currentDate?.getTime() ?? Date.now();
443
- const createdAt = now - now % 1e3;
444
- state.id = config.generateId?.() || crypto.randomUUID();
445
- state.createdAt = createdAt;
446
- state.expiresAt = config.maxAge === void 0 ? void 0 : createdAt + computeDurationInSeconds(config.maxAge) * 1e3;
447
- let token;
448
- try {
449
- token = await sealSession(config, state);
450
- } catch (error_) {
451
- state.id = oldSession.id;
452
- state.createdAt = oldSession.createdAt;
453
- state.expiresAt = oldSession.expiresAt;
454
- state.data = oldSession.data;
455
- state.token = oldSession.token;
456
- await config.hooks?.onError?.({
457
- session: state,
458
- context,
459
- error: error_,
460
- config
461
- });
462
- throw error_;
463
- }
464
- state.token = token;
465
- if (config.cookie !== false) {
466
- const { chunkMaxLength, ...attrs } = {
467
- ...DEFAULT_COOKIE,
468
- ...config.cookie
469
- };
470
- writeChunkedCookie(context.cookie, config.name || DEFAULT_NAME, token, {
54
+ function reactiveCookie(jar, name) {
55
+ const cookie = jar[name];
56
+ if (!cookie || typeof cookie.set !== "function") throw invalidConfigError(`${PREFIX$1} Session cookie jar must expose a reactive cookie at "${name}" with a set() method — pass Elysia's own \`cookie\` context property.`);
57
+ return cookie;
58
+ }
59
+ const PREFIX = "[unjwt/elysia]";
60
+ const SESSIONS = /* @__PURE__ */ new WeakMap();
61
+ const elysiaSessionPort = {
62
+ prefix: PREFIX,
63
+ carrier: (context) => ({ context }),
64
+ store: (context, slot) => recordSlotStore(() => requestSessions(context.request), slot),
65
+ readToken: readSessionToken,
66
+ scope: (context) => context.request,
67
+ writable: () => true,
68
+ setCookie: (context, sessionName, token, { attrs, chunkMaxLength }) => {
69
+ writeChunkedCookie(context.cookie, sessionName, token, {
471
70
  ...attrs,
472
- chunkMaxLength,
473
- expires: config.maxAge === void 0 ? void 0 : new Date(createdAt + computeDurationInSeconds(config.maxAge) * 1e3)
71
+ chunkMaxLength
474
72
  });
73
+ },
74
+ deleteCookie: (context, sessionName, { attrs }) => {
75
+ removeChunkedCookie(context.cookie, sessionName, attrs);
475
76
  }
476
- await config.hooks?.onUpdate?.({
477
- session: state,
478
- oldSession,
479
- context,
480
- config
77
+ };
78
+ function readSessionToken(context, sessionName, source) {
79
+ return resolveSessionToken(source, sessionName, {
80
+ cookie: (name) => readChunkedCookie(context.cookie, name),
81
+ header: (name) => context.request.headers.get(name)
481
82
  });
482
83
  }
483
- async function clearSession(context, config, state) {
484
- const oldSession = state.id !== void 0 || state.token !== void 0 ? {
485
- ...state,
486
- data: sanitizeObjectCopy(state.data)
487
- } : void 0;
488
- const fresh = emptySession(config);
489
- state.id = fresh.id;
490
- state.createdAt = fresh.createdAt;
491
- state.expiresAt = fresh.expiresAt;
492
- state.data = fresh.data;
493
- state.token = fresh.token;
494
- if (config.cookie !== false) {
495
- const { chunkMaxLength: _chunkMaxLength, ...attrs } = {
496
- ...DEFAULT_COOKIE,
497
- ...config.cookie
498
- };
499
- removeChunkedCookie(context.cookie, config.name || DEFAULT_NAME, attrs);
500
- }
501
- await config.hooks?.onClear?.({
502
- oldSession,
503
- context,
504
- config
505
- });
84
+ function requestSessions(request) {
85
+ const existing = SESSIONS.get(request);
86
+ if (existing) return existing;
87
+ const created = Object.create(null);
88
+ SESSIONS.set(request, created);
89
+ return created;
90
+ }
91
+ const codec$1 = createJWSCodec({
92
+ prefix: PREFIX,
93
+ defaultName: "elysia-jws"
94
+ });
95
+ async function createJWSSession(context, config) {
96
+ const binding = bind$1(context, config);
97
+ await loadSession(binding);
98
+ return sessionManager(binding);
506
99
  }
507
- async function sealSession(config, state) {
508
- const key = getEncryptKey(config.key);
509
- const iat = Math.floor(state.createdAt / 1e3);
510
- const exp = state.expiresAt ? Math.floor(state.expiresAt / 1e3) : void 0;
511
- const payload = {
512
- ...state.data,
513
- jti: state.id,
514
- iat
515
- };
516
- if (exp) payload.exp = exp;
517
- return encrypt(payload, key, {
518
- ...config.jwe?.encryptOptions,
519
- expiresIn: void 0,
520
- protectedHeader: {
521
- ...config.jwe?.encryptOptions?.protectedHeader,
522
- typ: resolveTyp(config) || "JWT",
523
- cty: "application/json"
524
- }
100
+ function jwsSession(config) {
101
+ return sessionPlugin({
102
+ name: "unjwt/elysia-jws",
103
+ sessionName: config.name || codec$1.defaultName,
104
+ contextKey: config.contextKey,
105
+ create: (context) => createJWSSession(context, config)
525
106
  });
526
107
  }
527
- async function unsealSession(context, config, sealed) {
528
- const key = config.hooks?.onUnsealKeyLookup ? (header) => config.hooks.onUnsealKeyLookup({
529
- header,
530
- context,
531
- config
532
- }) : getDecryptKey(config.key);
533
- const alg = config.jwe?.encryptOptions?.alg;
534
- const enc = config.jwe?.encryptOptions?.enc;
535
- const { payload } = await decrypt(sealed, key, {
536
- ...config.jwe?.decryptOptions,
537
- requiredClaims: [.../* @__PURE__ */ new Set([
538
- ...config.jwe?.decryptOptions?.requiredClaims || [],
539
- "jti",
540
- "iat"
541
- ])],
542
- typ: resolveTyp(config) || "JWT",
543
- maxTokenAge: config.maxAge,
544
- algorithms: alg ? [alg] : void 0,
545
- encryptionAlgorithms: enc ? [enc] : void 0,
546
- unwrappedKeyAlgorithm: void 0,
547
- keyUsage: void 0,
548
- forceUint8Array: false,
549
- validateClaims: true
550
- }).catch((error_) => {
551
- if (error_ instanceof Error) throw error_;
552
- throw new Error(String(error_));
108
+ function bind$1(context, config) {
109
+ return bindSession(context, config, {
110
+ port: elysiaSessionPort,
111
+ codec: codec$1,
112
+ actions: sessionActions()
553
113
  });
554
- const { jti, iat, exp, ...data } = payload;
555
- return {
556
- id: jti,
557
- createdAt: iat * 1e3,
558
- expiresAt: exp ? exp * 1e3 : void 0,
559
- data: data && typeof data === "object" ? data : emptyData()
560
- };
561
- }
562
- function emptySession(config) {
563
- const now = config.jwe?.encryptOptions?.currentDate?.getTime() ?? Date.now();
564
- const createdAt = now - now % 1e3;
565
- return {
566
- id: void 0,
567
- createdAt,
568
- expiresAt: config.maxAge === void 0 ? void 0 : createdAt + computeDurationInSeconds(config.maxAge) * 1e3,
569
- data: emptyData(),
570
- token: void 0
571
- };
572
- }
573
- function emptyData() {
574
- return Object.create(null);
575
114
  }
576
- function readToken(context, config) {
577
- const sessionName = config.name || DEFAULT_NAME;
578
- if (config.sessionHeader !== false) {
579
- const headerName = typeof config.sessionHeader === "string" ? config.sessionHeader.toLowerCase() : `x-${sessionName.toLowerCase()}-session`;
580
- const headerValue = context.request.headers.get(headerName);
581
- if (typeof headerValue === "string") return headerValue.startsWith("Bearer ") ? headerValue.slice(7).trim() : headerValue;
582
- }
583
- if (config.cookie !== false) return readChunkedCookie(context.cookie, sessionName);
584
- }
585
- function resolveTyp(config) {
586
- const typ = config.jwe?.encryptOptions?.protectedHeader?.typ;
587
- if (typeof typ === "string" && typ.toLowerCase().includes("jwt")) return typ;
115
+ const codec = createJWECodec({
116
+ prefix: PREFIX,
117
+ defaultName: "elysia-jwe"
118
+ });
119
+ async function createJWESession(context, config) {
120
+ const binding = bind(context, config);
121
+ await loadSession(binding);
122
+ return sessionManager(binding);
588
123
  }
589
- function getEncryptKey(key) {
590
- if (!key) throw new Error("[unjwt/elysia] JWE key is required.");
591
- let _key;
592
- if (typeof key === "string") _key = key;
593
- else if (isSymmetricJWK(key)) _key = key;
594
- else if ("publicKey" in key && key.publicKey && isPublicJWK(key.publicKey)) _key = key.publicKey;
595
- else if ("privateKey" in key && isPrivateJWK(key.privateKey)) _key = key.privateKey;
596
- if (!_key) throw new Error("[unjwt/elysia] Invalid JWE key. It must be a password string or valid JWK.", { cause: key });
597
- return _key;
124
+ function jweSession(config) {
125
+ return sessionPlugin({
126
+ name: "unjwt/elysia-jwe",
127
+ sessionName: config.name || codec.defaultName,
128
+ contextKey: config.contextKey,
129
+ create: (context) => createJWESession(context, config)
130
+ });
598
131
  }
599
- function getDecryptKey(key) {
600
- if (!key) throw new Error("[unjwt/elysia] JWE key is required.");
601
- let _key;
602
- if (typeof key === "string") _key = key;
603
- else if (isSymmetricJWK(key)) _key = key;
604
- else if ("privateKey" in key) _key = key.privateKey;
605
- if (!_key) throw new Error("[unjwt/elysia] Invalid JWE key. It must be a password string or a valid private JWK.", { cause: key });
606
- return _key;
132
+ function bind(context, config) {
133
+ return bindSession(context, config, {
134
+ port: elysiaSessionPort,
135
+ codec,
136
+ actions: sessionActions()
137
+ });
607
138
  }
608
- export { createJWESession, createJWSSession, deriveJWKFromPassword, exportPEM, generateJWK, importPEM, jweSession, jwsSession };
139
+ export { createJWESession, createJWSSession, deriveJWKFromPassword, exportPEM, generateJWK, importPEM, jweSession, jwsSession, thumbprint };