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