unjwt 0.5.24 → 0.5.26
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 +7 -5
- package/dist/adapters/h3.d.mts +48 -10
- package/dist/adapters/h3.mjs +89 -31
- package/dist/adapters/h3v2.d.mts +48 -10
- package/dist/adapters/h3v2.mjs +90 -128
- package/package.json +11 -5
package/README.md
CHANGED
|
@@ -15,7 +15,8 @@ A collection of low-level JWT ([RFC 7519](https://datatracker.ietf.org/doc/html/
|
|
|
15
15
|
- **JWK (JSON Web Key, [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517))**: generate, import, export, wrap, and unwrap keys in JWK format or as `CryptoKey` objects.
|
|
16
16
|
|
|
17
17
|
- **Adapters** for popular frameworks (PRs welcome for more!):
|
|
18
|
-
- [H3 (Nuxt, Nitro)](./src/adapters/
|
|
18
|
+
- [H3 v1 (Nuxt v4, Nitro v2)](./src/adapters/h3v1/): `useJWSSession()`, `useJWESession()`
|
|
19
|
+
- [H3 v2 (Nuxt v5, Nitro v3)](./src/adapters/h3v2/): `useJWSSession()`, `useJWESession()`
|
|
19
20
|
|
|
20
21
|
## Table of Contents
|
|
21
22
|
|
|
@@ -25,7 +26,8 @@ A collection of low-level JWT ([RFC 7519](https://datatracker.ietf.org/doc/html/
|
|
|
25
26
|
- [JWK](#jwk-json-web-key-rfc-7517)
|
|
26
27
|
- [Utility Functions](#utility-functions)
|
|
27
28
|
- [Adapters](#adapters)
|
|
28
|
-
- [H3 (Nuxt, Nitro)](#h3-nuxt-nitro)
|
|
29
|
+
- [H3 v1 (Nuxt up to v4, Nitro up to v2)](#h3-v1-nuxt-v4-nitro-v2)
|
|
30
|
+
- [H3 v2 (Nuxt v5, Nitro v3)](#h3-v2-nuxt-v5-nitro-v3)
|
|
29
31
|
- [Development](#development)
|
|
30
32
|
- [Credits](#credits)
|
|
31
33
|
- [License](#license)
|
|
@@ -616,7 +618,7 @@ Both helpers expose the same API: read `session.id` / `session.data`, call `sess
|
|
|
616
618
|
import { defineEventHandler } from "h3";
|
|
617
619
|
import { useJWESession, useJWSSession, generateJWK } from "unjwt/adapters/h3v1";
|
|
618
620
|
|
|
619
|
-
const keys = await generateJWK("RS256");
|
|
621
|
+
const keys = await generateJWK("RS256"); // Make sure to persist these keys somewhere!
|
|
620
622
|
|
|
621
623
|
export default defineEventHandler(async (event) => {
|
|
622
624
|
const privateSession = await useJWESession(event, {
|
|
@@ -663,7 +665,7 @@ import {
|
|
|
663
665
|
generateJWK,
|
|
664
666
|
} from "unjwt/adapters/h3v2";
|
|
665
667
|
|
|
666
|
-
const atJwk = await generateJWK("RS256");
|
|
668
|
+
const atJwk = await generateJWK("RS256"); // Make sure to persist these keys somewhere!
|
|
667
669
|
|
|
668
670
|
const jweOptions = {
|
|
669
671
|
key: "refresh_token_secret",
|
|
@@ -675,7 +677,7 @@ const jwsOptions = {
|
|
|
675
677
|
name: "access_token",
|
|
676
678
|
maxAge: 15 * 60, // 15 minutes
|
|
677
679
|
hooks: {
|
|
678
|
-
async onExpire(event,
|
|
680
|
+
async onExpire({ event, config }) {
|
|
679
681
|
const refreshSession = await getJWESession(event, jweOptions);
|
|
680
682
|
if (!refreshSession.data.sub) {
|
|
681
683
|
// no valid refresh session, nothing to do
|
package/dist/adapters/h3.d.mts
CHANGED
|
@@ -24,11 +24,30 @@ interface SessionJWE<T extends SessionDataT$1 = SessionDataT$1> {
|
|
|
24
24
|
[kGetSessionPromise$1]?: Promise<SessionJWE<T>>;
|
|
25
25
|
}
|
|
26
26
|
interface SessionHooksJWE {
|
|
27
|
-
onRead?: (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
onRead?: (args: {
|
|
28
|
+
session: SessionJWE;
|
|
29
|
+
event: H3Event;
|
|
30
|
+
config: SessionConfigJWE;
|
|
31
|
+
}) => void | Promise<void>;
|
|
32
|
+
onUpdate?: (args: {
|
|
33
|
+
session: SessionJWE;
|
|
34
|
+
event: H3Event;
|
|
35
|
+
config: SessionConfigJWE;
|
|
36
|
+
}) => void | Promise<void>;
|
|
37
|
+
onClear?: (args: {
|
|
38
|
+
event: H3Event;
|
|
39
|
+
config: Partial<SessionConfigJWE>;
|
|
40
|
+
}) => void | Promise<void>;
|
|
41
|
+
onExpire?: (args: {
|
|
42
|
+
event: H3Event;
|
|
43
|
+
error: Error;
|
|
44
|
+
config: SessionConfigJWE;
|
|
45
|
+
}) => void | Promise<void>;
|
|
46
|
+
onError?: (args: {
|
|
47
|
+
event: H3Event;
|
|
48
|
+
error: any;
|
|
49
|
+
config: SessionConfigJWE;
|
|
50
|
+
}) => void | Promise<void>;
|
|
32
51
|
}
|
|
33
52
|
interface SessionManager<T extends SessionDataT$1 = SessionDataT$1> {
|
|
34
53
|
readonly id: string | undefined;
|
|
@@ -128,11 +147,30 @@ interface SessionJWS<T extends SessionDataT = SessionDataT> {
|
|
|
128
147
|
[kGetSessionPromise]?: Promise<SessionJWS<T>>;
|
|
129
148
|
}
|
|
130
149
|
interface SessionHooksJWS {
|
|
131
|
-
onRead?: (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
150
|
+
onRead?: (args: {
|
|
151
|
+
session: SessionJWS;
|
|
152
|
+
event: H3Event;
|
|
153
|
+
config: SessionConfigJWS;
|
|
154
|
+
}) => void | Promise<void>;
|
|
155
|
+
onUpdate?: (args: {
|
|
156
|
+
session: SessionJWS;
|
|
157
|
+
event: H3Event;
|
|
158
|
+
config: SessionConfigJWS;
|
|
159
|
+
}) => void | Promise<void>;
|
|
160
|
+
onClear?: (args: {
|
|
161
|
+
event: H3Event;
|
|
162
|
+
config: Partial<SessionConfigJWS>;
|
|
163
|
+
}) => void | Promise<void>;
|
|
164
|
+
onExpire?: (args: {
|
|
165
|
+
event: H3Event;
|
|
166
|
+
error: Error;
|
|
167
|
+
config: SessionConfigJWS;
|
|
168
|
+
}) => void | Promise<void>;
|
|
169
|
+
onError?: (args: {
|
|
170
|
+
event: H3Event;
|
|
171
|
+
error: any;
|
|
172
|
+
config: SessionConfigJWS;
|
|
173
|
+
}) => void | Promise<void>;
|
|
136
174
|
}
|
|
137
175
|
interface SessionConfigJWS {
|
|
138
176
|
/**
|
package/dist/adapters/h3.mjs
CHANGED
|
@@ -59,12 +59,22 @@ async function getJWESession(event, config) {
|
|
|
59
59
|
if (existingSession) {
|
|
60
60
|
const session2 = existingSession[kGetSessionPromise$1] ? await existingSession[kGetSessionPromise$1] : existingSession;
|
|
61
61
|
if (session2.expiresAt !== void 0 && session2.expiresAt < Date.now() && isEvent(event)) {
|
|
62
|
-
await config.hooks?.onExpire?.(
|
|
62
|
+
await config.hooks?.onExpire?.({
|
|
63
|
+
event,
|
|
64
|
+
error: new Error(
|
|
65
|
+
`JWT "exp" (Expiration Time) Claim validation failed: Token has expired (exp: ${new Date(session2.expiresAt * 1e3).toISOString()})`
|
|
66
|
+
),
|
|
67
|
+
config
|
|
68
|
+
});
|
|
63
69
|
return clearJWESession(event, config).then(
|
|
64
70
|
() => getJWESession(event, config)
|
|
65
71
|
);
|
|
66
72
|
}
|
|
67
|
-
await config.hooks?.onRead?.(
|
|
73
|
+
await config.hooks?.onRead?.({
|
|
74
|
+
event,
|
|
75
|
+
session: session2,
|
|
76
|
+
config
|
|
77
|
+
});
|
|
68
78
|
return session2;
|
|
69
79
|
}
|
|
70
80
|
const session = {
|
|
@@ -74,27 +84,35 @@ async function getJWESession(event, config) {
|
|
|
74
84
|
data: /* @__PURE__ */ Object.create(null)
|
|
75
85
|
};
|
|
76
86
|
event.context.sessions[sessionName] = session;
|
|
77
|
-
let
|
|
87
|
+
let token;
|
|
78
88
|
if (config.sessionHeader !== false) {
|
|
79
89
|
const headerName = typeof config.sessionHeader === "string" ? config.sessionHeader.toLowerCase() : `x-${sessionName.toLowerCase()}-session`;
|
|
80
90
|
const headerValue = _getReqHeader$1(event, headerName);
|
|
81
91
|
if (typeof headerValue === "string") {
|
|
82
|
-
|
|
92
|
+
token = headerValue.startsWith("Bearer ") ? headerValue.slice(7).trim() : headerValue;
|
|
83
93
|
}
|
|
84
94
|
}
|
|
85
|
-
if (!
|
|
95
|
+
if (!token) {
|
|
86
96
|
const cookieHeader = _getReqHeader$1(event, "cookie");
|
|
87
97
|
if (cookieHeader) {
|
|
88
|
-
|
|
98
|
+
token = parse(String(cookieHeader))[sessionName];
|
|
89
99
|
}
|
|
90
100
|
}
|
|
91
|
-
if (
|
|
92
|
-
const promise = unsealJWESession(event, config,
|
|
101
|
+
if (token) {
|
|
102
|
+
const promise = unsealJWESession(event, config, token).catch(async (error_) => {
|
|
93
103
|
if (error_ instanceof Error && (error_.message.includes("Token has expired") || error_.message.includes("Token is too old"))) {
|
|
94
|
-
await config.hooks?.onExpire?.(
|
|
104
|
+
await config.hooks?.onExpire?.({
|
|
105
|
+
event,
|
|
106
|
+
error: error_,
|
|
107
|
+
config
|
|
108
|
+
});
|
|
95
109
|
return void 0;
|
|
96
110
|
}
|
|
97
|
-
await config.hooks?.onError?.(
|
|
111
|
+
await config.hooks?.onError?.({
|
|
112
|
+
event,
|
|
113
|
+
error: error_,
|
|
114
|
+
config
|
|
115
|
+
});
|
|
98
116
|
return void 0;
|
|
99
117
|
}).then((unsealed) => {
|
|
100
118
|
if (unsealed) {
|
|
@@ -117,7 +135,11 @@ async function getJWESession(event, config) {
|
|
|
117
135
|
session.expiresAt = config.maxAge ? session.createdAt + config.maxAge * 1e3 : void 0;
|
|
118
136
|
await updateJWESession(event, config);
|
|
119
137
|
}
|
|
120
|
-
await config.hooks?.onRead?.(
|
|
138
|
+
await config.hooks?.onRead?.({
|
|
139
|
+
event,
|
|
140
|
+
session,
|
|
141
|
+
config
|
|
142
|
+
});
|
|
121
143
|
return session;
|
|
122
144
|
}
|
|
123
145
|
function _getReqHeader$1(event, name) {
|
|
@@ -139,7 +161,11 @@ async function updateJWESession(event, config, update) {
|
|
|
139
161
|
}
|
|
140
162
|
if (update) {
|
|
141
163
|
Object.assign(session.data, update);
|
|
142
|
-
await config.hooks?.onUpdate?.(
|
|
164
|
+
await config.hooks?.onUpdate?.({
|
|
165
|
+
event,
|
|
166
|
+
session,
|
|
167
|
+
config
|
|
168
|
+
});
|
|
143
169
|
}
|
|
144
170
|
if (config.cookie !== false) {
|
|
145
171
|
const sealed = await sealJWESession(event, config);
|
|
@@ -195,11 +221,11 @@ async function unsealJWESession(_event, config, sealed) {
|
|
|
195
221
|
const { payload } = await decrypt(sealed, key, {
|
|
196
222
|
...config.jwe?.decryptOptions,
|
|
197
223
|
requiredClaims: [
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
224
|
+
.../* @__PURE__ */ new Set([
|
|
225
|
+
...config.jwe?.decryptOptions?.requiredClaims || [],
|
|
226
|
+
"jti",
|
|
227
|
+
"iat"
|
|
228
|
+
])
|
|
203
229
|
],
|
|
204
230
|
maxTokenAge: config.maxAge,
|
|
205
231
|
algorithms: alg ? [alg] : void 0,
|
|
@@ -235,7 +261,10 @@ async function clearJWESession(event, config) {
|
|
|
235
261
|
expires: /* @__PURE__ */ new Date(0),
|
|
236
262
|
maxAge: void 0
|
|
237
263
|
});
|
|
238
|
-
await config.hooks?.onClear?.(
|
|
264
|
+
await config.hooks?.onClear?.({
|
|
265
|
+
event,
|
|
266
|
+
config
|
|
267
|
+
});
|
|
239
268
|
}
|
|
240
269
|
function getEncryptKey(key) {
|
|
241
270
|
if (!key) {
|
|
@@ -334,12 +363,22 @@ async function getJWSSession(event, config) {
|
|
|
334
363
|
if (existingSession) {
|
|
335
364
|
const session2 = existingSession[kGetSessionPromise] ? await existingSession[kGetSessionPromise] : existingSession;
|
|
336
365
|
if (session2.expiresAt !== void 0 && session2.expiresAt < Date.now() && isEvent(event)) {
|
|
337
|
-
await config.hooks?.onExpire?.(
|
|
366
|
+
await config.hooks?.onExpire?.({
|
|
367
|
+
event,
|
|
368
|
+
error: new Error(
|
|
369
|
+
`JWT "exp" (Expiration Time) Claim validation failed: Token has expired (exp: ${new Date(session2.expiresAt * 1e3).toISOString()})`
|
|
370
|
+
),
|
|
371
|
+
config
|
|
372
|
+
});
|
|
338
373
|
return clearJWSSession(event, config).then(
|
|
339
374
|
() => getJWSSession(event, config)
|
|
340
375
|
);
|
|
341
376
|
}
|
|
342
|
-
await config.hooks?.onRead?.(
|
|
377
|
+
await config.hooks?.onRead?.({
|
|
378
|
+
event,
|
|
379
|
+
session: session2,
|
|
380
|
+
config
|
|
381
|
+
});
|
|
343
382
|
return session2;
|
|
344
383
|
}
|
|
345
384
|
const session = {
|
|
@@ -354,7 +393,7 @@ async function getJWSSession(event, config) {
|
|
|
354
393
|
const headerName = typeof config.sessionHeader === "string" ? config.sessionHeader.toLowerCase() : `x-${sessionName.toLowerCase()}-session`;
|
|
355
394
|
const headerValue = _getReqHeader(event, headerName);
|
|
356
395
|
if (typeof headerValue === "string") {
|
|
357
|
-
token = headerValue;
|
|
396
|
+
token = headerValue.startsWith("Bearer ") ? headerValue.slice(7).trim() : headerValue;
|
|
358
397
|
}
|
|
359
398
|
}
|
|
360
399
|
if (!token) {
|
|
@@ -366,10 +405,18 @@ async function getJWSSession(event, config) {
|
|
|
366
405
|
if (token) {
|
|
367
406
|
const promise = verifyJWSSession(event, config, token).catch(async (error_) => {
|
|
368
407
|
if (error_ instanceof Error && (error_.message.includes("Token has expired") || error_.message.includes("Token is too old"))) {
|
|
369
|
-
await config.hooks?.onExpire?.(
|
|
408
|
+
await config.hooks?.onExpire?.({
|
|
409
|
+
event,
|
|
410
|
+
error: error_,
|
|
411
|
+
config
|
|
412
|
+
});
|
|
370
413
|
return void 0;
|
|
371
414
|
}
|
|
372
|
-
await config.hooks?.onError?.(
|
|
415
|
+
await config.hooks?.onError?.({
|
|
416
|
+
event,
|
|
417
|
+
error: error_,
|
|
418
|
+
config
|
|
419
|
+
});
|
|
373
420
|
return void 0;
|
|
374
421
|
}).then((unsealed) => {
|
|
375
422
|
if (unsealed) {
|
|
@@ -392,7 +439,11 @@ async function getJWSSession(event, config) {
|
|
|
392
439
|
session.expiresAt = config.maxAge ? session.createdAt + config.maxAge * 1e3 : void 0;
|
|
393
440
|
await updateJWSSession(event, config);
|
|
394
441
|
}
|
|
395
|
-
await config.hooks?.onRead?.(
|
|
442
|
+
await config.hooks?.onRead?.({
|
|
443
|
+
event,
|
|
444
|
+
session,
|
|
445
|
+
config
|
|
446
|
+
});
|
|
396
447
|
return session;
|
|
397
448
|
}
|
|
398
449
|
function _getReqHeader(event, name) {
|
|
@@ -414,7 +465,11 @@ async function updateJWSSession(event, config, update) {
|
|
|
414
465
|
}
|
|
415
466
|
if (update) {
|
|
416
467
|
Object.assign(session.data, update);
|
|
417
|
-
await config.hooks?.onUpdate?.(
|
|
468
|
+
await config.hooks?.onUpdate?.({
|
|
469
|
+
event,
|
|
470
|
+
session,
|
|
471
|
+
config
|
|
472
|
+
});
|
|
418
473
|
}
|
|
419
474
|
if (config.cookie !== false) {
|
|
420
475
|
const token = await signJWSSession(event, config);
|
|
@@ -467,11 +522,11 @@ async function verifyJWSSession(_event, config, token) {
|
|
|
467
522
|
const { payload } = await verify(token, jwk, {
|
|
468
523
|
...config.jws?.verifyOptions,
|
|
469
524
|
requiredClaims: [
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
525
|
+
.../* @__PURE__ */ new Set([
|
|
526
|
+
...config.jws?.verifyOptions?.requiredClaims || [],
|
|
527
|
+
"jti",
|
|
528
|
+
"iat"
|
|
529
|
+
])
|
|
475
530
|
],
|
|
476
531
|
typ: typ || "JWT",
|
|
477
532
|
algorithms: alg ? [alg] : void 0,
|
|
@@ -501,7 +556,10 @@ async function clearJWSSession(event, config) {
|
|
|
501
556
|
expires: /* @__PURE__ */ new Date(0),
|
|
502
557
|
maxAge: void 0
|
|
503
558
|
});
|
|
504
|
-
await config.hooks?.onClear?.(
|
|
559
|
+
await config.hooks?.onClear?.({
|
|
560
|
+
event,
|
|
561
|
+
config
|
|
562
|
+
});
|
|
505
563
|
}
|
|
506
564
|
function getSignKey(key) {
|
|
507
565
|
if (!key) {
|
package/dist/adapters/h3v2.d.mts
CHANGED
|
@@ -18,11 +18,30 @@ interface SessionJWE<T extends SessionDataT$1 = SessionDataT$1> {
|
|
|
18
18
|
[kGetSessionPromise$1]?: Promise<SessionJWE<T>>;
|
|
19
19
|
}
|
|
20
20
|
interface SessionHooksJWE {
|
|
21
|
-
onRead?: (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
onRead?: (args: {
|
|
22
|
+
session: SessionJWE;
|
|
23
|
+
event: HTTPEvent;
|
|
24
|
+
config: SessionConfigJWE;
|
|
25
|
+
}) => void | Promise<void>;
|
|
26
|
+
onUpdate?: (args: {
|
|
27
|
+
session: SessionJWE;
|
|
28
|
+
event: HTTPEvent;
|
|
29
|
+
config: SessionConfigJWE;
|
|
30
|
+
}) => void | Promise<void>;
|
|
31
|
+
onClear?: (args: {
|
|
32
|
+
event: HTTPEvent;
|
|
33
|
+
config: Partial<SessionConfigJWE>;
|
|
34
|
+
}) => void | Promise<void>;
|
|
35
|
+
onExpire?: (args: {
|
|
36
|
+
event: HTTPEvent;
|
|
37
|
+
error: Error;
|
|
38
|
+
config: SessionConfigJWE;
|
|
39
|
+
}) => void | Promise<void>;
|
|
40
|
+
onError?: (args: {
|
|
41
|
+
event: HTTPEvent;
|
|
42
|
+
error: any;
|
|
43
|
+
config: SessionConfigJWE;
|
|
44
|
+
}) => void | Promise<void>;
|
|
26
45
|
}
|
|
27
46
|
interface SessionManager<T extends SessionDataT$1 = SessionDataT$1> {
|
|
28
47
|
readonly id: string | undefined;
|
|
@@ -93,11 +112,30 @@ interface SessionJWS<T extends SessionDataT = SessionDataT> {
|
|
|
93
112
|
[kGetSessionPromise]?: Promise<SessionJWS<T>>;
|
|
94
113
|
}
|
|
95
114
|
interface SessionHooksJWS {
|
|
96
|
-
onRead?: (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
115
|
+
onRead?: (args: {
|
|
116
|
+
session: SessionJWS;
|
|
117
|
+
event: HTTPEvent;
|
|
118
|
+
config: SessionConfigJWS;
|
|
119
|
+
}) => void | Promise<void>;
|
|
120
|
+
onUpdate?: (args: {
|
|
121
|
+
session: SessionJWS;
|
|
122
|
+
event: HTTPEvent;
|
|
123
|
+
config: SessionConfigJWS;
|
|
124
|
+
}) => void | Promise<void>;
|
|
125
|
+
onClear?: (args: {
|
|
126
|
+
event: HTTPEvent;
|
|
127
|
+
config: Partial<SessionConfigJWS>;
|
|
128
|
+
}) => void | Promise<void>;
|
|
129
|
+
onExpire?: (args: {
|
|
130
|
+
event: HTTPEvent;
|
|
131
|
+
error: Error;
|
|
132
|
+
config: SessionConfigJWS;
|
|
133
|
+
}) => void | Promise<void>;
|
|
134
|
+
onError?: (args: {
|
|
135
|
+
event: HTTPEvent;
|
|
136
|
+
error: any;
|
|
137
|
+
config: SessionConfigJWS;
|
|
138
|
+
}) => void | Promise<void>;
|
|
101
139
|
}
|
|
102
140
|
interface SessionConfigJWS {
|
|
103
141
|
key: JWK_Symmetric | {
|
package/dist/adapters/h3v2.mjs
CHANGED
|
@@ -1,106 +1,10 @@
|
|
|
1
1
|
export { k as deriveJWKFromPassword, h as exportJWKToPEM, c as generateJWK, f as importJWKFromPEM } from '../shared/unjwt.epPK1KFr.mjs';
|
|
2
|
-
import { getEventContext } from 'h3';
|
|
3
|
-
import { serialize, parseSetCookie, parse } from 'cookie-es';
|
|
2
|
+
import { getEventContext, getChunkedCookie, setChunkedCookie } from 'h3';
|
|
4
3
|
import { NullProtoObj } from 'rou3';
|
|
5
4
|
import { e as encrypt, d as decrypt } from '../shared/unjwt.CiBEuvtQ.mjs';
|
|
6
5
|
import { p as isSymmetricJWK, q as isPrivateJWK, u as isPublicJWK } from '../shared/unjwt.C86hA0o-.mjs';
|
|
7
6
|
import { s as sign, v as verify } from '../shared/unjwt.D-FATaw1.mjs';
|
|
8
7
|
|
|
9
|
-
const CHUNKED_COOKIE = "__chunked__";
|
|
10
|
-
const CHUNKS_MAX_LENGTH = 4e3;
|
|
11
|
-
function parseCookies(event) {
|
|
12
|
-
return parse(event.req.headers.get("cookie") || "");
|
|
13
|
-
}
|
|
14
|
-
function getCookie(event, name) {
|
|
15
|
-
return parseCookies(event)[name];
|
|
16
|
-
}
|
|
17
|
-
function setCookie(event, name, value, options) {
|
|
18
|
-
const newCookie = serialize(name, value, { path: "/", ...options });
|
|
19
|
-
const currentCookies = event.res.headers.getSetCookie();
|
|
20
|
-
if (currentCookies.length === 0) {
|
|
21
|
-
event.res.headers.set("set-cookie", newCookie);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
const newCookieKey = _getDistinctCookieKey(
|
|
25
|
-
name,
|
|
26
|
-
options || {}
|
|
27
|
-
);
|
|
28
|
-
event.res.headers.delete("set-cookie");
|
|
29
|
-
for (const cookie of currentCookies) {
|
|
30
|
-
const _key = _getDistinctCookieKey(
|
|
31
|
-
cookie.split("=")[0],
|
|
32
|
-
parseSetCookie(cookie)
|
|
33
|
-
);
|
|
34
|
-
if (_key === newCookieKey) {
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
event.res.headers.append("set-cookie", cookie);
|
|
38
|
-
}
|
|
39
|
-
event.res.headers.append("set-cookie", newCookie);
|
|
40
|
-
}
|
|
41
|
-
function deleteCookie(event, name, serializeOptions) {
|
|
42
|
-
setCookie(event, name, "", {
|
|
43
|
-
...serializeOptions,
|
|
44
|
-
maxAge: 0
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
function getChunkedCookie(event, name) {
|
|
48
|
-
const mainCookie = getCookie(event, name);
|
|
49
|
-
if (!mainCookie || !mainCookie.startsWith(CHUNKED_COOKIE)) {
|
|
50
|
-
return mainCookie;
|
|
51
|
-
}
|
|
52
|
-
const chunksCount = getChunkedCookieCount(mainCookie);
|
|
53
|
-
if (chunksCount === 0) {
|
|
54
|
-
return void 0;
|
|
55
|
-
}
|
|
56
|
-
const chunks = [];
|
|
57
|
-
for (let i = 1; i <= chunksCount; i++) {
|
|
58
|
-
const chunk = getCookie(event, chunkCookieName(name, i));
|
|
59
|
-
if (!chunk) {
|
|
60
|
-
return void 0;
|
|
61
|
-
}
|
|
62
|
-
chunks.push(chunk);
|
|
63
|
-
}
|
|
64
|
-
return chunks.join("");
|
|
65
|
-
}
|
|
66
|
-
function setChunkedCookie(event, name, value, options) {
|
|
67
|
-
const chunkMaxLength = options?.chunkMaxLength || CHUNKS_MAX_LENGTH;
|
|
68
|
-
const chunkCount = Math.ceil(value.length / chunkMaxLength);
|
|
69
|
-
const previousCookie = getCookie(event, name);
|
|
70
|
-
if (previousCookie?.startsWith(CHUNKED_COOKIE)) {
|
|
71
|
-
const previousChunkCount = getChunkedCookieCount(previousCookie);
|
|
72
|
-
if (previousChunkCount > chunkCount) {
|
|
73
|
-
for (let i = chunkCount; i <= previousChunkCount; i++) {
|
|
74
|
-
deleteCookie(event, chunkCookieName(name, i), options);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (chunkCount <= 1) {
|
|
79
|
-
setCookie(event, name, value, options);
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
const mainCookieValue = `${CHUNKED_COOKIE}${chunkCount}`;
|
|
83
|
-
setCookie(event, name, mainCookieValue, options);
|
|
84
|
-
for (let i = 1; i <= chunkCount; i++) {
|
|
85
|
-
const start = (i - 1) * chunkMaxLength;
|
|
86
|
-
const end = start + chunkMaxLength;
|
|
87
|
-
const chunkValue = value.slice(start, end);
|
|
88
|
-
setCookie(event, chunkCookieName(name, i), chunkValue, options);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function _getDistinctCookieKey(name, options) {
|
|
92
|
-
return [name, options.domain || "", options.path || "/"].join(";");
|
|
93
|
-
}
|
|
94
|
-
function getChunkedCookieCount(cookie) {
|
|
95
|
-
if (!cookie?.startsWith(CHUNKED_COOKIE)) {
|
|
96
|
-
return Number.NaN;
|
|
97
|
-
}
|
|
98
|
-
return Number.parseInt(cookie.slice(CHUNKED_COOKIE.length));
|
|
99
|
-
}
|
|
100
|
-
function chunkCookieName(name, chunkNumber) {
|
|
101
|
-
return `${name}.${chunkNumber}`;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
8
|
const kGetSessionPromise$1 = Symbol("h3_jwe_getSession");
|
|
105
9
|
const DEFAULT_NAME$1 = "h3-jwe";
|
|
106
10
|
const DEFAULT_COOKIE$1 = {
|
|
@@ -145,12 +49,22 @@ async function getJWESession(event, config) {
|
|
|
145
49
|
if (existingSession) {
|
|
146
50
|
const session2 = existingSession[kGetSessionPromise$1] ? await existingSession[kGetSessionPromise$1] : existingSession;
|
|
147
51
|
if (session2.expiresAt !== void 0 && session2.expiresAt < Date.now() && hasWritableResponse$1(event)) {
|
|
148
|
-
await config.hooks?.onExpire?.(
|
|
52
|
+
await config.hooks?.onExpire?.({
|
|
53
|
+
event,
|
|
54
|
+
error: new Error(
|
|
55
|
+
`JWT "exp" (Expiration Time) Claim validation failed: Token has expired (exp: ${new Date(session2.expiresAt * 1e3).toISOString()})`
|
|
56
|
+
),
|
|
57
|
+
config
|
|
58
|
+
});
|
|
149
59
|
return clearJWESession(event, config).then(
|
|
150
60
|
() => getJWESession(event, config)
|
|
151
61
|
);
|
|
152
62
|
}
|
|
153
|
-
await config.hooks?.onRead?.(
|
|
63
|
+
await config.hooks?.onRead?.({
|
|
64
|
+
event,
|
|
65
|
+
session: session2,
|
|
66
|
+
config
|
|
67
|
+
});
|
|
154
68
|
return session2;
|
|
155
69
|
}
|
|
156
70
|
const session = {
|
|
@@ -160,27 +74,35 @@ async function getJWESession(event, config) {
|
|
|
160
74
|
data: new NullProtoObj()
|
|
161
75
|
};
|
|
162
76
|
context.sessions[sessionName] = session;
|
|
163
|
-
let
|
|
77
|
+
let token;
|
|
164
78
|
if (config.sessionHeader !== false) {
|
|
165
79
|
const headerName = typeof config.sessionHeader === "string" ? config.sessionHeader.toLowerCase() : `x-${sessionName.toLowerCase()}-session`;
|
|
166
80
|
const headerValue = event.req.headers.get(headerName);
|
|
167
81
|
if (typeof headerValue === "string") {
|
|
168
|
-
|
|
82
|
+
token = headerValue.startsWith("Bearer ") ? headerValue.slice(7).trim() : headerValue;
|
|
169
83
|
}
|
|
170
84
|
}
|
|
171
|
-
if (!
|
|
172
|
-
|
|
85
|
+
if (!token) {
|
|
86
|
+
token = getChunkedCookie(event, sessionName);
|
|
173
87
|
}
|
|
174
|
-
if (
|
|
175
|
-
const promise = unsealJWESession(event, config,
|
|
88
|
+
if (token) {
|
|
89
|
+
const promise = unsealJWESession(event, config, token).catch(async (error_) => {
|
|
176
90
|
if (error_ instanceof Error) {
|
|
177
91
|
const message = error_.message;
|
|
178
92
|
if (message.includes("Token has expired") || message.includes("Token is too old")) {
|
|
179
|
-
await config.hooks?.onExpire?.(
|
|
93
|
+
await config.hooks?.onExpire?.({
|
|
94
|
+
event,
|
|
95
|
+
error: error_,
|
|
96
|
+
config
|
|
97
|
+
});
|
|
180
98
|
return void 0;
|
|
181
99
|
}
|
|
182
100
|
}
|
|
183
|
-
await config.hooks?.onError?.(
|
|
101
|
+
await config.hooks?.onError?.({
|
|
102
|
+
event,
|
|
103
|
+
error: error_,
|
|
104
|
+
config
|
|
105
|
+
});
|
|
184
106
|
return void 0;
|
|
185
107
|
}).then((unsealed) => {
|
|
186
108
|
if (unsealed) {
|
|
@@ -198,7 +120,11 @@ async function getJWESession(event, config) {
|
|
|
198
120
|
session.expiresAt = config.maxAge ? session.createdAt + config.maxAge * 1e3 : void 0;
|
|
199
121
|
await updateJWESession(event, config);
|
|
200
122
|
}
|
|
201
|
-
await config.hooks?.onRead?.(
|
|
123
|
+
await config.hooks?.onRead?.({
|
|
124
|
+
event,
|
|
125
|
+
session,
|
|
126
|
+
config
|
|
127
|
+
});
|
|
202
128
|
return session;
|
|
203
129
|
}
|
|
204
130
|
async function updateJWESession(event, config, update) {
|
|
@@ -210,7 +136,11 @@ async function updateJWESession(event, config, update) {
|
|
|
210
136
|
}
|
|
211
137
|
if (update) {
|
|
212
138
|
Object.assign(session.data, update);
|
|
213
|
-
await config.hooks?.onUpdate?.(
|
|
139
|
+
await config.hooks?.onUpdate?.({
|
|
140
|
+
event,
|
|
141
|
+
session,
|
|
142
|
+
config
|
|
143
|
+
});
|
|
214
144
|
}
|
|
215
145
|
if (config.cookie !== false && hasWritableResponse$1(event)) {
|
|
216
146
|
const sealed = await sealJWESession(event, config);
|
|
@@ -263,11 +193,11 @@ async function unsealJWESession(_event, config, sealed) {
|
|
|
263
193
|
const { payload } = await decrypt(sealed, key, {
|
|
264
194
|
...config.jwe?.decryptOptions,
|
|
265
195
|
requiredClaims: [
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
196
|
+
.../* @__PURE__ */ new Set([
|
|
197
|
+
...config.jwe?.decryptOptions?.requiredClaims || [],
|
|
198
|
+
"jti",
|
|
199
|
+
"iat"
|
|
200
|
+
])
|
|
271
201
|
],
|
|
272
202
|
maxTokenAge: config.maxAge,
|
|
273
203
|
algorithms: alg ? [alg] : void 0,
|
|
@@ -303,7 +233,10 @@ async function clearJWESession(event, config) {
|
|
|
303
233
|
expires: /* @__PURE__ */ new Date(0),
|
|
304
234
|
maxAge: void 0
|
|
305
235
|
});
|
|
306
|
-
await config.hooks?.onClear?.(
|
|
236
|
+
await config.hooks?.onClear?.({
|
|
237
|
+
event,
|
|
238
|
+
config
|
|
239
|
+
});
|
|
307
240
|
}
|
|
308
241
|
}
|
|
309
242
|
function getSessionFromContext$1(event, sessionName) {
|
|
@@ -400,12 +333,22 @@ async function getJWSSession(event, config) {
|
|
|
400
333
|
if (existingSession) {
|
|
401
334
|
const session2 = existingSession[kGetSessionPromise] ? await existingSession[kGetSessionPromise] : existingSession;
|
|
402
335
|
if (session2.expiresAt !== void 0 && session2.expiresAt < Date.now() && hasWritableResponse(event)) {
|
|
403
|
-
await config.hooks?.onExpire?.(
|
|
336
|
+
await config.hooks?.onExpire?.({
|
|
337
|
+
event,
|
|
338
|
+
error: new Error(
|
|
339
|
+
`JWT "exp" (Expiration Time) Claim validation failed: Token has expired (exp: ${new Date(session2.expiresAt * 1e3).toISOString()})`
|
|
340
|
+
),
|
|
341
|
+
config
|
|
342
|
+
});
|
|
404
343
|
return clearJWSSession(event, config).then(
|
|
405
344
|
() => getJWSSession(event, config)
|
|
406
345
|
);
|
|
407
346
|
}
|
|
408
|
-
await config.hooks?.onRead?.(
|
|
347
|
+
await config.hooks?.onRead?.({
|
|
348
|
+
event,
|
|
349
|
+
session: session2,
|
|
350
|
+
config
|
|
351
|
+
});
|
|
409
352
|
return session2;
|
|
410
353
|
}
|
|
411
354
|
const session = {
|
|
@@ -420,7 +363,7 @@ async function getJWSSession(event, config) {
|
|
|
420
363
|
const headerName = typeof config.sessionHeader === "string" ? config.sessionHeader.toLowerCase() : `x-${sessionName.toLowerCase()}-session`;
|
|
421
364
|
const headerValue = event.req.headers.get(headerName);
|
|
422
365
|
if (typeof headerValue === "string") {
|
|
423
|
-
token = headerValue;
|
|
366
|
+
token = headerValue.startsWith("Bearer ") ? headerValue.slice(7).trim() : headerValue;
|
|
424
367
|
}
|
|
425
368
|
}
|
|
426
369
|
if (!token) {
|
|
@@ -429,10 +372,18 @@ async function getJWSSession(event, config) {
|
|
|
429
372
|
if (token) {
|
|
430
373
|
const promise = verifyJWSSession(event, config, token).catch(async (error_) => {
|
|
431
374
|
if (error_ instanceof Error && (error_.message.includes("Token has expired") || error_.message.includes("Token is too old"))) {
|
|
432
|
-
await config.hooks?.onExpire?.(
|
|
375
|
+
await config.hooks?.onExpire?.({
|
|
376
|
+
event,
|
|
377
|
+
error: error_,
|
|
378
|
+
config
|
|
379
|
+
});
|
|
433
380
|
return void 0;
|
|
434
381
|
}
|
|
435
|
-
await config.hooks?.onError?.(
|
|
382
|
+
await config.hooks?.onError?.({
|
|
383
|
+
event,
|
|
384
|
+
error: error_,
|
|
385
|
+
config
|
|
386
|
+
});
|
|
436
387
|
return void 0;
|
|
437
388
|
}).then((unsealed) => {
|
|
438
389
|
if (unsealed) {
|
|
@@ -450,7 +401,11 @@ async function getJWSSession(event, config) {
|
|
|
450
401
|
session.expiresAt = config.maxAge ? session.createdAt + config.maxAge * 1e3 : void 0;
|
|
451
402
|
await updateJWSSession(event, config);
|
|
452
403
|
}
|
|
453
|
-
await config.hooks?.onRead?.(
|
|
404
|
+
await config.hooks?.onRead?.({
|
|
405
|
+
event,
|
|
406
|
+
session,
|
|
407
|
+
config
|
|
408
|
+
});
|
|
454
409
|
return session;
|
|
455
410
|
}
|
|
456
411
|
async function updateJWSSession(event, config, update) {
|
|
@@ -462,7 +417,11 @@ async function updateJWSSession(event, config, update) {
|
|
|
462
417
|
}
|
|
463
418
|
if (update) {
|
|
464
419
|
Object.assign(session.data, update);
|
|
465
|
-
await config.hooks?.onUpdate?.(
|
|
420
|
+
await config.hooks?.onUpdate?.({
|
|
421
|
+
event,
|
|
422
|
+
session,
|
|
423
|
+
config
|
|
424
|
+
});
|
|
466
425
|
}
|
|
467
426
|
if (config.cookie !== false && hasWritableResponse(event)) {
|
|
468
427
|
const token = await signJWSSession(event, config);
|
|
@@ -516,11 +475,11 @@ async function verifyJWSSession(_event, config, token) {
|
|
|
516
475
|
const { payload } = await verify(token, jwk, {
|
|
517
476
|
...config.jws?.verifyOptions,
|
|
518
477
|
requiredClaims: [
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
478
|
+
.../* @__PURE__ */ new Set([
|
|
479
|
+
...config.jws?.verifyOptions?.requiredClaims || [],
|
|
480
|
+
"jti",
|
|
481
|
+
"iat"
|
|
482
|
+
])
|
|
524
483
|
],
|
|
525
484
|
typ: typ || "JWT",
|
|
526
485
|
algorithms: alg ? [alg] : void 0,
|
|
@@ -551,7 +510,10 @@ async function clearJWSSession(event, config) {
|
|
|
551
510
|
expires: /* @__PURE__ */ new Date(0),
|
|
552
511
|
maxAge: void 0
|
|
553
512
|
});
|
|
554
|
-
await config.hooks?.onClear?.(
|
|
513
|
+
await config.hooks?.onClear?.({
|
|
514
|
+
event,
|
|
515
|
+
config
|
|
516
|
+
});
|
|
555
517
|
}
|
|
556
518
|
}
|
|
557
519
|
function getSessionFromContext(event, sessionName) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unjwt",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.26",
|
|
4
4
|
"description": "Low-level JWT utilities",
|
|
5
5
|
"repository": "sandros94/unjwt",
|
|
6
6
|
"license": "MIT",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"eslint": "^9.36.0",
|
|
74
74
|
"eslint-config-unjs": "^0.5.0",
|
|
75
75
|
"h3v1": "npm:h3@^1.15.4",
|
|
76
|
-
"h3v2": "npm:h3@^2.0.
|
|
76
|
+
"h3v2": "npm:h3@^2.0.1-rc.5",
|
|
77
77
|
"jose": "^6.1.0",
|
|
78
78
|
"prettier": "^3.6.2",
|
|
79
79
|
"supertest": "^7.1.4",
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
},
|
|
84
84
|
"peerDependencies": {
|
|
85
85
|
"cookie-es": "^1.2.2 || ^2.0.0",
|
|
86
|
-
"h3": "^1.15.4 || >=2.0.
|
|
87
|
-
"rou3": "^0.7.
|
|
86
|
+
"h3": "^1.15.4 || >=2.0.1-rc.5 || ^2.0.1",
|
|
87
|
+
"rou3": "^0.7.10"
|
|
88
88
|
},
|
|
89
89
|
"peerDependenciesMeta": {
|
|
90
90
|
"cookie-es": {
|
|
@@ -92,7 +92,13 @@
|
|
|
92
92
|
},
|
|
93
93
|
"h3": {
|
|
94
94
|
"optional": true
|
|
95
|
+
},
|
|
96
|
+
"rou3": {
|
|
97
|
+
"optional": true
|
|
95
98
|
}
|
|
96
99
|
},
|
|
97
|
-
"
|
|
100
|
+
"resolutions": {
|
|
101
|
+
"@eslint/plugin-kit": "^0.3.4"
|
|
102
|
+
},
|
|
103
|
+
"packageManager": "pnpm@10.19.0"
|
|
98
104
|
}
|