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.
- package/README.md +4 -4
- package/dist/THIRD-PARTY-LICENSES.md.gz +0 -0
- package/dist/_chunks/_codec-jwe.d.mts +170 -0
- package/dist/_chunks/_codec-jws.mjs +878 -0
- package/dist/_chunks/_internal.d.mts +7 -0
- package/dist/_chunks/_jwt-internal.mjs +388 -0
- package/dist/_chunks/algorithms.mjs +186 -0
- package/dist/_chunks/index.d.mts +500 -808
- package/dist/_chunks/index2.d.mts +11 -0
- package/dist/_chunks/index3.d.mts +108 -0
- package/dist/_chunks/jwe.d.mts +43 -66
- package/dist/_chunks/jwe.mjs +384 -252
- package/dist/_chunks/jwk.mjs +1374 -607
- package/dist/_chunks/jws.d.mts +48 -103
- package/dist/_chunks/jws.mjs +372 -184
- package/dist/_chunks/jwt.d.mts +56 -0
- package/dist/_chunks/libs/unsecure.d.mts +164 -85
- package/dist/_chunks/libs/unsecure.mjs +390 -123
- package/dist/adapters/elysia/index.d.mts +33 -171
- package/dist/adapters/elysia/index.mjs +106 -575
- package/dist/adapters/h3v1/index.d.mts +59 -344
- package/dist/adapters/h3v1/index.mjs +114 -619
- package/dist/adapters/h3v2/index.d.mts +58 -326
- package/dist/adapters/h3v2/index.mjs +108 -611
- package/dist/core/internal.d.mts +2 -0
- package/dist/core/internal.mjs +1 -0
- package/dist/core/jwe.d.mts +4 -2
- package/dist/core/jwe.mjs +4 -2
- package/dist/core/jwk.d.mts +183 -371
- package/dist/core/jwk.mjs +4 -3
- package/dist/core/jws.d.mts +4 -2
- package/dist/core/jws.mjs +4 -2
- package/dist/core/utils/index.d.mts +5 -158
- package/dist/core/utils/index.mjs +12 -3
- package/dist/index.d.mts +8 -4
- package/dist/index.mjs +6 -3
- package/package.json +29 -30
- package/dist/_chunks/_internal.mjs +0 -88
- package/dist/_chunks/utils.mjs +0 -392
|
@@ -1,642 +1,139 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
|
332
|
-
await
|
|
333
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
550
|
-
|
|
551
|
-
|
|
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
|
-
|
|
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
|
|
606
|
-
return
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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,
|
|
139
|
+
export { clearJWESession, clearJWSSession, deriveJWKFromPassword, exportPEM, generateJWK, getJWESession, getJWESessionToken, getJWSSession, getJWSSessionToken, importPEM, sealJWESession, signJWSSession, thumbprint, unsealJWESession, updateJWESession, updateJWSSession, useJWESession, useJWSSession, verifyJWSSession };
|