unjwt 0.5.21 → 0.5.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,11 +20,12 @@ A collection of low-level JWT ([RFC 7519](https://datatracker.ietf.org/doc/html/
20
20
  ## Table of Contents
21
21
 
22
22
  - [Usage](#usage)
23
- - [Adapters](#adapters)
24
23
  - [JWS](#jws-json-web-signature-rfc-7515)
25
24
  - [JWE](#jwe-json-web-encryption-rfc-7516)
26
25
  - [JWK](#jwk-json-web-key-rfc-7517)
27
26
  - [Utility Functions](#utility-functions)
27
+ - [Adapters](#adapters)
28
+ - [H3 (Nuxt, Nitro)](#h3-nuxt-nitro)
28
29
  - [Development](#development)
29
30
  - [Credits](#credits)
30
31
  - [License](#license)
package/dist/h3.d.mts CHANGED
@@ -9,7 +9,7 @@ export { d as deriveJWKFromPassword, e as exportJWKToPEM, g as generateJWK, i as
9
9
 
10
10
  /**
11
11
  * This is a fork of h3 library's session utility functions.
12
- * @source https://github.com/h3js/h3/blob/b4dce71c256911335f3402d09f30ffad120ad61a/test/session.test.ts
12
+ * @source https://github.com/h3js/h3/blob/b4dce71c256911335f3402d09f30ffad120ad61a/src/utils/session.ts
13
13
  * @license MIT https://github.com/h3js/h3/blob/b4dce71c256911335f3402d09f30ffad120ad61a/LICENSE
14
14
  */
15
15
 
@@ -23,6 +23,13 @@ interface SessionJWE<T extends SessionDataT$1 = SessionDataT$1> {
23
23
  data: SessionData<T>;
24
24
  [kGetSessionPromise$1]?: Promise<SessionJWE<T>>;
25
25
  }
26
+ interface SessionHooksJWE {
27
+ onRead?: (session: SessionJWE, event: H3Event, config: SessionConfigJWE) => void | Promise<void>;
28
+ onUpdate?: (session: SessionJWE, event: H3Event, config: SessionConfigJWE) => void | Promise<void>;
29
+ onClear?: (event: H3Event, config: Partial<SessionConfigJWE>) => void | Promise<void>;
30
+ onExpire?: (event: H3Event, error: any | undefined, config: SessionConfigJWE) => void | Promise<void>;
31
+ onError?: (event: H3Event, error: any, config: SessionConfigJWE) => void | Promise<void>;
32
+ }
26
33
  interface SessionManager<T extends SessionDataT$1 = SessionDataT$1> {
27
34
  readonly id: string | undefined;
28
35
  readonly createdAt: number;
@@ -31,9 +38,14 @@ interface SessionManager<T extends SessionDataT$1 = SessionDataT$1> {
31
38
  update: (update: SessionUpdate$1<T>) => Promise<SessionManager<T>>;
32
39
  clear: () => Promise<SessionManager<T>>;
33
40
  }
34
- interface SessionJWEConfig {
35
- /** Shared secret used, string for PBES2 or Json Web Key (JWK) */
36
- secret: string | JWK_Symmetric | {
41
+ interface SessionConfigJWE {
42
+ /** @deprecated use `key` instead */
43
+ secret?: string | JWK_Symmetric | {
44
+ privateKey: JWK_Private | JWK_Symmetric;
45
+ publicKey?: JWK_Public;
46
+ };
47
+ /** Shared key used, string for PBES2 or Json Web Key (JWK) */
48
+ key: string | JWK_Symmetric | JWK_Private | {
37
49
  privateKey: JWK_Private | JWK_Symmetric;
38
50
  publicKey?: JWK_Public;
39
51
  };
@@ -45,8 +57,6 @@ interface SessionJWEConfig {
45
57
  cookie?: false | CookieSerializeOptions;
46
58
  /** Default is x-h3-session / x-{name}-session */
47
59
  sessionHeader?: false | string;
48
- /** Optional WebCrypto implementation override */
49
- crypto?: Crypto;
50
60
  /** Default is crypto.randomUUID */
51
61
  generateId?: () => string;
52
62
  /** JWE configuration overrides */
@@ -54,7 +64,12 @@ interface SessionJWEConfig {
54
64
  encryptOptions?: Omit<JWEEncryptOptions, "expiresIn">;
55
65
  decryptOptions?: JWTClaimValidationOptions;
56
66
  };
67
+ hooks?: SessionHooksJWE;
57
68
  }
69
+ /**
70
+ * @deprecated use `SessionConfigJWE` instead
71
+ */
72
+ type SessionJWEConfig = SessionConfigJWE;
58
73
  type CompatEvent$1 = {
59
74
  request: {
60
75
  headers: Headers;
@@ -68,15 +83,15 @@ type SessionUpdate$1<T extends SessionDataT$1 = SessionDataT$1> = Partial<Sessio
68
83
  /**
69
84
  * Create a session manager for the current request.
70
85
  */
71
- declare function useJWESession<T extends SessionDataT$1 = SessionDataT$1>(event: H3Event | CompatEvent$1, config: SessionJWEConfig): Promise<SessionManager<T>>;
86
+ declare function useJWESession<T extends SessionDataT$1 = SessionDataT$1>(event: H3Event | CompatEvent$1, config: SessionConfigJWE): Promise<SessionManager<T>>;
72
87
  /**
73
88
  * Get (and lazily initialize) the session for the current request.
74
89
  */
75
- declare function getJWESession<T extends SessionDataT$1 = SessionDataT$1>(event: H3Event | CompatEvent$1, config: SessionJWEConfig): Promise<SessionJWE<T>>;
90
+ declare function getJWESession<T extends SessionDataT$1 = SessionDataT$1>(event: H3Event | CompatEvent$1, config: SessionConfigJWE): Promise<SessionJWE<T>>;
76
91
  /**
77
92
  * Update the session (optionally mutating the session data) and re-issue the JWE token.
78
93
  */
79
- declare function updateJWESession<T extends SessionDataT$1 = SessionDataT$1>(event: H3Event, config: SessionJWEConfig, update?: SessionUpdate$1<T>): Promise<SessionJWE<T>>;
94
+ declare function updateJWESession<T extends SessionDataT$1 = SessionDataT$1>(event: H3Event, config: SessionConfigJWE, update?: SessionUpdate$1<T>): Promise<SessionJWE<T>>;
80
95
  /**
81
96
  * Produce a JWE for the current session.
82
97
  * Payload structure:
@@ -87,19 +102,19 @@ declare function updateJWESession<T extends SessionDataT$1 = SessionDataT$1>(eve
87
102
  * data: Record<string, any>;
88
103
  * }
89
104
  */
90
- declare function sealJWESession<T extends SessionDataT$1 = SessionDataT$1>(event: H3Event | CompatEvent$1, config: SessionJWEConfig): Promise<string>;
105
+ declare function sealJWESession<T extends SessionDataT$1 = SessionDataT$1>(event: H3Event | CompatEvent$1, config: SessionConfigJWE): Promise<string>;
91
106
  /**
92
107
  * Decrypt the JWE and return a Session-compatible object.
93
108
  */
94
- declare function unsealJWESession(_event: H3Event | CompatEvent$1, config: SessionJWEConfig, sealed: string): Promise<Partial<SessionJWE>>;
109
+ declare function unsealJWESession(_event: H3Event | CompatEvent$1, config: SessionConfigJWE, sealed: string): Promise<Partial<SessionJWE>>;
95
110
  /**
96
111
  * Clear the session (delete from context and drop cookie).
97
112
  */
98
- declare function clearJWESession(event: H3Event, config: Partial<SessionJWEConfig>): Promise<void>;
113
+ declare function clearJWESession(event: H3Event, config: Partial<SessionConfigJWE>): Promise<void>;
99
114
 
100
115
  /**
101
116
  * This is a fork of h3 library's session utility functions.
102
- * @source https://github.com/h3js/h3/blob/b4dce71c256911335f3402d09f30ffad120ad61a/test/session.test.ts
117
+ * @source https://github.com/h3js/h3/blob/b4dce71c256911335f3402d09f30ffad120ad61a/src/utils/session.ts
103
118
  * @license MIT https://github.com/h3js/h3/blob/b4dce71c256911335f3402d09f30ffad120ad61a/LICENSE
104
119
  */
105
120
 
@@ -112,7 +127,14 @@ interface SessionJWS<T extends SessionDataT = SessionDataT> {
112
127
  data: SessionData<T>;
113
128
  [kGetSessionPromise]?: Promise<SessionJWS<T>>;
114
129
  }
115
- interface SessionJWSConfig {
130
+ interface SessionHooksJWS {
131
+ onRead?: (session: SessionJWS, event: H3Event, config: SessionConfigJWS) => void | Promise<void>;
132
+ onUpdate?: (session: SessionJWS, event: H3Event, config: SessionConfigJWS) => void | Promise<void>;
133
+ onClear?: (event: H3Event, config: Partial<SessionConfigJWS>) => void | Promise<void>;
134
+ onExpire?: (event: H3Event, error: any | undefined, config: SessionConfigJWS) => void | Promise<void>;
135
+ onError?: (event: H3Event, error: any, config: SessionConfigJWS) => void | Promise<void>;
136
+ }
137
+ interface SessionConfigJWS {
116
138
  /**
117
139
  * JWK (private for signing with RS/ES/PS, or symmetric oct) used for signing.
118
140
  */
@@ -128,8 +150,6 @@ interface SessionJWSConfig {
128
150
  cookie?: false | CookieSerializeOptions;
129
151
  /** Custom header (false to disable header lookup) */
130
152
  sessionHeader?: false | string;
131
- /** Provide a custom WebCrypto (if needed for algorithm operations) */
132
- crypto?: Crypto;
133
153
  /** Custom ID generator (defaults to crypto.randomUUID) */
134
154
  generateId?: () => string;
135
155
  /** JWS customization */
@@ -137,7 +157,12 @@ interface SessionJWSConfig {
137
157
  signOptions?: Omit<JWSSignOptions, "expiresIn">;
138
158
  verifyOptions?: JWTClaimValidationOptions;
139
159
  };
160
+ hooks?: SessionHooksJWS;
140
161
  }
162
+ /**
163
+ * @deprecated use `SessionConfigJWS` instead
164
+ */
165
+ type SessionJWSConfig = SessionConfigJWS;
141
166
  type CompatEvent = {
142
167
  request: {
143
168
  headers: Headers;
@@ -152,21 +177,21 @@ type SessionUpdate<T extends SessionDataT = SessionDataT> = Partial<SessionData<
152
177
  * Create a session manager for the current request using JWS (signed only, not encrypted).
153
178
  * NOTE: Contents are visible to clients (Base64URL-decoded JSON). Do not store secrets in session data.
154
179
  */
155
- declare function useJWSSession<T extends SessionDataT = SessionDataT>(event: H3Event | CompatEvent, config: SessionJWSConfig): Promise<SessionManager<T>>;
180
+ declare function useJWSSession<T extends SessionDataT = SessionDataT>(event: H3Event | CompatEvent, config: SessionConfigJWS): Promise<SessionManager<T>>;
156
181
  /**
157
182
  * Retrieve (and lazily initialize) the session.
158
183
  */
159
- declare function getJWSSession<T extends SessionDataT = SessionDataT>(event: H3Event | CompatEvent, config: SessionJWSConfig): Promise<SessionJWS<T>>;
184
+ declare function getJWSSession<T extends SessionDataT = SessionDataT>(event: H3Event | CompatEvent, config: SessionConfigJWS): Promise<SessionJWS<T>>;
160
185
  /**
161
186
  * Update session data (if provided) and reissue JWS.
162
187
  */
163
- declare function updateJWSSession<T extends SessionDataT = SessionDataT>(event: H3Event, config: SessionJWSConfig, update?: SessionUpdate<T>): Promise<SessionJWS<T>>;
188
+ declare function updateJWSSession<T extends SessionDataT = SessionDataT>(event: H3Event, config: SessionConfigJWS, update?: SessionUpdate<T>): Promise<SessionJWS<T>>;
164
189
  /**
165
190
  * Sign current session as a compact JWS.
166
191
  * Payload claims:
167
192
  * jti, iat, exp?, data, plus optional extraClaims (cannot override reserved)
168
193
  */
169
- declare function signJWSSession<T extends SessionDataT = SessionDataT>(event: H3Event | CompatEvent, config: SessionJWSConfig): Promise<string>;
194
+ declare function signJWSSession<T extends SessionDataT = SessionDataT>(event: H3Event | CompatEvent, config: SessionConfigJWS): Promise<string>;
170
195
  /**
171
196
  * Verify and parse a compact JWS into a Session structure.
172
197
  * Performs:
@@ -175,11 +200,11 @@ declare function signJWSSession<T extends SessionDataT = SessionDataT>(event: H3
175
200
  * - ensures jti & iat presence
176
201
  * - enforces maxAge if provided
177
202
  */
178
- declare function verifyJWSSession(_event: H3Event | CompatEvent, config: SessionJWSConfig, token: string): Promise<Partial<SessionJWS>>;
203
+ declare function verifyJWSSession(_event: H3Event | CompatEvent, config: SessionConfigJWS, token: string): Promise<Partial<SessionJWS>>;
179
204
  /**
180
205
  * Destroy the session (context + cookie).
181
206
  */
182
- declare function clearJWSSession(event: H3Event, config: Partial<SessionJWSConfig>): Promise<void>;
207
+ declare function clearJWSSession(event: H3Event, config: Partial<SessionConfigJWS>): Promise<void>;
183
208
 
184
209
  export { JWEEncryptOptions, JWKSet, JWK_Private, JWK_Public, JWK_Symmetric, JWTClaimValidationOptions, JWTClaims, clearJWESession, clearJWSSession, getJWESession, getJWSSession, sealJWESession, signJWSSession, unsealJWESession, updateJWESession, updateJWSSession, useJWESession, useJWSSession, verifyJWSSession };
185
- export type { SessionData, SessionJWE, SessionJWEConfig, SessionJWS, SessionJWSConfig, SessionManager };
210
+ export type { SessionConfigJWE, SessionConfigJWS, SessionData, SessionHooksJWE, SessionHooksJWS, SessionJWE, SessionJWEConfig, SessionJWS, SessionJWSConfig, SessionManager };
package/dist/h3.mjs CHANGED
@@ -1,13 +1,13 @@
1
- export { k as deriveJWKFromPassword, h as exportJWKToPEM, c as generateJWK, f as importJWKFromPEM } from './shared/unjwt.Vg02PZWw.mjs';
1
+ export { k as deriveJWKFromPassword, h as exportJWKToPEM, c as generateJWK, f as importJWKFromPEM } from './shared/unjwt.epPK1KFr.mjs';
2
2
  import { isEvent, setCookie } from 'h3';
3
3
  import { parse } from 'cookie-es';
4
- import { e as encrypt, d as decrypt } from './shared/unjwt.CXl9FD-V.mjs';
5
- import { p as isSymmetricJWK, q as isPrivateJWK, u as isAsymmetricJWK } from './shared/unjwt.DCiTNyCM.mjs';
6
- import { s as sign, v as verify } from './shared/unjwt.DmRAjMp8.mjs';
4
+ import { e as encrypt, d as decrypt } from './shared/unjwt.CiBEuvtQ.mjs';
5
+ import { p as isSymmetricJWK, q as isPrivateJWK, u as isPublicJWK } from './shared/unjwt.C86hA0o-.mjs';
6
+ import { s as sign, v as verify } from './shared/unjwt.D-FATaw1.mjs';
7
7
 
8
8
  /**
9
9
  * This is a fork of h3 library's session utility functions.
10
- * @source https://github.com/h3js/h3/blob/b4dce71c256911335f3402d09f30ffad120ad61a/test/session.test.ts
10
+ * @source https://github.com/h3js/h3/blob/b4dce71c256911335f3402d09f30ffad120ad61a/src/utils/session.ts
11
11
  * @license MIT https://github.com/h3js/h3/blob/b4dce71c256911335f3402d09f30ffad120ad61a/LICENSE
12
12
  */
13
13
  const kGetSessionPromise$1 = Symbol("h3_jwe_getSession");
@@ -57,9 +57,15 @@ async function getJWESession(event, config) {
57
57
  }
58
58
  const existingSession = event.context.sessions[sessionName];
59
59
  if (existingSession) {
60
- return existingSession.expiresAt === void 0 ? existingSession[kGetSessionPromise$1] || existingSession : existingSession.expiresAt < Date.now() && isEvent(event) ? clearJWESession(event, config).then(
61
- () => getJWESession(event, config)
62
- ) : existingSession[kGetSessionPromise$1] || existingSession;
60
+ const session2 = existingSession[kGetSessionPromise$1] ? await existingSession[kGetSessionPromise$1] : existingSession;
61
+ if (session2.expiresAt !== void 0 && session2.expiresAt < Date.now() && isEvent(event)) {
62
+ await config.hooks?.onExpire?.(event, void 0, config);
63
+ return clearJWESession(event, config).then(
64
+ () => getJWESession(event, config)
65
+ );
66
+ }
67
+ await config.hooks?.onRead?.(session2, event, config);
68
+ return session2;
63
69
  }
64
70
  const session = {
65
71
  id: "",
@@ -83,7 +89,13 @@ async function getJWESession(event, config) {
83
89
  }
84
90
  }
85
91
  if (jweToken) {
86
- const promise = unsealJWESession(event, config, jweToken).catch(() => {
92
+ const promise = unsealJWESession(event, config, jweToken).catch(async (error_) => {
93
+ if (error_ instanceof Error && (error_.message.includes("Token has expired") || error_.message.includes("Token is too old"))) {
94
+ await config.hooks?.onExpire?.(event, error_, config);
95
+ return void 0;
96
+ }
97
+ await config.hooks?.onError?.(event, error_, config);
98
+ return void 0;
87
99
  }).then((unsealed) => {
88
100
  if (unsealed) {
89
101
  Object.assign(session, unsealed);
@@ -100,12 +112,12 @@ async function getJWESession(event, config) {
100
112
  "Cannot initialize a new session. Use `useSession(event)` within the main handler."
101
113
  );
102
114
  }
103
- session.id = config.generateId?.() ?? (config.crypto || crypto).randomUUID();
104
- session.createdAt = config.jwe?.encryptOptions?.currentDate === void 0 ? Date.now() : config.jwe.encryptOptions.currentDate.getTime();
115
+ session.id = config.generateId?.() ?? crypto.randomUUID();
116
+ session.createdAt = config.jwe?.encryptOptions?.currentDate?.getTime() ?? Date.now();
105
117
  session.expiresAt = config.maxAge ? session.createdAt + config.maxAge * 1e3 : void 0;
106
- session.data = /* @__PURE__ */ Object.create(null);
107
118
  await updateJWESession(event, config);
108
119
  }
120
+ await config.hooks?.onRead?.(session, event, config);
109
121
  return session;
110
122
  }
111
123
  function _getReqHeader$1(event, name) {
@@ -127,6 +139,7 @@ async function updateJWESession(event, config, update) {
127
139
  }
128
140
  if (update) {
129
141
  Object.assign(session.data, update);
142
+ await config.hooks?.onUpdate?.(session, event, config);
130
143
  }
131
144
  if (config.cookie !== false) {
132
145
  const sealed = await sealJWESession(event, config);
@@ -142,12 +155,7 @@ async function updateJWESession(event, config, update) {
142
155
  return session;
143
156
  }
144
157
  async function sealJWESession(event, config) {
145
- const key = getEncryptKey(config.secret);
146
- if (typeof key !== "string" && !isSymmetricJWK(key) && !isPrivateJWK(key)) {
147
- throw new Error(
148
- "Session: JWE secret must be a password string or a private JWK."
149
- );
150
- }
158
+ const key = getEncryptKey(config.key || config.secret);
151
159
  const sessionName = config.name || DEFAULT_NAME$1;
152
160
  const session = event.context.sessions?.[sessionName] || await getJWESession(event, config);
153
161
  const iatSeconds = Math.floor(session.createdAt / 1e3);
@@ -160,25 +168,30 @@ async function sealJWESession(event, config) {
160
168
  if (expSeconds) {
161
169
  payload.exp = expSeconds;
162
170
  }
171
+ let typ = void 0;
172
+ if (config.jwe?.encryptOptions?.protectedHeader?.typ && typeof config.jwe.encryptOptions.protectedHeader.typ === "string" && config.jwe.encryptOptions.protectedHeader.typ.toLowerCase().includes("jwt")) {
173
+ typ = config.jwe.encryptOptions.protectedHeader.typ;
174
+ }
163
175
  const token = await encrypt(payload, key, {
164
176
  ...config.jwe?.encryptOptions,
177
+ expiresIn: void 0,
178
+ // controlled via 'exp' claim
165
179
  protectedHeader: {
166
180
  ...config.jwe?.encryptOptions?.protectedHeader,
167
- typ: "JWT",
181
+ kid: typeof key === "string" ? void 0 : key.kid,
182
+ typ: typ || "JWT",
168
183
  cty: "application/json"
169
184
  }
170
185
  });
171
186
  return token;
172
187
  }
173
188
  async function unsealJWESession(_event, config, sealed) {
174
- const key = getDecryptKey(config.secret);
175
- if (typeof key !== "string" && !isSymmetricJWK(key) && !isPrivateJWK(key)) {
176
- throw new Error(
177
- "Session: JWE secret must be a password string or a private JWK."
178
- );
179
- }
189
+ const key = getDecryptKey(config.key || config.secret);
180
190
  const alg = config.jwe?.encryptOptions?.alg;
181
191
  const enc = config.jwe?.encryptOptions?.enc;
192
+ if (config.jwe?.encryptOptions?.protectedHeader?.typ && typeof config.jwe.encryptOptions.protectedHeader.typ === "string" && config.jwe.encryptOptions.protectedHeader.typ.toLowerCase().includes("jwt")) {
193
+ config.jwe.encryptOptions.protectedHeader.typ;
194
+ }
182
195
  const { payload } = await decrypt(sealed, key, {
183
196
  ...config.jwe?.decryptOptions,
184
197
  requiredClaims: [
@@ -211,7 +224,7 @@ async function unsealJWESession(_event, config, sealed) {
211
224
  data: data && typeof data === "object" ? data : /* @__PURE__ */ Object.create(null)
212
225
  };
213
226
  }
214
- function clearJWESession(event, config) {
227
+ async function clearJWESession(event, config) {
215
228
  const sessionName = config.name || DEFAULT_NAME$1;
216
229
  if (event.context.sessions?.[sessionName]) {
217
230
  delete event.context.sessions[sessionName];
@@ -219,31 +232,57 @@ function clearJWESession(event, config) {
219
232
  setCookie(event, sessionName, "", {
220
233
  ...DEFAULT_COOKIE$1,
221
234
  ...config.cookie,
222
- // Optionally set expires in the past
223
- expires: /* @__PURE__ */ new Date(0)
235
+ expires: /* @__PURE__ */ new Date(0),
236
+ maxAge: void 0
224
237
  });
225
- return Promise.resolve();
238
+ await config.hooks?.onClear?.(event, config);
226
239
  }
227
- function getEncryptKey(secret) {
228
- if (typeof secret === "string") {
229
- return secret;
230
- } else if (isSymmetricJWK(secret)) {
231
- return secret;
240
+ function getEncryptKey(key) {
241
+ if (!key) {
242
+ throw new Error("Session: JWE key is required.");
243
+ }
244
+ let _key;
245
+ if (typeof key === "string") {
246
+ _key = key;
247
+ } else if (isSymmetricJWK(key) || isPrivateJWK(key)) {
248
+ _key = key;
249
+ } else if ("publicKey" in key && isPublicJWK(key.publicKey)) {
250
+ _key = key.publicKey;
251
+ } else if ("privateKey" in key && isPrivateJWK(key.privateKey)) {
252
+ _key = key.privateKey;
253
+ }
254
+ if (!_key) {
255
+ throw new Error(
256
+ "Session: Invalid JWE key. It must be a password string or valid JWK.",
257
+ { cause: key }
258
+ );
232
259
  }
233
- return secret.publicKey || secret.privateKey;
260
+ return _key;
234
261
  }
235
- function getDecryptKey(secret) {
236
- if (typeof secret === "string") {
237
- return secret;
238
- } else if ("privateKey" in secret) {
239
- return secret.privateKey;
262
+ function getDecryptKey(key) {
263
+ if (!key) {
264
+ throw new Error("Session: JWE key is required.");
265
+ }
266
+ let _key = void 0;
267
+ if (typeof key === "string") {
268
+ _key = key;
269
+ } else if (isSymmetricJWK(key)) {
270
+ _key = key;
271
+ } else if ("privateKey" in key) {
272
+ _key = key.privateKey;
273
+ }
274
+ if (!_key) {
275
+ throw new Error(
276
+ "Session: Invalid JWE key. It must be a password string or a valid private JWK.",
277
+ { cause: key }
278
+ );
240
279
  }
241
- return secret;
280
+ return _key;
242
281
  }
243
282
 
244
283
  /**
245
284
  * This is a fork of h3 library's session utility functions.
246
- * @source https://github.com/h3js/h3/blob/b4dce71c256911335f3402d09f30ffad120ad61a/test/session.test.ts
285
+ * @source https://github.com/h3js/h3/blob/b4dce71c256911335f3402d09f30ffad120ad61a/src/utils/session.ts
247
286
  * @license MIT https://github.com/h3js/h3/blob/b4dce71c256911335f3402d09f30ffad120ad61a/LICENSE
248
287
  */
249
288
  const kGetSessionPromise = Symbol("h3_jws_getSession");
@@ -293,9 +332,15 @@ async function getJWSSession(event, config) {
293
332
  }
294
333
  const existingSession = event.context.sessions[sessionName];
295
334
  if (existingSession) {
296
- return existingSession.expiresAt === void 0 ? existingSession[kGetSessionPromise] || existingSession : existingSession.expiresAt < Date.now() && isEvent(event) ? clearJWSSession(event, config).then(
297
- () => getJWSSession(event, config)
298
- ) : existingSession[kGetSessionPromise] || existingSession;
335
+ const session2 = existingSession[kGetSessionPromise] ? await existingSession[kGetSessionPromise] : existingSession;
336
+ if (session2.expiresAt !== void 0 && session2.expiresAt < Date.now() && isEvent(event)) {
337
+ await config.hooks?.onExpire?.(event, void 0, config);
338
+ return clearJWSSession(event, config).then(
339
+ () => getJWSSession(event, config)
340
+ );
341
+ }
342
+ await config.hooks?.onRead?.(session2, event, config);
343
+ return session2;
299
344
  }
300
345
  const session = {
301
346
  id: "",
@@ -319,7 +364,13 @@ async function getJWSSession(event, config) {
319
364
  }
320
365
  }
321
366
  if (token) {
322
- const promise = verifyJWSSession(event, config, token).catch(() => {
367
+ const promise = verifyJWSSession(event, config, token).catch(async (error_) => {
368
+ if (error_ instanceof Error && (error_.message.includes("Token has expired") || error_.message.includes("Token is too old"))) {
369
+ await config.hooks?.onExpire?.(event, error_, config);
370
+ return void 0;
371
+ }
372
+ await config.hooks?.onError?.(event, error_, config);
373
+ return void 0;
323
374
  }).then((unsealed) => {
324
375
  if (unsealed) {
325
376
  Object.assign(session, unsealed);
@@ -336,12 +387,12 @@ async function getJWSSession(event, config) {
336
387
  "Cannot initialize a new session outside main handler. Use `useJWSSession(event)` properly."
337
388
  );
338
389
  }
339
- session.id = config.generateId?.() ?? (config.crypto || globalThis.crypto).randomUUID();
340
- session.createdAt = config.jws?.signOptions?.currentDate === void 0 ? Date.now() : config.jws.signOptions.currentDate.getTime();
390
+ session.id = config.generateId?.() ?? crypto.randomUUID();
391
+ session.createdAt = config.jws?.signOptions?.currentDate?.getTime() ?? Date.now();
341
392
  session.expiresAt = config.maxAge ? session.createdAt + config.maxAge * 1e3 : void 0;
342
- session.data = /* @__PURE__ */ Object.create(null);
343
393
  await updateJWSSession(event, config);
344
394
  }
395
+ await config.hooks?.onRead?.(session, event, config);
345
396
  return session;
346
397
  }
347
398
  function _getReqHeader(event, name) {
@@ -363,6 +414,7 @@ async function updateJWSSession(event, config, update) {
363
414
  }
364
415
  if (update) {
365
416
  Object.assign(session.data, update);
417
+ await config.hooks?.onUpdate?.(session, event, config);
366
418
  }
367
419
  if (config.cookie !== false) {
368
420
  const token = await signJWSSession(event, config);
@@ -376,9 +428,6 @@ async function updateJWSSession(event, config, update) {
376
428
  }
377
429
  async function signJWSSession(event, config) {
378
430
  const key = getSignKey(config.key);
379
- if (isAsymmetricJWK(key) && !isPrivateJWK(key)) {
380
- throw new Error("Session: JWS key cannot be a public asymmetric JWK.");
381
- }
382
431
  const sessionName = config.name || DEFAULT_NAME;
383
432
  const session = event.context.sessions?.[sessionName] || await getJWSSession(event, config);
384
433
  const iatSeconds = Math.floor(session.createdAt / 1e3);
@@ -391,11 +440,19 @@ async function signJWSSession(event, config) {
391
440
  if (expSeconds) {
392
441
  payload.exp = expSeconds;
393
442
  }
443
+ let typ = void 0;
444
+ if (config.jws?.signOptions?.protectedHeader?.typ && typeof config.jws.signOptions.protectedHeader.typ === "string" && config.jws.signOptions.protectedHeader.typ.toLowerCase().includes("jwt")) {
445
+ typ = config.jws.signOptions.protectedHeader.typ;
446
+ }
394
447
  const token = await sign(payload, key, {
395
448
  ...config.jws?.signOptions,
449
+ expiresIn: void 0,
450
+ // controlled via 'exp' claim
396
451
  protectedHeader: {
397
452
  ...config.jws?.signOptions?.protectedHeader,
398
- typ: "JWT"
453
+ kid: key.kid,
454
+ typ: typ || "JWT",
455
+ cty: "application/json"
399
456
  }
400
457
  });
401
458
  return token;
@@ -403,8 +460,9 @@ async function signJWSSession(event, config) {
403
460
  async function verifyJWSSession(_event, config, token) {
404
461
  const alg = config.jws?.signOptions?.alg;
405
462
  const jwk = getVerifyKey(config.key);
406
- if (isAsymmetricJWK(jwk) && isPrivateJWK(jwk)) {
407
- throw new Error("Session: JWS key cannot be a private asymmetric JWK.");
463
+ let typ = void 0;
464
+ if (config.jws?.signOptions?.protectedHeader?.typ && typeof config.jws.signOptions.protectedHeader.typ === "string" && config.jws.signOptions.protectedHeader.typ.toLowerCase().includes("jwt")) {
465
+ typ = config.jws.signOptions.protectedHeader.typ;
408
466
  }
409
467
  const { payload } = await verify(token, jwk, {
410
468
  ...config.jws?.verifyOptions,
@@ -415,7 +473,7 @@ async function verifyJWSSession(_event, config, token) {
415
473
  "jti",
416
474
  "iat"
417
475
  ],
418
- typ: "JWT",
476
+ typ: typ || "JWT",
419
477
  algorithms: alg ? [alg] : void 0,
420
478
  forceUint8Array: false,
421
479
  validateJWT: true
@@ -432,7 +490,7 @@ async function verifyJWSSession(_event, config, token) {
432
490
  data: data && typeof data === "object" ? data : /* @__PURE__ */ Object.create(null)
433
491
  };
434
492
  }
435
- function clearJWSSession(event, config) {
493
+ async function clearJWSSession(event, config) {
436
494
  const sessionName = config.name || DEFAULT_NAME;
437
495
  if (event.context.sessions?.[sessionName]) {
438
496
  delete event.context.sessions[sessionName];
@@ -440,21 +498,51 @@ function clearJWSSession(event, config) {
440
498
  setCookie(event, sessionName, "", {
441
499
  ...DEFAULT_COOKIE,
442
500
  ...config.cookie,
443
- expires: /* @__PURE__ */ new Date(0)
501
+ expires: /* @__PURE__ */ new Date(0),
502
+ maxAge: void 0
444
503
  });
445
- return Promise.resolve();
504
+ await config.hooks?.onClear?.(event, config);
446
505
  }
447
506
  function getSignKey(key) {
448
- if ("privateKey" in key) {
449
- return key.privateKey;
507
+ if (!key) {
508
+ throw new Error("Session: JWS key is required.");
509
+ }
510
+ let _key = void 0;
511
+ if (isSymmetricJWK(key)) {
512
+ _key = key;
513
+ } else if ("privateKey" in key && isPrivateJWK(key.privateKey)) {
514
+ _key = key.privateKey;
450
515
  }
451
- return key;
516
+ if (!_key) {
517
+ throw new Error(
518
+ "Session: Invalid JWS key. It must be a symmetric JWK or a private JWK.",
519
+ { cause: key }
520
+ );
521
+ }
522
+ return _key;
452
523
  }
453
524
  function getVerifyKey(key) {
454
- if ("publicKey" in key) {
455
- return Array.isArray(key.publicKey) ? { keys: key.publicKey } : key.publicKey;
525
+ if (!key) {
526
+ throw new Error("Session: JWS key is required.");
527
+ }
528
+ let _key = void 0;
529
+ if (isSymmetricJWK(key)) {
530
+ _key = key;
531
+ } else if ("publicKey" in key && isPublicJWK(key.publicKey)) {
532
+ _key = key.publicKey;
533
+ } else if ("publicKey" in key && Array.isArray(key.publicKey)) {
534
+ const keys = key.publicKey.filter((k) => isPublicJWK(k));
535
+ if (keys && keys.length > 0) {
536
+ _key = { keys };
537
+ }
538
+ }
539
+ if (!_key) {
540
+ throw new Error(
541
+ "Session: Invalid JWS key. It must be a symmetric JWK or a public JWK/set.",
542
+ { cause: key }
543
+ );
456
544
  }
457
- return key;
545
+ return _key;
458
546
  }
459
547
 
460
548
  export { clearJWESession, clearJWSSession, getJWESession, getJWSSession, sealJWESession, signJWSSession, unsealJWESession, updateJWESession, updateJWSSession, useJWESession, useJWSSession, verifyJWSSession };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { i as utils } from './shared/unjwt.DCiTNyCM.mjs';
2
- export { j as jwe } from './shared/unjwt.CXl9FD-V.mjs';
3
- export { j as jwk } from './shared/unjwt.Vg02PZWw.mjs';
4
- export { j as jws } from './shared/unjwt.DmRAjMp8.mjs';
1
+ export { i as utils } from './shared/unjwt.C86hA0o-.mjs';
2
+ export { j as jwe } from './shared/unjwt.CiBEuvtQ.mjs';
3
+ export { j as jwk } from './shared/unjwt.epPK1KFr.mjs';
4
+ export { j as jws } from './shared/unjwt.D-FATaw1.mjs';
package/dist/jwe.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import './shared/unjwt.Vg02PZWw.mjs';
2
- import './shared/unjwt.DCiTNyCM.mjs';
3
- export { d as decrypt, e as encrypt } from './shared/unjwt.CXl9FD-V.mjs';
1
+ import './shared/unjwt.epPK1KFr.mjs';
2
+ import './shared/unjwt.C86hA0o-.mjs';
3
+ export { d as decrypt, e as encrypt } from './shared/unjwt.CiBEuvtQ.mjs';
package/dist/jwk.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import './shared/unjwt.DCiTNyCM.mjs';
2
- export { k as deriveJWKFromPassword, m as deriveKeyFromPassword, h as exportJWKToPEM, n as exportKey, c as generateJWK, l as generateKey, b as getJWKFromSet, f as importJWKFromPEM, i as importKey, u as unwrapKey, w as wrapKey } from './shared/unjwt.Vg02PZWw.mjs';
1
+ import './shared/unjwt.C86hA0o-.mjs';
2
+ export { k as deriveJWKFromPassword, m as deriveKeyFromPassword, h as exportJWKToPEM, n as exportKey, c as generateJWK, l as generateKey, b as getJWKFromSet, f as importJWKFromPEM, i as importKey, u as unwrapKey, w as wrapKey } from './shared/unjwt.epPK1KFr.mjs';
package/dist/jws.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import './shared/unjwt.Vg02PZWw.mjs';
2
- import './shared/unjwt.DCiTNyCM.mjs';
3
- export { s as sign, v as verify } from './shared/unjwt.DmRAjMp8.mjs';
1
+ import './shared/unjwt.epPK1KFr.mjs';
2
+ import './shared/unjwt.C86hA0o-.mjs';
3
+ export { s as sign, v as verify } from './shared/unjwt.D-FATaw1.mjs';
@@ -435,4 +435,4 @@ const index = {
435
435
  validateJwtClaims: validateJwtClaims
436
436
  };
437
437
 
438
- export { maybeArray as A, isPublicJWK as B, isJWTContent as C, decodeMaybeJWTString as D, computeExpiresInSeconds as E, computeMaxTokenAgeSeconds as F, isJWK as a, applyTypCtyDefaults as b, computeJwtTimeClaims as c, base64UrlEncode as d, base64UrlDecode as e, decodePayloadFromBytes as f, getPlaintextBytes as g, validateJwtClaims as h, index as i, isCryptoKeyPair as j, isCryptoKey as k, textDecoder as l, isJWKSet as m, decodePayloadFromB64UrlSegment as n, validateCriticalHeadersJWS as o, isSymmetricJWK as p, isPrivateJWK as q, randomBytes as r, sanitizeObject as s, textEncoder as t, isAsymmetricJWK as u, validateCriticalHeadersJWE as v, concatUint8Arrays as w, base64Decode as x, base64Encode as y, assertCryptoKey as z };
438
+ export { isAsymmetricJWK as A, maybeArray as B, isJWTContent as C, decodeMaybeJWTString as D, computeExpiresInSeconds as E, computeMaxTokenAgeSeconds as F, isJWK as a, applyTypCtyDefaults as b, computeJwtTimeClaims as c, base64UrlEncode as d, base64UrlDecode as e, decodePayloadFromBytes as f, getPlaintextBytes as g, validateJwtClaims as h, index as i, isCryptoKeyPair as j, isCryptoKey as k, textDecoder as l, isJWKSet as m, decodePayloadFromB64UrlSegment as n, validateCriticalHeadersJWS as o, isSymmetricJWK as p, isPrivateJWK as q, randomBytes as r, sanitizeObject as s, textEncoder as t, isPublicJWK as u, validateCriticalHeadersJWE as v, concatUint8Arrays as w, base64Decode as x, base64Encode as y, assertCryptoKey as z };
@@ -1,5 +1,5 @@
1
- import { i as importKey, e as encryptKey, g as generateIV, a as encrypt$1, u as unwrapKey, d as decrypt$1 } from './unjwt.Vg02PZWw.mjs';
2
- import { r as randomBytes, a as isJWK, s as sanitizeObject, b as applyTypCtyDefaults, c as computeJwtTimeClaims, g as getPlaintextBytes, d as base64UrlEncode, t as textEncoder, e as base64UrlDecode, f as decodePayloadFromBytes, v as validateCriticalHeadersJWE, h as validateJwtClaims, j as isCryptoKeyPair, k as isCryptoKey } from './unjwt.DCiTNyCM.mjs';
1
+ import { i as importKey, e as encryptKey, g as generateIV, a as encrypt$1, u as unwrapKey, d as decrypt$1 } from './unjwt.epPK1KFr.mjs';
2
+ import { r as randomBytes, a as isJWK, s as sanitizeObject, b as applyTypCtyDefaults, c as computeJwtTimeClaims, g as getPlaintextBytes, d as base64UrlEncode, t as textEncoder, e as base64UrlDecode, f as decodePayloadFromBytes, v as validateCriticalHeadersJWE, h as validateJwtClaims, j as isCryptoKeyPair, k as isCryptoKey } from './unjwt.C86hA0o-.mjs';
3
3
 
4
4
  async function encrypt(payload, key, options = {}) {
5
5
  const {
@@ -1,5 +1,5 @@
1
- import { i as importKey, s as sign$1, b as getJWKFromSet, v as verify$1 } from './unjwt.Vg02PZWw.mjs';
2
- import { s as sanitizeObject, a as isJWK, b as applyTypCtyDefaults, c as computeJwtTimeClaims, d as base64UrlEncode, t as textEncoder, l as textDecoder, e as base64UrlDecode, m as isJWKSet, n as decodePayloadFromB64UrlSegment, o as validateCriticalHeadersJWS, h as validateJwtClaims } from './unjwt.DCiTNyCM.mjs';
1
+ import { i as importKey, s as sign$1, b as getJWKFromSet, v as verify$1 } from './unjwt.epPK1KFr.mjs';
2
+ import { s as sanitizeObject, a as isJWK, b as applyTypCtyDefaults, c as computeJwtTimeClaims, d as base64UrlEncode, t as textEncoder, l as textDecoder, e as base64UrlDecode, m as isJWKSet, n as decodePayloadFromB64UrlSegment, o as validateCriticalHeadersJWS, h as validateJwtClaims } from './unjwt.C86hA0o-.mjs';
3
3
 
4
4
  async function sign(payload, key, options = {}) {
5
5
  const { protectedHeader: additionalProtectedHeader } = options;
@@ -1,4 +1,4 @@
1
- import { k as isCryptoKey, w as concatUint8Arrays, d as base64UrlEncode, x as base64Decode, y as base64Encode, t as textEncoder, s as sanitizeObject, z as assertCryptoKey, a as isJWK, e as base64UrlDecode, u as isAsymmetricJWK, r as randomBytes, m as isJWKSet } from './unjwt.DCiTNyCM.mjs';
1
+ import { k as isCryptoKey, w as concatUint8Arrays, d as base64UrlEncode, x as base64Decode, y as base64Encode, t as textEncoder, s as sanitizeObject, z as assertCryptoKey, a as isJWK, e as base64UrlDecode, A as isAsymmetricJWK, r as randomBytes, m as isJWKSet } from './unjwt.C86hA0o-.mjs';
2
2
 
3
3
  /**
4
4
  * This is a fork of Jose library's utility functions.
@@ -1482,7 +1482,10 @@ async function generateJWK(alg, jwkParams, options = {}) {
1482
1482
  } = options;
1483
1483
  return generateKey(alg, {
1484
1484
  ...opts,
1485
- toJWK: jwkParams || true
1485
+ toJWK: {
1486
+ kid: typeof jwkParams?.kid === "string" ? jwkParams.kid : crypto.randomUUID(),
1487
+ ...jwkParams
1488
+ }
1486
1489
  });
1487
1490
  }
1488
1491
  async function deriveKeyFromPassword(password, alg, options) {
package/dist/utils.mjs CHANGED
@@ -1 +1 @@
1
- export { b as applyTypCtyDefaults, z as assertCryptoKey, x as base64Decode, y as base64Encode, e as base64UrlDecode, d as base64UrlEncode, E as computeExpiresInSeconds, c as computeJwtTimeClaims, F as computeMaxTokenAgeSeconds, w as concatUint8Arrays, D as decodeMaybeJWTString, n as decodePayloadFromB64UrlSegment, f as decodePayloadFromBytes, g as getPlaintextBytes, u as isAsymmetricJWK, k as isCryptoKey, j as isCryptoKeyPair, a as isJWK, m as isJWKSet, C as isJWTContent, q as isPrivateJWK, B as isPublicJWK, p as isSymmetricJWK, A as maybeArray, r as randomBytes, s as sanitizeObject, l as textDecoder, t as textEncoder, v as validateCriticalHeadersJWE, o as validateCriticalHeadersJWS, h as validateJwtClaims } from './shared/unjwt.DCiTNyCM.mjs';
1
+ export { b as applyTypCtyDefaults, z as assertCryptoKey, x as base64Decode, y as base64Encode, e as base64UrlDecode, d as base64UrlEncode, E as computeExpiresInSeconds, c as computeJwtTimeClaims, F as computeMaxTokenAgeSeconds, w as concatUint8Arrays, D as decodeMaybeJWTString, n as decodePayloadFromB64UrlSegment, f as decodePayloadFromBytes, g as getPlaintextBytes, A as isAsymmetricJWK, k as isCryptoKey, j as isCryptoKeyPair, a as isJWK, m as isJWKSet, C as isJWTContent, q as isPrivateJWK, u as isPublicJWK, p as isSymmetricJWK, B as maybeArray, r as randomBytes, s as sanitizeObject, l as textDecoder, t as textEncoder, v as validateCriticalHeadersJWE, o as validateCriticalHeadersJWS, h as validateJwtClaims } from './shared/unjwt.C86hA0o-.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unjwt",
3
- "version": "0.5.21",
3
+ "version": "0.5.23",
4
4
  "description": "Low-level JWT utilities",
5
5
  "repository": "sandros94/unjwt",
6
6
  "license": "MIT",
@@ -44,7 +44,7 @@
44
44
  ],
45
45
  "scripts": {
46
46
  "build": "unbuild",
47
- "dev": "vitest dev",
47
+ "dev": "bun --watch ./playground/main.ts",
48
48
  "lint": "eslint . && prettier -c .",
49
49
  "lint:fix": "automd && eslint . --fix && prettier -w .",
50
50
  "prepack": "pnpm build",