unjwt 0.7.3 → 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.
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 +1374 -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 +29 -30
  38. package/dist/_chunks/_internal.mjs +0 -88
  39. package/dist/_chunks/utils.mjs +0 -392
@@ -1,642 +1,139 @@
1
- import { JWTError, computeDurationInSeconds, isJWTError, isPrivateJWK, isPublicJWK, isSymmetricJWK } from "../../_chunks/utils.mjs";
2
- import { sanitizeObjectCopy } from "../../_chunks/libs/unsecure.mjs";
3
- import { deriveJWKFromPassword, exportPEM, exportToPEM, generateJWK, importFromPEM, importPEM } from "../../_chunks/jwk.mjs";
4
- import { sign, verify } from "../../_chunks/jws.mjs";
5
- import { decrypt, encrypt } from "../../_chunks/jwe.mjs";
6
- import { getChunkedCookie, getEventContext, setChunkedCookie } from "h3";
7
- import { NullProtoObj } from "rou3";
8
- const kGetSessionPromise$1 = Symbol("h3_jwe_getSession");
9
- const DEFAULT_NAME$1 = "h3-jwe";
10
- const DEFAULT_COOKIE$1 = {
11
- path: "/",
12
- secure: true,
13
- httpOnly: true
1
+ import { deriveJWKFromPassword, exportPEM, generateJWK, importPEM, thumbprint } from "../../_chunks/jwk.mjs";
2
+ import { bindSession, createJWECodec, createJWSCodec, dropSession, encodeSession, isChunkName, issueSession, loadSession, openSessionToken, readChunkedBy, readChunkedFromSetCookie, recordSlotStore, removeChunked, resolveSessionToken, responseCookieStore, sessionActions, sessionManager, sessionToken, setCookieName, writeChunked } from "../../_chunks/_codec-jws.mjs";
3
+ import { getCookie, getEventContext, setCookie } from "h3";
4
+ function eventCookieStore(event) {
5
+ return responseCookieStore({
6
+ request: (name) => getCookie(event, name),
7
+ response: () => event.res.headers.getSetCookie(),
8
+ write: (name, value, attrs) => setCookie(event, name, value, attrs)
9
+ });
10
+ }
11
+ const PREFIX = "[unjwt/h3]";
12
+ const h3SessionPort = {
13
+ prefix: PREFIX,
14
+ carrier: (event) => ({ event }),
15
+ store: (event, slot) => recordSlotStore(() => {
16
+ const context = getEventContext(event);
17
+ return context.sessions ||= Object.create(null);
18
+ }, slot),
19
+ readToken: readSessionToken,
20
+ scope: (event) => getEventContext(event),
21
+ writable: hasWritableResponse,
22
+ setCookie: (event, sessionName, token, { attrs, chunkMaxLength }) => {
23
+ const target = cookieEvent(event);
24
+ writeChunked(eventCookieStore(target), sessionName, token, {
25
+ attrs,
26
+ chunkMaxLength,
27
+ prefix: PREFIX
28
+ });
29
+ stageSessionErrCookies(target, sessionName);
30
+ },
31
+ deleteCookie: (event, sessionName, { attrs }) => {
32
+ const target = cookieEvent(event);
33
+ removeChunked(eventCookieStore(target), sessionName, { attrs });
34
+ stageSessionErrCookies(target, sessionName);
35
+ }
14
36
  };
15
- async function useJWESession(event, config) {
16
- const sessionName = config.name || DEFAULT_NAME$1;
17
- await getJWESession(event, config);
18
- const sessionManager = {
19
- get id() {
20
- return getSessionFromContext$1(event, sessionName)?.id;
21
- },
22
- get createdAt() {
23
- return getSessionFromContext$1(event, sessionName)?.createdAt ?? Date.now();
24
- },
25
- get expiresAt() {
26
- return getSessionFromContext$1(event, sessionName)?.expiresAt;
27
- },
28
- get data() {
29
- return getSessionFromContext$1(event, sessionName)?.data || {};
30
- },
31
- get token() {
32
- return getSessionFromContext$1(event, sessionName)?.token ?? getJWESessionToken(event, config);
33
- },
34
- update: async (update) => {
35
- await updateJWESession(event, config, update);
36
- return sessionManager;
37
- },
38
- clear: async () => {
39
- await clearJWESession(event, config);
40
- return sessionManager;
41
- }
37
+ function hasResponse(event) {
38
+ return "res" in event && Boolean(event.res);
39
+ }
40
+ function hasWritableResponse(event) {
41
+ if (!hasResponse(event)) return false;
42
+ const res = event.runtime?.node?.res;
43
+ return !res || !res.headersSent && !res.writableEnded;
44
+ }
45
+ function readSessionToken(event, sessionName, source) {
46
+ return resolveSessionToken(source, sessionName, {
47
+ written: (name) => hasResponse(event) ? readChunkedFromSetCookie(event.res.headers.getSetCookie(), name) : void 0,
48
+ cookie: (name) => readChunkedBy((key) => getCookie(event, key), name),
49
+ header: (name) => event.req.headers.get(name)
50
+ });
51
+ }
52
+ function stageSessionErrCookies(event, sessionName) {
53
+ const isSessionCookie = (cookie) => {
54
+ const name = setCookieName(cookie);
55
+ return name !== void 0 && (name === sessionName || isChunkName(name, sessionName));
42
56
  };
43
- return sessionManager;
57
+ const errHeaders = event.res.errHeaders;
58
+ const staged = [...errHeaders.getSetCookie().filter((cookie) => !isSessionCookie(cookie)), ...event.res.headers.getSetCookie().filter((cookie) => isSessionCookie(cookie))];
59
+ errHeaders.delete("set-cookie");
60
+ for (const cookie of staged) errHeaders.append("set-cookie", cookie);
61
+ }
62
+ function cookieEvent(event) {
63
+ return event;
64
+ }
65
+ const codec$1 = createJWECodec({
66
+ prefix: PREFIX,
67
+ defaultName: "h3-jwe"
68
+ });
69
+ async function useJWESession(event, config) {
70
+ const binding = bind$1(event, config);
71
+ await loadSession(binding);
72
+ return sessionManager(binding);
44
73
  }
45
74
  async function getJWESession(event, config) {
46
- const sessionName = config.name || DEFAULT_NAME$1;
47
- const context = getEventContext(event);
48
- if (!context.sessions) context.sessions = new NullProtoObj();
49
- const existingSession = context.sessions[sessionName];
50
- if (existingSession) {
51
- const session = existingSession[kGetSessionPromise$1] ? await existingSession[kGetSessionPromise$1] : existingSession;
52
- if (session.expiresAt !== void 0 && session.expiresAt < Date.now()) {
53
- await config.hooks?.onExpire?.({
54
- session: {
55
- id: session.id,
56
- createdAt: session.createdAt,
57
- expiresAt: session.expiresAt,
58
- token: session.token
59
- },
60
- event,
61
- error: new JWTError(`JWT "exp" (Expiration Time) Claim validation failed: Token has expired (exp: ${(/* @__PURE__ */ new Date(session.expiresAt * 1e3)).toISOString()})`, "ERR_JWT_EXPIRED", {
62
- jti: session.id,
63
- exp: session.expiresAt / 1e3
64
- }),
65
- config
66
- });
67
- delete context.sessions[sessionName];
68
- if (config.cookie !== false) if (hasWritableResponse$1(event)) setChunkedCookie(event, sessionName, "", {
69
- ...DEFAULT_COOKIE$1,
70
- ...config.cookie,
71
- expires: /* @__PURE__ */ new Date(0),
72
- maxAge: void 0
73
- });
74
- else console.warn("[unjwt/h3] Session expired but cookie cannot be cleared on a read-only event.");
75
- const freshNow = config.jwe?.encryptOptions?.currentDate?.getTime() ?? Date.now();
76
- const freshCreatedAt = freshNow - freshNow % 1e3;
77
- const freshSession = {
78
- id: void 0,
79
- createdAt: freshCreatedAt,
80
- expiresAt: config.maxAge === void 0 ? void 0 : freshCreatedAt + computeDurationInSeconds(config.maxAge) * 1e3,
81
- data: new NullProtoObj(),
82
- token: void 0
83
- };
84
- context.sessions[sessionName] = freshSession;
85
- return freshSession;
86
- }
87
- if (session.id !== void 0 && session.token !== void 0) await config.hooks?.onRead?.({
88
- session,
89
- event,
90
- config
91
- });
92
- return session;
93
- }
94
- const now = config.jwe?.encryptOptions?.currentDate?.getTime() ?? Date.now();
95
- const createdAt = now - now % 1e3;
96
- const session = {
97
- id: void 0,
98
- createdAt,
99
- expiresAt: config.maxAge === void 0 ? void 0 : createdAt + computeDurationInSeconds(config.maxAge) * 1e3,
100
- data: new NullProtoObj(),
101
- token: void 0
102
- };
103
- context.sessions[sessionName] = session;
104
- const token = getJWESessionToken(event, config);
105
- let exclusiveHookFired = false;
106
- if (token) {
107
- session.token = token;
108
- const promise = unsealJWESession(event, config, token).catch(async (error_) => {
109
- exclusiveHookFired = true;
110
- if (isJWTError(error_, "ERR_JWT_EXPIRED")) await config.hooks?.onExpire?.({
111
- session: {
112
- id: error_.cause.jti,
113
- createdAt: error_.cause.iat ? error_.cause.iat * 1e3 : void 0,
114
- expiresAt: error_.cause.exp ? error_.cause.exp * 1e3 : void 0,
115
- token
116
- },
117
- event,
118
- error: error_,
119
- config
120
- });
121
- else await config.hooks?.onError?.({
122
- session,
123
- event,
124
- error: error_,
125
- config
126
- });
127
- }).then((unsealed) => {
128
- if (unsealed) Object.assign(session, unsealed);
129
- delete session[kGetSessionPromise$1];
130
- return session;
131
- });
132
- session[kGetSessionPromise$1] = promise;
133
- await promise;
134
- }
135
- if (!exclusiveHookFired && session.id !== void 0 && session.token !== void 0) await config.hooks?.onRead?.({
136
- session,
137
- event,
138
- config
139
- });
140
- return session;
75
+ return loadSession(bind$1(event, config));
141
76
  }
142
77
  function getJWESessionToken(event, config) {
143
- const sessionName = config.name || DEFAULT_NAME$1;
144
- let token;
145
- if (config.sessionHeader !== false) {
146
- const headerName = typeof config.sessionHeader === "string" ? config.sessionHeader.toLowerCase() : `x-${sessionName.toLowerCase()}-session`;
147
- const headerValue = event.req.headers.get(headerName);
148
- if (typeof headerValue === "string") token = headerValue.startsWith("Bearer ") ? headerValue.slice(7).trim() : headerValue;
149
- }
150
- if (config.cookie !== false && hasWritableResponse$1(event)) {
151
- const setCookie = event.res.headers.get("set-cookie");
152
- if (typeof setCookie === "string") token = findSetCookie$1(setCookie, sessionName);
153
- }
154
- if (!token) token = getChunkedCookie(event, sessionName);
155
- return token;
78
+ return sessionToken(bind$1(event, config));
156
79
  }
157
- async function updateJWESession(event, config, update) {
158
- const canWriteCookie = hasWritableResponse$1(event);
159
- if (config.cookie !== false && !canWriteCookie) throw new Error("[unjwt/h3] Cannot update session on read-only event.");
160
- const sessionName = config.name || DEFAULT_NAME$1;
161
- const session = getEventContext(event).sessions?.[sessionName] || await getJWESession(event, config);
162
- const oldSession = {
163
- ...session,
164
- data: sanitizeObjectCopy(session.data)
165
- };
166
- if (typeof update === "function") update = update(session.data);
167
- if (update) Object.assign(session.data, update);
168
- const now = config.jwe?.encryptOptions?.currentDate?.getTime() ?? Date.now();
169
- const createdAt = now - now % 1e3;
170
- Object.assign(session, {
171
- id: config.generateId?.() || crypto.randomUUID(),
172
- createdAt,
173
- expiresAt: config.maxAge === void 0 ? void 0 : createdAt + computeDurationInSeconds(config.maxAge) * 1e3
174
- });
175
- let sealed;
176
- try {
177
- sealed = await sealJWESession(event, config);
178
- } catch (error_) {
179
- Object.assign(session, {
180
- id: oldSession.id,
181
- createdAt: oldSession.createdAt,
182
- expiresAt: oldSession.expiresAt,
183
- data: oldSession.data
184
- });
185
- session.token = oldSession.token;
186
- await config.hooks?.onError?.({
187
- session,
188
- event,
189
- error: error_,
190
- config
191
- });
192
- throw error_;
193
- }
194
- session.token = sealed;
195
- if (config.cookie !== false && canWriteCookie) setChunkedCookie(event, sessionName, sealed, {
196
- ...DEFAULT_COOKIE$1,
197
- expires: config.maxAge === void 0 ? void 0 : new Date(session.createdAt + computeDurationInSeconds(config.maxAge) * 1e3),
198
- ...config.cookie
199
- });
200
- await config.hooks?.onUpdate?.({
201
- session,
202
- oldSession,
203
- event,
204
- config
205
- });
206
- return session;
80
+ async function updateJWESession(event, config, update, options) {
81
+ return issueSession(bind$1(event, config), update, options);
207
82
  }
208
83
  async function sealJWESession(event, config) {
209
- const key = getEncryptKey(config.key);
210
- const sessionName = config.name || DEFAULT_NAME$1;
211
- const session = getEventContext(event).sessions?.[sessionName] || await getJWESession(event, config);
212
- const iat = Math.floor(session.createdAt / 1e3);
213
- const exp = session.expiresAt ? Math.floor(session.expiresAt / 1e3) : void 0;
214
- const payload = {
215
- ...session.data,
216
- jti: session.id,
217
- iat
218
- };
219
- if (exp) payload.exp = exp;
220
- let typ = void 0;
221
- if (config.jwe?.encryptOptions?.protectedHeader?.typ && typeof config.jwe.encryptOptions.protectedHeader.typ === "string" && config.jwe.encryptOptions.protectedHeader.typ.toLowerCase().includes("jwt")) typ = config.jwe.encryptOptions.protectedHeader.typ;
222
- return await encrypt(payload, key, {
223
- ...config.jwe?.encryptOptions,
224
- expiresIn: void 0,
225
- protectedHeader: {
226
- ...config.jwe?.encryptOptions?.protectedHeader,
227
- typ: typ || "JWT",
228
- cty: "application/json"
229
- }
230
- });
84
+ return encodeSession(bind$1(event, config));
231
85
  }
232
86
  async function unsealJWESession(event, config, sealed) {
233
- const key = config.hooks?.onUnsealKeyLookup ? (header) => config.hooks.onUnsealKeyLookup({
234
- header,
235
- event,
236
- config
237
- }) : getDecryptKey(config.key);
238
- const alg = config.jwe?.encryptOptions?.alg;
239
- const enc = config.jwe?.encryptOptions?.enc;
240
- let typ = void 0;
241
- if (config.jwe?.encryptOptions?.protectedHeader?.typ && typeof config.jwe.encryptOptions.protectedHeader.typ === "string" && config.jwe.encryptOptions.protectedHeader.typ.toLowerCase().includes("jwt")) typ = config.jwe.encryptOptions.protectedHeader.typ;
242
- const { payload } = await decrypt(sealed, key, {
243
- ...config.jwe?.decryptOptions,
244
- requiredClaims: [.../* @__PURE__ */ new Set([
245
- ...config.jwe?.decryptOptions?.requiredClaims || [],
246
- "jti",
247
- "iat"
248
- ])],
249
- typ: typ || "JWT",
250
- maxTokenAge: config.maxAge,
251
- algorithms: alg ? [alg] : void 0,
252
- encryptionAlgorithms: enc ? [enc] : void 0,
253
- unwrappedKeyAlgorithm: void 0,
254
- keyUsage: void 0,
255
- forceUint8Array: false,
256
- validateClaims: true
257
- }).catch((error_) => {
258
- if (error_ instanceof Error) throw error_;
259
- throw new Error(String(error_));
87
+ return openSessionToken(codec$1, config, {
88
+ token: sealed,
89
+ carrier: { event }
260
90
  });
261
- const { jti, iat, exp, ...data } = payload;
262
- return {
263
- id: jti,
264
- createdAt: iat * 1e3,
265
- expiresAt: exp ? exp * 1e3 : void 0,
266
- data: data && typeof data === "object" ? data : new NullProtoObj()
267
- };
268
91
  }
269
92
  async function clearJWESession(event, config) {
270
- const canWriteCookie = hasWritableResponse$1(event);
271
- if (config.cookie !== false && !canWriteCookie) throw new Error("[unjwt/h3] Cannot clear session on read-only event.");
272
- const context = getEventContext(event);
273
- const sessionName = config.name || DEFAULT_NAME$1;
274
- let session = context.sessions?.[sessionName];
275
- if (session && session[kGetSessionPromise$1]) session = await session[kGetSessionPromise$1];
276
- const oldSession = session ? {
277
- ...session,
278
- data: sanitizeObjectCopy(session.data)
279
- } : void 0;
280
- if (context.sessions?.[sessionName]) delete context.sessions[sessionName];
281
- if (config.cookie !== false && canWriteCookie) setChunkedCookie(event, sessionName, "", {
282
- ...DEFAULT_COOKIE$1,
283
- ...config.cookie,
284
- expires: /* @__PURE__ */ new Date(0),
285
- maxAge: void 0
286
- });
287
- await config.hooks?.onClear?.({
288
- oldSession,
289
- event,
290
- config
291
- });
292
- }
293
- function getSessionFromContext$1(event, sessionName) {
294
- return getEventContext(event).sessions?.[sessionName];
295
- }
296
- function hasWritableResponse$1(event) {
297
- return Boolean(event.res);
298
- }
299
- function getEncryptKey(key) {
300
- if (!key) throw new Error("Session: JWE key is required.");
301
- let _key;
302
- if (typeof key === "string") _key = key;
303
- else if (isSymmetricJWK(key)) _key = key;
304
- else if ("publicKey" in key && key.publicKey && isPublicJWK(key.publicKey)) _key = key.publicKey;
305
- else if ("privateKey" in key && isPrivateJWK(key.privateKey)) _key = key.privateKey;
306
- if (!_key) throw new Error("Session: Invalid JWE key. It must be a password string or valid JWK.", { cause: key });
307
- return _key;
93
+ return dropSession(bind$1(event, config));
308
94
  }
309
- function getDecryptKey(key) {
310
- if (!key) throw new Error("Session: JWE key is required.");
311
- let _key = void 0;
312
- if (typeof key === "string") _key = key;
313
- else if (isSymmetricJWK(key)) _key = key;
314
- else if ("privateKey" in key) _key = key.privateKey;
315
- if (!_key) throw new Error("Session: Invalid JWE key. It must be a password string or a valid private JWK.", { cause: key });
316
- return _key;
317
- }
318
- function findSetCookie$1(setCookie, name) {
319
- const regex = new RegExp(`(?:^|,\\s*)${name}=([^;]+)`);
320
- const match = setCookie.match(regex);
321
- return match ? match[1] : void 0;
95
+ function bind$1(event, config) {
96
+ return bindSession(event, config, {
97
+ port: h3SessionPort,
98
+ codec: codec$1,
99
+ actions: sessionActions()
100
+ });
322
101
  }
323
- const kGetSessionPromise = Symbol("h3_jws_getSession");
324
- const DEFAULT_NAME = "h3-jws";
325
- const DEFAULT_COOKIE = {
326
- path: "/",
327
- secure: true,
328
- httpOnly: false
329
- };
102
+ const codec = createJWSCodec({
103
+ prefix: PREFIX,
104
+ defaultName: "h3-jws"
105
+ });
330
106
  async function useJWSSession(event, config) {
331
- const sessionName = config.name || DEFAULT_NAME;
332
- await getJWSSession(event, config);
333
- const sessionManager = {
334
- get id() {
335
- return getSessionFromContext(event, sessionName)?.id;
336
- },
337
- get createdAt() {
338
- return getSessionFromContext(event, sessionName)?.createdAt ?? Date.now();
339
- },
340
- get expiresAt() {
341
- return getSessionFromContext(event, sessionName)?.expiresAt;
342
- },
343
- get data() {
344
- return getSessionFromContext(event, sessionName)?.data || {};
345
- },
346
- get token() {
347
- return getSessionFromContext(event, sessionName)?.token ?? getJWSSessionToken(event, config);
348
- },
349
- update: async (update) => {
350
- await updateJWSSession(event, config, update);
351
- return sessionManager;
352
- },
353
- clear: async () => {
354
- await clearJWSSession(event, config);
355
- return sessionManager;
356
- }
357
- };
358
- return sessionManager;
107
+ const binding = bind(event, config);
108
+ await loadSession(binding);
109
+ return sessionManager(binding);
359
110
  }
360
111
  async function getJWSSession(event, config) {
361
- const sessionName = config.name || DEFAULT_NAME;
362
- const context = getEventContext(event);
363
- if (!context.sessions) context.sessions = new NullProtoObj();
364
- const existingSession = context.sessions[sessionName];
365
- if (existingSession) {
366
- const session = existingSession[kGetSessionPromise] ? await existingSession[kGetSessionPromise] : existingSession;
367
- if (session.expiresAt !== void 0 && session.expiresAt < Date.now()) {
368
- await config.hooks?.onExpire?.({
369
- session: {
370
- id: session.id,
371
- createdAt: session.createdAt,
372
- expiresAt: session.expiresAt,
373
- token: session.token
374
- },
375
- event,
376
- error: new JWTError(`JWT "exp" (Expiration Time) Claim validation failed: Token has expired (exp: ${(/* @__PURE__ */ new Date(session.expiresAt * 1e3)).toISOString()})`, "ERR_JWT_EXPIRED", {
377
- jti: session.id,
378
- exp: session.expiresAt / 1e3
379
- }),
380
- config
381
- });
382
- delete context.sessions[sessionName];
383
- if (config.cookie !== false) if (hasWritableResponse(event)) setChunkedCookie(event, sessionName, "", {
384
- ...DEFAULT_COOKIE,
385
- ...config.cookie,
386
- expires: /* @__PURE__ */ new Date(0),
387
- maxAge: void 0
388
- });
389
- else console.warn("[unjwt/h3] Session expired but cookie cannot be cleared on a read-only event.");
390
- const freshNow = config.jws?.signOptions?.currentDate?.getTime() ?? Date.now();
391
- const freshCreatedAt = freshNow - freshNow % 1e3;
392
- const freshSession = {
393
- id: void 0,
394
- createdAt: freshCreatedAt,
395
- expiresAt: config.maxAge === void 0 ? void 0 : freshCreatedAt + computeDurationInSeconds(config.maxAge) * 1e3,
396
- data: new NullProtoObj(),
397
- token: void 0
398
- };
399
- context.sessions[sessionName] = freshSession;
400
- return freshSession;
401
- }
402
- if (session.id !== void 0 && session.token !== void 0) await config.hooks?.onRead?.({
403
- session,
404
- event,
405
- config
406
- });
407
- return session;
408
- }
409
- const now = config.jws?.signOptions?.currentDate?.getTime() ?? Date.now();
410
- const createdAt = now - now % 1e3;
411
- const session = {
412
- id: void 0,
413
- createdAt,
414
- expiresAt: config.maxAge === void 0 ? void 0 : createdAt + computeDurationInSeconds(config.maxAge) * 1e3,
415
- data: new NullProtoObj(),
416
- token: void 0
417
- };
418
- context.sessions[sessionName] = session;
419
- const token = getJWSSessionToken(event, config);
420
- let exclusiveHookFired = false;
421
- if (token) {
422
- session.token = token;
423
- const promise = verifyJWSSession(event, config, token).catch(async (error_) => {
424
- exclusiveHookFired = true;
425
- if (isJWTError(error_, "ERR_JWT_EXPIRED")) await config.hooks?.onExpire?.({
426
- session: {
427
- id: error_.cause.jti,
428
- createdAt: error_.cause.iat ? error_.cause.iat * 1e3 : void 0,
429
- expiresAt: error_.cause.exp ? error_.cause.exp * 1e3 : void 0,
430
- token
431
- },
432
- event,
433
- error: error_,
434
- config
435
- });
436
- else await config.hooks?.onError?.({
437
- session,
438
- event,
439
- error: error_,
440
- config
441
- });
442
- }).then((unsealed) => {
443
- if (unsealed) Object.assign(session, unsealed);
444
- delete session[kGetSessionPromise];
445
- return session;
446
- });
447
- session[kGetSessionPromise] = promise;
448
- await promise;
449
- }
450
- if (!exclusiveHookFired && session.id !== void 0 && session.token !== void 0) await config.hooks?.onRead?.({
451
- session,
452
- event,
453
- config
454
- });
455
- return session;
112
+ return loadSession(bind(event, config));
456
113
  }
457
114
  function getJWSSessionToken(event, config) {
458
- const sessionName = config.name || DEFAULT_NAME;
459
- let token;
460
- if (config.sessionHeader !== false) {
461
- const headerName = typeof config.sessionHeader === "string" ? config.sessionHeader.toLowerCase() : `x-${sessionName.toLowerCase()}-session`;
462
- const headerValue = event.req.headers.get(headerName);
463
- if (typeof headerValue === "string") token = headerValue.startsWith("Bearer ") ? headerValue.slice(7).trim() : headerValue;
464
- }
465
- if (config.cookie !== false && hasWritableResponse(event)) {
466
- const setCookie = event.res.headers.get("set-cookie");
467
- if (typeof setCookie === "string") token = findSetCookie(setCookie, sessionName);
468
- }
469
- if (!token) token = getChunkedCookie(event, sessionName);
470
- return token;
115
+ return sessionToken(bind(event, config));
471
116
  }
472
- async function updateJWSSession(event, config, update) {
473
- const canWriteCookie = hasWritableResponse(event);
474
- if (config.cookie !== false && !canWriteCookie) throw new Error("[unjwt/h3] Cannot update session on read-only event.");
475
- const sessionName = config.name || DEFAULT_NAME;
476
- const session = getEventContext(event).sessions?.[sessionName] || await getJWSSession(event, config);
477
- const oldSession = {
478
- ...session,
479
- data: sanitizeObjectCopy(session.data)
480
- };
481
- if (typeof update === "function") update = update(session.data);
482
- if (update) Object.assign(session.data, update);
483
- const now = config.jws?.signOptions?.currentDate?.getTime() ?? Date.now();
484
- const createdAt = now - now % 1e3;
485
- Object.assign(session, {
486
- id: config.generateId?.() || crypto.randomUUID(),
487
- createdAt,
488
- expiresAt: config.maxAge === void 0 ? void 0 : createdAt + computeDurationInSeconds(config.maxAge) * 1e3
489
- });
490
- let token;
491
- try {
492
- token = await signJWSSession(event, config);
493
- } catch (error_) {
494
- Object.assign(session, {
495
- id: oldSession.id,
496
- createdAt: oldSession.createdAt,
497
- expiresAt: oldSession.expiresAt,
498
- data: oldSession.data
499
- });
500
- session.token = oldSession.token;
501
- await config.hooks?.onError?.({
502
- session,
503
- event,
504
- error: error_,
505
- config
506
- });
507
- throw error_;
508
- }
509
- session.token = token;
510
- if (config.cookie !== false && canWriteCookie) setChunkedCookie(event, sessionName, token, {
511
- ...DEFAULT_COOKIE,
512
- expires: config.maxAge === void 0 ? void 0 : new Date(session.createdAt + computeDurationInSeconds(config.maxAge) * 1e3),
513
- ...config.cookie
514
- });
515
- await config.hooks?.onUpdate?.({
516
- session,
517
- oldSession,
518
- event,
519
- config
520
- });
521
- return session;
117
+ async function updateJWSSession(event, config, update, options) {
118
+ return issueSession(bind(event, config), update, options);
522
119
  }
523
120
  async function signJWSSession(event, config) {
524
- const key = getSignKey(config.key);
525
- const sessionName = config.name || DEFAULT_NAME;
526
- const session = getEventContext(event).sessions?.[sessionName] || await getJWSSession(event, config);
527
- const iat = Math.floor(session.createdAt / 1e3);
528
- const exp = session.expiresAt ? Math.floor(session.expiresAt / 1e3) : void 0;
529
- const payload = {
530
- ...session.data,
531
- jti: session.id,
532
- iat
533
- };
534
- if (exp) payload.exp = exp;
535
- let typ;
536
- if (config.jws?.signOptions?.protectedHeader?.typ && typeof config.jws.signOptions.protectedHeader.typ === "string" && config.jws.signOptions.protectedHeader.typ.toLowerCase().includes("jwt")) typ = config.jws.signOptions.protectedHeader.typ;
537
- return await sign(payload, key, {
538
- ...config.jws?.signOptions,
539
- expiresIn: void 0,
540
- protectedHeader: {
541
- ...config.jws?.signOptions?.protectedHeader,
542
- kid: "kid" in key ? key.kid : void 0,
543
- typ: typ || "JWT",
544
- cty: "application/json"
545
- }
546
- });
121
+ return encodeSession(bind(event, config));
547
122
  }
548
123
  async function verifyJWSSession(event, config, token) {
549
- const alg = config.jws?.signOptions?.alg;
550
- const jwk = config.hooks?.onVerifyKeyLookup ? (header) => config.hooks.onVerifyKeyLookup({
551
- header,
552
- event,
553
- config
554
- }) : getVerifyKey(config.key);
555
- let typ;
556
- if (config.jws?.signOptions?.protectedHeader?.typ && typeof config.jws.signOptions.protectedHeader.typ === "string" && config.jws.signOptions.protectedHeader.typ.toLowerCase().includes("jwt")) typ = config.jws.signOptions.protectedHeader.typ;
557
- const { payload } = await verify(token, jwk, {
558
- ...config.jws?.verifyOptions,
559
- requiredClaims: [.../* @__PURE__ */ new Set([
560
- ...config.jws?.verifyOptions?.requiredClaims || [],
561
- "jti",
562
- "iat"
563
- ])],
564
- typ: typ || "JWT",
565
- algorithms: alg ? [alg] : void 0,
566
- forceUint8Array: false,
567
- validateClaims: true
568
- }).catch((error_) => {
569
- if (isJWTError(error_, "ERR_JWT_EXPIRED")) throw error_;
570
- const message = error_ instanceof Error ? error_.message : String(error_);
571
- throw new Error(`Invalid session token: ${message}`);
124
+ return openSessionToken(codec, config, {
125
+ token,
126
+ carrier: { event }
572
127
  });
573
- const { jti, iat, exp, ...data } = payload;
574
- return {
575
- id: jti,
576
- createdAt: iat * 1e3,
577
- expiresAt: exp ? exp * 1e3 : void 0,
578
- data: data && typeof data === "object" ? data : new NullProtoObj()
579
- };
580
128
  }
581
129
  async function clearJWSSession(event, config) {
582
- const canWriteCookie = hasWritableResponse(event);
583
- if (config.cookie !== false && !canWriteCookie) throw new Error("[unjwt/h3] Cannot clear session on read-only event.");
584
- const context = getEventContext(event);
585
- const sessionName = config.name || DEFAULT_NAME;
586
- let session = context.sessions?.[sessionName];
587
- if (session && session[kGetSessionPromise]) session = await session[kGetSessionPromise];
588
- const oldSession = session ? {
589
- ...session,
590
- data: sanitizeObjectCopy(session.data)
591
- } : void 0;
592
- if (context.sessions?.[sessionName]) delete context.sessions[sessionName];
593
- if (config.cookie !== false && canWriteCookie) setChunkedCookie(event, sessionName, "", {
594
- ...DEFAULT_COOKIE,
595
- ...config.cookie,
596
- expires: /* @__PURE__ */ new Date(0),
597
- maxAge: void 0
598
- });
599
- await config.hooks?.onClear?.({
600
- oldSession,
601
- event,
602
- config
603
- });
130
+ return dropSession(bind(event, config));
604
131
  }
605
- function getSessionFromContext(event, sessionName) {
606
- return getEventContext(event).sessions?.[sessionName];
607
- }
608
- function hasWritableResponse(event) {
609
- return Boolean(event.res);
610
- }
611
- function getSignKey(key) {
612
- if (!key) throw new Error("Session: JWS key is required.");
613
- let _key;
614
- if (isSymmetricJWK(key)) _key = key;
615
- else if ("privateKey" in key && isPrivateJWK(key.privateKey)) _key = key.privateKey;
616
- if (!_key) throw new Error("Session: Invalid JWS key. It must be a symmetric JWK or a private JWK.", { cause: key });
617
- return _key;
618
- }
619
- function getVerifyKey(key) {
620
- if (!key) throw new Error("Session: JWS key is required.");
621
- let _key;
622
- if (isSymmetricJWK(key)) _key = key;
623
- else if ("publicKey" in key) {
624
- const publicKey = key.publicKey;
625
- if (isPublicJWK(publicKey)) _key = publicKey;
626
- else if (Array.isArray(publicKey)) {
627
- const keys = publicKey.filter((candidate) => isPublicJWK(candidate));
628
- if (keys.length > 0) _key = { keys };
629
- } else if (publicKey && typeof publicKey === "object" && Array.isArray(publicKey.keys)) {
630
- const keys = publicKey.keys.filter((candidate) => isPublicJWK(candidate));
631
- if (keys.length > 0) _key = { keys };
632
- }
633
- }
634
- if (!_key) throw new Error("Session: Invalid JWS key. It must be a symmetric JWK or a public JWK/set.", { cause: key });
635
- return _key;
636
- }
637
- function findSetCookie(setCookie, name) {
638
- const regex = new RegExp(`(?:^|,\\s*)${name}=([^;]+)`);
639
- const match = setCookie.match(regex);
640
- return match ? match[1] : void 0;
132
+ function bind(event, config) {
133
+ return bindSession(event, config, {
134
+ port: h3SessionPort,
135
+ codec,
136
+ actions: sessionActions()
137
+ });
641
138
  }
642
- export { clearJWESession, clearJWSSession, deriveJWKFromPassword, exportPEM, exportToPEM, generateJWK, getJWESession, getJWESessionToken, getJWSSession, getJWSSessionToken, importFromPEM, importPEM, sealJWESession, signJWSSession, unsealJWESession, updateJWESession, updateJWSSession, useJWESession, useJWSSession, verifyJWSSession };
139
+ export { clearJWESession, clearJWSSession, deriveJWKFromPassword, exportPEM, generateJWK, getJWESession, getJWESessionToken, getJWSSession, getJWSSessionToken, importPEM, sealJWESession, signJWSSession, thumbprint, unsealJWESession, updateJWESession, updateJWSSession, useJWESession, useJWSSession, verifyJWSSession };