@h3ravel/session 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +43 -0
- package/dist/index.cjs +1434 -0
- package/dist/index.d.ts +774 -0
- package/dist/index.js +1391 -0
- package/package.json +62 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1434 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
let __h3ravel_support = require("@h3ravel/support");
|
|
29
|
+
let __h3ravel_database = require("@h3ravel/database");
|
|
30
|
+
let crypto$1 = require("crypto");
|
|
31
|
+
crypto$1 = __toESM(crypto$1);
|
|
32
|
+
let __h3ravel_foundation = require("@h3ravel/foundation");
|
|
33
|
+
let __h3ravel_contracts = require("@h3ravel/contracts");
|
|
34
|
+
let fs = require("fs");
|
|
35
|
+
let path = require("path");
|
|
36
|
+
path = __toESM(path);
|
|
37
|
+
let __h3ravel_musket = require("@h3ravel/musket");
|
|
38
|
+
|
|
39
|
+
//#region src/Encryption.ts
|
|
40
|
+
var Encryption = class {
|
|
41
|
+
key;
|
|
42
|
+
constructor() {
|
|
43
|
+
const appKey = process.env.APP_KEY;
|
|
44
|
+
if (!appKey) throw new __h3ravel_foundation.ConfigException("APP_KEY not set in env");
|
|
45
|
+
this.key = (0, crypto$1.createHash)("sha256").update(Buffer.from(appKey, "base64")).digest();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Encrypt session data using AES-256-CBC and the APP_KEY.
|
|
49
|
+
*/
|
|
50
|
+
encrypt(value) {
|
|
51
|
+
value = typeof value === "string" ? value : JSON.stringify(value);
|
|
52
|
+
const iv = crypto$1.default.randomBytes(16);
|
|
53
|
+
const cipher = crypto$1.default.createCipheriv("aes-256-cbc", this.key, iv);
|
|
54
|
+
const encrypted = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
55
|
+
return iv.toString("hex") + ":" + encrypted.toString("hex");
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Decrypt session data.
|
|
59
|
+
*/
|
|
60
|
+
decrypt(value) {
|
|
61
|
+
const [ivHex, encryptedHex] = value.split(":");
|
|
62
|
+
const iv = Buffer.from(ivHex, "hex");
|
|
63
|
+
const encrypted = Buffer.from(encryptedHex, "hex");
|
|
64
|
+
const decipher = crypto$1.default.createDecipheriv("aes-256-cbc", this.key, iv);
|
|
65
|
+
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
|
|
66
|
+
try {
|
|
67
|
+
return JSON.parse(decrypted);
|
|
68
|
+
} catch {
|
|
69
|
+
return decrypted;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/FlashBag.ts
|
|
76
|
+
/**
|
|
77
|
+
* FlashBag
|
|
78
|
+
*
|
|
79
|
+
* Manages flash data for session management, handling temporary data
|
|
80
|
+
* that persists for one request cycle.
|
|
81
|
+
*/
|
|
82
|
+
var FlashBag = class {
|
|
83
|
+
/**
|
|
84
|
+
* Storage for flash data
|
|
85
|
+
*
|
|
86
|
+
* Structure:
|
|
87
|
+
* {
|
|
88
|
+
* new: { key1: value1, key2: value2 },
|
|
89
|
+
* old: { key3: value3, key4: value4 }
|
|
90
|
+
* }
|
|
91
|
+
*/
|
|
92
|
+
flashData = {
|
|
93
|
+
new: {},
|
|
94
|
+
old: {}
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Flash a value for the next request
|
|
98
|
+
*
|
|
99
|
+
* @param key Key to store in flash
|
|
100
|
+
* @param value Value to be flashed
|
|
101
|
+
*/
|
|
102
|
+
flash(key, value) {
|
|
103
|
+
this.flashData.new[key] = value;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Store a temporary value for the current request only
|
|
107
|
+
*
|
|
108
|
+
* @param key Key to store
|
|
109
|
+
* @param value Value to store
|
|
110
|
+
*/
|
|
111
|
+
now(key, value) {
|
|
112
|
+
this.flashData.new[key] = value;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Reflash all current flash data for another request cycle
|
|
116
|
+
*/
|
|
117
|
+
reflash() {
|
|
118
|
+
this.flashData.old = { ...this.flashData.new };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Keep only specific flash keys for the next request
|
|
122
|
+
*
|
|
123
|
+
* @param keys Keys to keep
|
|
124
|
+
*/
|
|
125
|
+
keep(keys) {
|
|
126
|
+
const keptNew = {};
|
|
127
|
+
const keptOld = {};
|
|
128
|
+
keys.forEach((key) => {
|
|
129
|
+
if (this.flashData.new[key] !== void 0) keptNew[key] = this.flashData.new[key];
|
|
130
|
+
if (this.flashData.old[key] !== void 0) keptOld[key] = this.flashData.old[key];
|
|
131
|
+
});
|
|
132
|
+
this.flashData.new = keptNew;
|
|
133
|
+
this.flashData.old = keptOld;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Age flash data at the end of the request
|
|
137
|
+
*
|
|
138
|
+
* - Removes old flash data
|
|
139
|
+
* - Moves new flash data to old
|
|
140
|
+
* - Clears new flash data
|
|
141
|
+
*/
|
|
142
|
+
ageFlashData() {
|
|
143
|
+
this.flashData.old = {};
|
|
144
|
+
this.flashData.old = { ...this.flashData.new };
|
|
145
|
+
this.flashData.new = {};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get a flash value
|
|
149
|
+
*
|
|
150
|
+
* @param key Key to retrieve
|
|
151
|
+
* @param defaultValue Default value if key doesn't exist
|
|
152
|
+
* @returns Flash value or default
|
|
153
|
+
*/
|
|
154
|
+
get(key, defaultValue) {
|
|
155
|
+
return this.flashData.new[key] ?? this.flashData.old[key] ?? defaultValue;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Check if a flash key exists
|
|
159
|
+
*
|
|
160
|
+
* @param key Key to check
|
|
161
|
+
* @returns Boolean indicating existence
|
|
162
|
+
*/
|
|
163
|
+
has(key) {
|
|
164
|
+
return key in this.flashData.new || key in this.flashData.old;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get all flash data
|
|
168
|
+
*
|
|
169
|
+
* @returns Combined flash data
|
|
170
|
+
*/
|
|
171
|
+
all() {
|
|
172
|
+
return {
|
|
173
|
+
...this.flashData.old,
|
|
174
|
+
...this.flashData.new
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Get all flash data keys
|
|
179
|
+
*
|
|
180
|
+
* @returns Combined flash data
|
|
181
|
+
*/
|
|
182
|
+
keys() {
|
|
183
|
+
return Object.keys({
|
|
184
|
+
...this.flashData.old,
|
|
185
|
+
...this.flashData.new
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Get the raww flash data
|
|
190
|
+
*
|
|
191
|
+
* @returns raw flash data
|
|
192
|
+
*/
|
|
193
|
+
raw() {
|
|
194
|
+
return this.flashData;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Clear all flash data
|
|
198
|
+
*/
|
|
199
|
+
clear() {
|
|
200
|
+
this.flashData.new = {};
|
|
201
|
+
this.flashData.old = {};
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/drivers/Driver.ts
|
|
207
|
+
/**
|
|
208
|
+
* Driver
|
|
209
|
+
*
|
|
210
|
+
* Base Session driver.
|
|
211
|
+
*/
|
|
212
|
+
var Driver = class extends __h3ravel_contracts.ISessionDriver {
|
|
213
|
+
encryptor = new Encryption();
|
|
214
|
+
sessionId;
|
|
215
|
+
flashBag = new FlashBag();
|
|
216
|
+
/**
|
|
217
|
+
* Save the raw session payload (session + flash)
|
|
218
|
+
*/
|
|
219
|
+
saveRawPayload() {
|
|
220
|
+
this.savePayload(Object.assign({}, this.fetchPayload(), { _flash: this.flashBag.raw() }));
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Retrieve all data from the session including flash
|
|
224
|
+
*
|
|
225
|
+
* @returns
|
|
226
|
+
*/
|
|
227
|
+
getAll() {
|
|
228
|
+
const payload = this.fetchPayload();
|
|
229
|
+
const flash = payload._flash ?? {
|
|
230
|
+
old: {},
|
|
231
|
+
new: {}
|
|
232
|
+
};
|
|
233
|
+
return {
|
|
234
|
+
...payload,
|
|
235
|
+
...flash.old,
|
|
236
|
+
...flash.new
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Retrieve a value from the session
|
|
241
|
+
*
|
|
242
|
+
* @param key
|
|
243
|
+
* @param defaultValue
|
|
244
|
+
* @returns
|
|
245
|
+
*/
|
|
246
|
+
get(key, defaultValue) {
|
|
247
|
+
return (0, __h3ravel_support.safeDot)(this.getAll(), key) || defaultValue;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Store a value in the session
|
|
251
|
+
*
|
|
252
|
+
* @param key
|
|
253
|
+
* @param value
|
|
254
|
+
*/
|
|
255
|
+
set(value) {
|
|
256
|
+
const payload = this.fetchPayload();
|
|
257
|
+
Object.assign(payload, value);
|
|
258
|
+
return this.savePayload(payload);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Store multiple key/value pairs
|
|
262
|
+
*
|
|
263
|
+
* @param values
|
|
264
|
+
*/
|
|
265
|
+
put(key, value) {
|
|
266
|
+
const payload = this.fetchPayload();
|
|
267
|
+
(0, __h3ravel_support.setNested)(payload, key, value);
|
|
268
|
+
return this.savePayload(payload);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Append a value to an array key
|
|
272
|
+
*
|
|
273
|
+
* @param key
|
|
274
|
+
* @param value
|
|
275
|
+
*/
|
|
276
|
+
push(key, value) {
|
|
277
|
+
const payload = this.fetchPayload();
|
|
278
|
+
if (!Array.isArray(payload[key])) payload[key] = [];
|
|
279
|
+
payload[key].push(value);
|
|
280
|
+
return this.savePayload(payload);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Remove a key from the session
|
|
284
|
+
*
|
|
285
|
+
* @param key
|
|
286
|
+
*/
|
|
287
|
+
forget(key) {
|
|
288
|
+
const payload = this.fetchPayload();
|
|
289
|
+
delete payload[key];
|
|
290
|
+
return this.savePayload(payload);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Retrieve all session data
|
|
294
|
+
*
|
|
295
|
+
* @returns
|
|
296
|
+
*/
|
|
297
|
+
all() {
|
|
298
|
+
return this.fetchPayload();
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Determine if a key exists (even if null).
|
|
302
|
+
*
|
|
303
|
+
* @param key
|
|
304
|
+
* @returns
|
|
305
|
+
*/
|
|
306
|
+
exists(key) {
|
|
307
|
+
const data = this.getAll();
|
|
308
|
+
return Object.prototype.hasOwnProperty.call(data, key);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Determine if a key has a non-null value.
|
|
312
|
+
*
|
|
313
|
+
* @param key
|
|
314
|
+
* @returns
|
|
315
|
+
*/
|
|
316
|
+
has(key) {
|
|
317
|
+
const data = this.getAll();
|
|
318
|
+
return data[key] !== void 0 && data[key] !== null;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Get only specific keys.
|
|
322
|
+
*
|
|
323
|
+
* @param keys
|
|
324
|
+
* @returns
|
|
325
|
+
*/
|
|
326
|
+
only(keys) {
|
|
327
|
+
const data = this.fetchPayload();
|
|
328
|
+
const result = {};
|
|
329
|
+
keys.forEach((k) => {
|
|
330
|
+
if (k in data) result[k] = data[k];
|
|
331
|
+
});
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Return all keys except the specified ones.
|
|
336
|
+
*
|
|
337
|
+
* @param keys
|
|
338
|
+
* @returns
|
|
339
|
+
*/
|
|
340
|
+
except(keys) {
|
|
341
|
+
const data = this.fetchPayload();
|
|
342
|
+
keys.forEach((k) => delete data[k]);
|
|
343
|
+
return data;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Return and delete a key from the session.
|
|
347
|
+
*
|
|
348
|
+
* @param key
|
|
349
|
+
* @param defaultValue
|
|
350
|
+
* @returns
|
|
351
|
+
*/
|
|
352
|
+
pull(key, defaultValue = null) {
|
|
353
|
+
const data = this.fetchPayload();
|
|
354
|
+
const value = data[key] ?? defaultValue;
|
|
355
|
+
delete data[key];
|
|
356
|
+
this.savePayload(data);
|
|
357
|
+
return value;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Increment a numeric value by amount (default 1).
|
|
361
|
+
*
|
|
362
|
+
* @param key
|
|
363
|
+
* @param amount
|
|
364
|
+
* @returns
|
|
365
|
+
*/
|
|
366
|
+
increment(key, amount = 1) {
|
|
367
|
+
const data = this.fetchPayload();
|
|
368
|
+
const newVal = (parseFloat(data[key]) || 0) + amount;
|
|
369
|
+
data[key] = newVal;
|
|
370
|
+
this.savePayload(data);
|
|
371
|
+
return newVal;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Decrement a numeric value by amount (default 1).
|
|
375
|
+
*
|
|
376
|
+
* @param key
|
|
377
|
+
* @param amount
|
|
378
|
+
* @returns
|
|
379
|
+
*/
|
|
380
|
+
decrement(key, amount = 1) {
|
|
381
|
+
return this.increment(key, -amount);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Flash a value for next request only.
|
|
385
|
+
*
|
|
386
|
+
* @param key
|
|
387
|
+
* @param value
|
|
388
|
+
*/
|
|
389
|
+
flash(key, value) {
|
|
390
|
+
this.flashBag.flash(key, value);
|
|
391
|
+
this.saveRawPayload();
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Reflash all flash data for one more cycle.
|
|
395
|
+
*
|
|
396
|
+
* @returns
|
|
397
|
+
*/
|
|
398
|
+
reflash() {
|
|
399
|
+
this.flashBag.reflash();
|
|
400
|
+
this.saveRawPayload();
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Keep only selected flash data.
|
|
404
|
+
*
|
|
405
|
+
* @param keys
|
|
406
|
+
* @returns
|
|
407
|
+
*/
|
|
408
|
+
keep(keys) {
|
|
409
|
+
this.flashBag.keep(keys);
|
|
410
|
+
this.saveRawPayload();
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Store a temporary value (flash) for this request only (not persisted)
|
|
414
|
+
*
|
|
415
|
+
* @param key
|
|
416
|
+
* @param value
|
|
417
|
+
*/
|
|
418
|
+
now(key, value) {
|
|
419
|
+
this.flashBag.now(key, value);
|
|
420
|
+
this.saveRawPayload();
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Regenerate session ID and persist data under new ID.
|
|
424
|
+
*/
|
|
425
|
+
regenerate() {
|
|
426
|
+
const oldData = this.fetchPayload();
|
|
427
|
+
this.sessionId = crypto.randomUUID();
|
|
428
|
+
this.savePayload(oldData);
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Age flash data at the end of the request lifecycle.
|
|
432
|
+
*/
|
|
433
|
+
ageFlashData() {
|
|
434
|
+
const data = this.flashBag.ageFlashData();
|
|
435
|
+
this.saveRawPayload();
|
|
436
|
+
return data;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Determine if an item is not present in the session.
|
|
440
|
+
*
|
|
441
|
+
* @param key
|
|
442
|
+
* @returns
|
|
443
|
+
*/
|
|
444
|
+
missing(key) {
|
|
445
|
+
return !this.exists(key);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Flush all session data
|
|
449
|
+
*/
|
|
450
|
+
flush() {
|
|
451
|
+
return this.savePayload({});
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
//#endregion
|
|
456
|
+
//#region src/drivers/DatabaseDriver.ts
|
|
457
|
+
/**
|
|
458
|
+
* DatabaseDriver
|
|
459
|
+
*
|
|
460
|
+
* Stores sessions in a database table. Each session ID maps to a row.
|
|
461
|
+
* The `payload` column contains all session key/value pairs as JSON.
|
|
462
|
+
*/
|
|
463
|
+
var DatabaseDriver = class extends Driver {
|
|
464
|
+
/**
|
|
465
|
+
*
|
|
466
|
+
* @param sessionId The current session ID
|
|
467
|
+
* @param table
|
|
468
|
+
*/
|
|
469
|
+
constructor(sessionId, table = "sessions") {
|
|
470
|
+
super();
|
|
471
|
+
this.sessionId = sessionId;
|
|
472
|
+
this.table = table;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Get the query builder for this table
|
|
476
|
+
*/
|
|
477
|
+
query() {
|
|
478
|
+
return __h3ravel_database.DB.table(this.table).where("id", this.sessionId);
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Fetch the session payload
|
|
482
|
+
*/
|
|
483
|
+
async fetchPayload() {
|
|
484
|
+
const row = await this.query().first();
|
|
485
|
+
if (!row) return {};
|
|
486
|
+
try {
|
|
487
|
+
const decrypted = this.encryptor.decrypt(row.payload);
|
|
488
|
+
return typeof decrypted === "string" ? JSON.parse(decrypted) : decrypted;
|
|
489
|
+
} catch {
|
|
490
|
+
return {};
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Save the session payload back to DB
|
|
495
|
+
*/
|
|
496
|
+
async savePayload(payload) {
|
|
497
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
498
|
+
const exists = await this.query().exists();
|
|
499
|
+
const encrypted = this.encryptor.encrypt(JSON.stringify(payload));
|
|
500
|
+
if (exists) await this.query().update({
|
|
501
|
+
payload: encrypted,
|
|
502
|
+
last_activity: now
|
|
503
|
+
});
|
|
504
|
+
else await __h3ravel_database.DB.table(this.table).insert({
|
|
505
|
+
id: this.sessionId,
|
|
506
|
+
payload: encrypted,
|
|
507
|
+
last_activity: now
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Retrieve all data from the session including flash
|
|
512
|
+
*/
|
|
513
|
+
async getAll() {
|
|
514
|
+
const payload = await this.fetchPayload();
|
|
515
|
+
const flash = payload._flash ?? {
|
|
516
|
+
old: {},
|
|
517
|
+
new: {}
|
|
518
|
+
};
|
|
519
|
+
return {
|
|
520
|
+
...payload,
|
|
521
|
+
...flash.old,
|
|
522
|
+
...flash.new
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Get a value from the session
|
|
527
|
+
*/
|
|
528
|
+
async get(key, defaultValue) {
|
|
529
|
+
return (0, __h3ravel_support.safeDot)(await this.getAll(), key) || defaultValue;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Set one or multiple session values
|
|
533
|
+
*/
|
|
534
|
+
async set(values) {
|
|
535
|
+
const payload = await this.fetchPayload();
|
|
536
|
+
Object.assign(payload, values);
|
|
537
|
+
await this.savePayload(payload);
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Store a single key/value pair
|
|
541
|
+
*/
|
|
542
|
+
async put(key, value) {
|
|
543
|
+
const payload = await this.fetchPayload();
|
|
544
|
+
(0, __h3ravel_support.setNested)(payload, key, value);
|
|
545
|
+
await this.savePayload(payload);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Append a value to an array key
|
|
549
|
+
*/
|
|
550
|
+
async push(key, value) {
|
|
551
|
+
const payload = await this.fetchPayload();
|
|
552
|
+
if (!Array.isArray(payload[key])) payload[key] = [];
|
|
553
|
+
payload[key].push(value);
|
|
554
|
+
await this.savePayload(payload);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Forget a session key
|
|
558
|
+
*/
|
|
559
|
+
async forget(key) {
|
|
560
|
+
const payload = await this.fetchPayload();
|
|
561
|
+
delete payload[key];
|
|
562
|
+
await this.savePayload(payload);
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Retrieve all session data (excluding flash)
|
|
566
|
+
*/
|
|
567
|
+
async all() {
|
|
568
|
+
return this.fetchPayload();
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Determine if a key exists (even if null)
|
|
572
|
+
*/
|
|
573
|
+
async exists(key) {
|
|
574
|
+
const data = await this.getAll();
|
|
575
|
+
return Object.prototype.hasOwnProperty.call(data, key);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Determine if a key has a non-null value
|
|
579
|
+
*/
|
|
580
|
+
async has(key) {
|
|
581
|
+
const data = await this.getAll();
|
|
582
|
+
return data[key] !== void 0 && data[key] !== null;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Get only specific keys
|
|
586
|
+
*/
|
|
587
|
+
async only(keys) {
|
|
588
|
+
const data = await this.fetchPayload();
|
|
589
|
+
const result = {};
|
|
590
|
+
keys.forEach((k) => {
|
|
591
|
+
if (k in data) result[k] = data[k];
|
|
592
|
+
});
|
|
593
|
+
return result;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Return all except specific keys
|
|
597
|
+
*/
|
|
598
|
+
async except(keys) {
|
|
599
|
+
const data = await this.fetchPayload();
|
|
600
|
+
keys.forEach((k) => delete data[k]);
|
|
601
|
+
return data;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Retrieve and delete a value
|
|
605
|
+
*/
|
|
606
|
+
async pull(key, defaultValue = null) {
|
|
607
|
+
const data = await this.fetchPayload();
|
|
608
|
+
const value = data[key] ?? defaultValue;
|
|
609
|
+
delete data[key];
|
|
610
|
+
await this.savePayload(data);
|
|
611
|
+
return value;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Increment a numeric value
|
|
615
|
+
*/
|
|
616
|
+
async increment(key, amount = 1) {
|
|
617
|
+
const data = await this.fetchPayload();
|
|
618
|
+
const newVal = (parseFloat(data[key]) || 0) + amount;
|
|
619
|
+
data[key] = newVal;
|
|
620
|
+
await this.savePayload(data);
|
|
621
|
+
return newVal;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Decrement a numeric value
|
|
625
|
+
*/
|
|
626
|
+
async decrement(key, amount = 1) {
|
|
627
|
+
return this.increment(key, -amount);
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Flash a value for next request only
|
|
631
|
+
*/
|
|
632
|
+
async flash(key, value) {
|
|
633
|
+
this.flashBag.flash(key, value);
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Reflash all flash data for one more cycle
|
|
637
|
+
*/
|
|
638
|
+
async reflash() {
|
|
639
|
+
this.flashBag.reflash();
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Keep only specific flash keys
|
|
643
|
+
*/
|
|
644
|
+
async keep(keys) {
|
|
645
|
+
this.flashBag.keep(keys);
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Store a temporary value (flash) for this request only (not persisted)
|
|
649
|
+
*/
|
|
650
|
+
async now(key, value) {
|
|
651
|
+
this.flashBag.now(key, value);
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Regenerate session ID with same data
|
|
655
|
+
*/
|
|
656
|
+
async regenerate() {
|
|
657
|
+
const oldData = await this.fetchPayload();
|
|
658
|
+
this.sessionId = crypto.randomUUID();
|
|
659
|
+
await this.savePayload(oldData);
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Check if a key is missing
|
|
663
|
+
*/
|
|
664
|
+
async missing(key) {
|
|
665
|
+
return !await this.exists(key);
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Flush all session data
|
|
669
|
+
*/
|
|
670
|
+
async flush() {
|
|
671
|
+
await this.savePayload({});
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Invalidate the session and regenerate
|
|
675
|
+
*/
|
|
676
|
+
async invalidate() {
|
|
677
|
+
await __h3ravel_database.DB.table(this.table).where("id", this.sessionId).delete();
|
|
678
|
+
this.sessionId = crypto.randomUUID();
|
|
679
|
+
this.flashBag = new FlashBag();
|
|
680
|
+
await this.savePayload({});
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Age flash data at the end of the request lifecycle.
|
|
684
|
+
*/
|
|
685
|
+
async ageFlashData() {
|
|
686
|
+
this.flashBag.ageFlashData();
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
//#endregion
|
|
691
|
+
//#region src/drivers/FileDriver.ts
|
|
692
|
+
/**
|
|
693
|
+
* FileDriver
|
|
694
|
+
*
|
|
695
|
+
* Stores session data as encrypted JSON files.
|
|
696
|
+
* Each session is stored in its own file named after the session ID.
|
|
697
|
+
* Ideal for local development or low-scale deployments.
|
|
698
|
+
*/
|
|
699
|
+
var FileDriver = class extends Driver {
|
|
700
|
+
constructor(sessionId, sessionDir = path.default.resolve(".sessions"), cwd = process.cwd()) {
|
|
701
|
+
super();
|
|
702
|
+
this.sessionId = sessionId;
|
|
703
|
+
this.sessionDir = sessionDir;
|
|
704
|
+
this.cwd = cwd;
|
|
705
|
+
this.sessionDir = path.default.join(this.cwd, sessionDir);
|
|
706
|
+
this.sessionId = sessionId;
|
|
707
|
+
if (!(0, fs.existsSync)(this.sessionDir)) (0, fs.mkdirSync)(this.sessionDir, { recursive: true });
|
|
708
|
+
this.ensureSessionFile();
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Ensures the session file exists and is initialized.
|
|
712
|
+
*/
|
|
713
|
+
ensureSessionFile() {
|
|
714
|
+
if (!(0, fs.existsSync)(this.sessionFilePath())) this.savePayload({});
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Get the absolute path for the current session file.
|
|
718
|
+
*/
|
|
719
|
+
sessionFilePath() {
|
|
720
|
+
return path.default.join(this.sessionDir, this.sessionId);
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Read raw decrypted payload (including _flash).
|
|
724
|
+
*/
|
|
725
|
+
readRawPayload() {
|
|
726
|
+
const file = this.sessionFilePath();
|
|
727
|
+
if (!(0, fs.existsSync)(file)) return {};
|
|
728
|
+
const content = (0, fs.readFileSync)(file, "utf8");
|
|
729
|
+
try {
|
|
730
|
+
return this.encryptor.decrypt(content);
|
|
731
|
+
} catch {
|
|
732
|
+
return {};
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Fetch decrypted payload and strip out flash metadata.
|
|
737
|
+
*/
|
|
738
|
+
fetchPayload() {
|
|
739
|
+
return this.readRawPayload();
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Write and encrypt session data to file.
|
|
743
|
+
* Always persists flash state.
|
|
744
|
+
*
|
|
745
|
+
* @param data
|
|
746
|
+
*/
|
|
747
|
+
savePayload(payload) {
|
|
748
|
+
(0, fs.writeFileSync)(this.sessionFilePath(), this.encryptor.encrypt(payload), "utf8");
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Completely invalidate the current session and regenerate a new one.
|
|
752
|
+
*/
|
|
753
|
+
invalidate() {
|
|
754
|
+
const file = this.sessionFilePath();
|
|
755
|
+
if ((0, fs.existsSync)(file)) (0, fs.rmSync)(file, { recursive: true });
|
|
756
|
+
this.sessionId = crypto.randomUUID();
|
|
757
|
+
this.flashBag = new FlashBag();
|
|
758
|
+
this.savePayload({});
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
//#endregion
|
|
763
|
+
//#region src/drivers/MemoryDriver.ts
|
|
764
|
+
/**
|
|
765
|
+
* MemoryDriver
|
|
766
|
+
*
|
|
767
|
+
* Lightweight, ephemeral session storage.
|
|
768
|
+
* Intended for tests, local development, or short-lived apps.
|
|
769
|
+
*/
|
|
770
|
+
var MemoryDriver = class MemoryDriver extends Driver {
|
|
771
|
+
static store = {};
|
|
772
|
+
constructor(sessionId) {
|
|
773
|
+
super();
|
|
774
|
+
this.sessionId = sessionId;
|
|
775
|
+
this.sessionId = sessionId;
|
|
776
|
+
if (!MemoryDriver.store[this.sessionId]) MemoryDriver.store[this.sessionId] = {};
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Fetch and return session payload.
|
|
780
|
+
*
|
|
781
|
+
* @returns Decrypted and usable payload
|
|
782
|
+
*/
|
|
783
|
+
fetchPayload() {
|
|
784
|
+
return { ...MemoryDriver.store[this.sessionId] };
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Persist session payload and flash bag state.
|
|
788
|
+
*
|
|
789
|
+
* @param data
|
|
790
|
+
*/
|
|
791
|
+
savePayload(payload) {
|
|
792
|
+
MemoryDriver.store[this.sessionId] = { ...payload };
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Invalidate current session and regenerate new session ID.
|
|
796
|
+
*/
|
|
797
|
+
invalidate() {
|
|
798
|
+
delete MemoryDriver.store[this.sessionId];
|
|
799
|
+
this.sessionId = crypto$1.default.randomUUID();
|
|
800
|
+
this.flashBag = new FlashBag();
|
|
801
|
+
this.savePayload({});
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
//#endregion
|
|
806
|
+
//#region src/drivers/RedisDriver.ts
|
|
807
|
+
/**
|
|
808
|
+
* RedisDriver (placeholder)
|
|
809
|
+
*/
|
|
810
|
+
var RedisDriver = class extends Driver {
|
|
811
|
+
static store = {};
|
|
812
|
+
constructor(sessionId, redisClient, prefix) {
|
|
813
|
+
super();
|
|
814
|
+
this.sessionId = sessionId;
|
|
815
|
+
this.redisClient = redisClient;
|
|
816
|
+
this.prefix = prefix;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Fetch and return session payload.
|
|
820
|
+
*
|
|
821
|
+
* @returns Decrypted and usable payload
|
|
822
|
+
*/
|
|
823
|
+
fetchPayload() {
|
|
824
|
+
return {};
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Persist session payload and flash bag state.
|
|
828
|
+
*
|
|
829
|
+
* @param data
|
|
830
|
+
*/
|
|
831
|
+
savePayload(_payload) {}
|
|
832
|
+
/**
|
|
833
|
+
* Invalidate current session and regenerate new session ID.
|
|
834
|
+
*/
|
|
835
|
+
invalidate() {
|
|
836
|
+
this.flashBag = new FlashBag();
|
|
837
|
+
this.savePayload({});
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
//#endregion
|
|
842
|
+
//#region src/adapters.ts
|
|
843
|
+
/**
|
|
844
|
+
* FileDriver builder
|
|
845
|
+
* constructor(sessionId: string, sessionDir?: string, cwd?: string)
|
|
846
|
+
*/
|
|
847
|
+
const fileBuilder = (sessionId, options = {}) => {
|
|
848
|
+
return new FileDriver(sessionId, options.sessionDir ?? options.dir ?? "./storage/sessions", options.cwd ?? process.cwd());
|
|
849
|
+
};
|
|
850
|
+
/**
|
|
851
|
+
* DatabaseDriver builder
|
|
852
|
+
* constructor(sessionId: string, table?: string)
|
|
853
|
+
*/
|
|
854
|
+
const dbBuilder = (sessionId, options = {}) => {
|
|
855
|
+
const table = options.table ?? "sessions";
|
|
856
|
+
return new DatabaseDriver(options.sessionId ?? sessionId, table);
|
|
857
|
+
};
|
|
858
|
+
/**
|
|
859
|
+
* MemoryDriver builder
|
|
860
|
+
* constructor(sessionId: string)
|
|
861
|
+
*/
|
|
862
|
+
const memoryBuilder = (sessionId) => {
|
|
863
|
+
return new MemoryDriver(sessionId);
|
|
864
|
+
};
|
|
865
|
+
/**
|
|
866
|
+
* RedisDriver builder
|
|
867
|
+
* constructor(sessionId: string, redisClient?: RedisClient, prefix?: string)
|
|
868
|
+
*/
|
|
869
|
+
const redisBuilder = (sessionId, options = {}) => {
|
|
870
|
+
const client = options.client;
|
|
871
|
+
return new RedisDriver(sessionId, client, options.prefix ?? "h3ravel:sessions:");
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
//#endregion
|
|
875
|
+
//#region src/Commands/MakeSessionTableCommand.ts
|
|
876
|
+
var MakeSessionTableCommand = class extends __h3ravel_musket.Command {
|
|
877
|
+
/**
|
|
878
|
+
* The name and signature of the console command.
|
|
879
|
+
*
|
|
880
|
+
* @var string
|
|
881
|
+
*/
|
|
882
|
+
signature = "make:session-table";
|
|
883
|
+
/**
|
|
884
|
+
* The console command description.
|
|
885
|
+
*
|
|
886
|
+
* @var string
|
|
887
|
+
*/
|
|
888
|
+
description = "Create a migration for the session database table";
|
|
889
|
+
async handle() {
|
|
890
|
+
await __h3ravel_database.DB.instance().schema.hasTable("sessions").then(async function(exists) {
|
|
891
|
+
if (!exists) return __h3ravel_database.DB.instance().schema.createTable("sessions", (table) => {
|
|
892
|
+
table.string("id", 255).primary();
|
|
893
|
+
table.bigInteger("user_id").nullable().index();
|
|
894
|
+
table.string("ip_address", 45).nullable();
|
|
895
|
+
table.text("user_agent").nullable();
|
|
896
|
+
table.text("payload", "longtext").nullable();
|
|
897
|
+
table.integer("last_activity").index();
|
|
898
|
+
});
|
|
899
|
+
});
|
|
900
|
+
this.info("INFO: session table created successfully.");
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
//#endregion
|
|
905
|
+
//#region ../../node_modules/.pnpm/h3@2.0.1-rc.5/node_modules/h3/dist/h3.mjs
|
|
906
|
+
const kEventNS = "h3.internal.event.";
|
|
907
|
+
const kEventRes = /* @__PURE__ */ Symbol.for(`${kEventNS}res`);
|
|
908
|
+
const kEventResHeaders = /* @__PURE__ */ Symbol.for(`${kEventNS}res.headers`);
|
|
909
|
+
function parse(str, options) {
|
|
910
|
+
if (typeof str !== "string") throw new TypeError("argument str must be a string");
|
|
911
|
+
const obj = {};
|
|
912
|
+
const opt = options || {};
|
|
913
|
+
const dec = opt.decode || decode;
|
|
914
|
+
let index = 0;
|
|
915
|
+
while (index < str.length) {
|
|
916
|
+
const eqIdx = str.indexOf("=", index);
|
|
917
|
+
if (eqIdx === -1) break;
|
|
918
|
+
let endIdx = str.indexOf(";", index);
|
|
919
|
+
if (endIdx === -1) endIdx = str.length;
|
|
920
|
+
else if (endIdx < eqIdx) {
|
|
921
|
+
index = str.lastIndexOf(";", eqIdx - 1) + 1;
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
const key = str.slice(index, eqIdx).trim();
|
|
925
|
+
if (opt?.filter && !opt?.filter(key)) {
|
|
926
|
+
index = endIdx + 1;
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
if (void 0 === obj[key]) {
|
|
930
|
+
let val = str.slice(eqIdx + 1, endIdx).trim();
|
|
931
|
+
if (val.codePointAt(0) === 34) val = val.slice(1, -1);
|
|
932
|
+
obj[key] = tryDecode(val, dec);
|
|
933
|
+
}
|
|
934
|
+
index = endIdx + 1;
|
|
935
|
+
}
|
|
936
|
+
return obj;
|
|
937
|
+
}
|
|
938
|
+
function decode(str) {
|
|
939
|
+
return str.includes("%") ? decodeURIComponent(str) : str;
|
|
940
|
+
}
|
|
941
|
+
function tryDecode(str, decode2) {
|
|
942
|
+
try {
|
|
943
|
+
return decode2(str);
|
|
944
|
+
} catch {
|
|
945
|
+
return str;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
const fieldContentRegExp = /^[\u0009\u0020-\u007E\u0080-\u00FF]+$/;
|
|
949
|
+
function serialize(name, value, options) {
|
|
950
|
+
const opt = options || {};
|
|
951
|
+
const enc = opt.encode || encodeURIComponent;
|
|
952
|
+
if (typeof enc !== "function") throw new TypeError("option encode is invalid");
|
|
953
|
+
if (!fieldContentRegExp.test(name)) throw new TypeError("argument name is invalid");
|
|
954
|
+
const encodedValue = enc(value);
|
|
955
|
+
if (encodedValue && !fieldContentRegExp.test(encodedValue)) throw new TypeError("argument val is invalid");
|
|
956
|
+
let str = name + "=" + encodedValue;
|
|
957
|
+
if (void 0 !== opt.maxAge && opt.maxAge !== null) {
|
|
958
|
+
const maxAge = opt.maxAge - 0;
|
|
959
|
+
if (Number.isNaN(maxAge) || !Number.isFinite(maxAge)) throw new TypeError("option maxAge is invalid");
|
|
960
|
+
str += "; Max-Age=" + Math.floor(maxAge);
|
|
961
|
+
}
|
|
962
|
+
if (opt.domain) {
|
|
963
|
+
if (!fieldContentRegExp.test(opt.domain)) throw new TypeError("option domain is invalid");
|
|
964
|
+
str += "; Domain=" + opt.domain;
|
|
965
|
+
}
|
|
966
|
+
if (opt.path) {
|
|
967
|
+
if (!fieldContentRegExp.test(opt.path)) throw new TypeError("option path is invalid");
|
|
968
|
+
str += "; Path=" + opt.path;
|
|
969
|
+
}
|
|
970
|
+
if (opt.expires) {
|
|
971
|
+
if (!isDate(opt.expires) || Number.isNaN(opt.expires.valueOf())) throw new TypeError("option expires is invalid");
|
|
972
|
+
str += "; Expires=" + opt.expires.toUTCString();
|
|
973
|
+
}
|
|
974
|
+
if (opt.httpOnly) str += "; HttpOnly";
|
|
975
|
+
if (opt.secure) str += "; Secure";
|
|
976
|
+
if (opt.priority) switch (typeof opt.priority === "string" ? opt.priority.toLowerCase() : opt.priority) {
|
|
977
|
+
case "low":
|
|
978
|
+
str += "; Priority=Low";
|
|
979
|
+
break;
|
|
980
|
+
case "medium":
|
|
981
|
+
str += "; Priority=Medium";
|
|
982
|
+
break;
|
|
983
|
+
case "high":
|
|
984
|
+
str += "; Priority=High";
|
|
985
|
+
break;
|
|
986
|
+
default: throw new TypeError("option priority is invalid");
|
|
987
|
+
}
|
|
988
|
+
if (opt.sameSite) switch (typeof opt.sameSite === "string" ? opt.sameSite.toLowerCase() : opt.sameSite) {
|
|
989
|
+
case true:
|
|
990
|
+
str += "; SameSite=Strict";
|
|
991
|
+
break;
|
|
992
|
+
case "lax":
|
|
993
|
+
str += "; SameSite=Lax";
|
|
994
|
+
break;
|
|
995
|
+
case "strict":
|
|
996
|
+
str += "; SameSite=Strict";
|
|
997
|
+
break;
|
|
998
|
+
case "none":
|
|
999
|
+
str += "; SameSite=None";
|
|
1000
|
+
break;
|
|
1001
|
+
default: throw new TypeError("option sameSite is invalid");
|
|
1002
|
+
}
|
|
1003
|
+
if (opt.partitioned) str += "; Partitioned";
|
|
1004
|
+
return str;
|
|
1005
|
+
}
|
|
1006
|
+
function isDate(val) {
|
|
1007
|
+
return Object.prototype.toString.call(val) === "[object Date]" || val instanceof Date;
|
|
1008
|
+
}
|
|
1009
|
+
function parseSetCookie(setCookieValue, options) {
|
|
1010
|
+
const parts = (setCookieValue || "").split(";").filter((str) => typeof str === "string" && !!str.trim());
|
|
1011
|
+
const parsed = _parseNameValuePair(parts.shift() || "");
|
|
1012
|
+
const name = parsed.name;
|
|
1013
|
+
let value = parsed.value;
|
|
1014
|
+
try {
|
|
1015
|
+
value = options?.decode === false ? value : (options?.decode || decodeURIComponent)(value);
|
|
1016
|
+
} catch {}
|
|
1017
|
+
const cookie = {
|
|
1018
|
+
name,
|
|
1019
|
+
value
|
|
1020
|
+
};
|
|
1021
|
+
for (const part of parts) {
|
|
1022
|
+
const sides = part.split("=");
|
|
1023
|
+
const partKey = (sides.shift() || "").trimStart().toLowerCase();
|
|
1024
|
+
const partValue = sides.join("=");
|
|
1025
|
+
switch (partKey) {
|
|
1026
|
+
case "expires":
|
|
1027
|
+
cookie.expires = new Date(partValue);
|
|
1028
|
+
break;
|
|
1029
|
+
case "max-age":
|
|
1030
|
+
cookie.maxAge = Number.parseInt(partValue, 10);
|
|
1031
|
+
break;
|
|
1032
|
+
case "secure":
|
|
1033
|
+
cookie.secure = true;
|
|
1034
|
+
break;
|
|
1035
|
+
case "httponly":
|
|
1036
|
+
cookie.httpOnly = true;
|
|
1037
|
+
break;
|
|
1038
|
+
case "samesite":
|
|
1039
|
+
cookie.sameSite = partValue;
|
|
1040
|
+
break;
|
|
1041
|
+
default: cookie[partKey] = partValue;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
return cookie;
|
|
1045
|
+
}
|
|
1046
|
+
function _parseNameValuePair(nameValuePairStr) {
|
|
1047
|
+
let name = "";
|
|
1048
|
+
let value = "";
|
|
1049
|
+
const nameValueArr = nameValuePairStr.split("=");
|
|
1050
|
+
if (nameValueArr.length > 1) {
|
|
1051
|
+
name = nameValueArr.shift();
|
|
1052
|
+
value = nameValueArr.join("=");
|
|
1053
|
+
} else value = nameValuePairStr;
|
|
1054
|
+
return {
|
|
1055
|
+
name,
|
|
1056
|
+
value
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
function parseCookies(event) {
|
|
1060
|
+
return parse(event.req.headers.get("cookie") || "");
|
|
1061
|
+
}
|
|
1062
|
+
function getCookie(event, name) {
|
|
1063
|
+
return parseCookies(event)[name];
|
|
1064
|
+
}
|
|
1065
|
+
function setCookie(event, name, value, options) {
|
|
1066
|
+
const newCookie = serialize(name, value, {
|
|
1067
|
+
path: "/",
|
|
1068
|
+
...options
|
|
1069
|
+
});
|
|
1070
|
+
const currentCookies = event.res.headers.getSetCookie();
|
|
1071
|
+
if (currentCookies.length === 0) {
|
|
1072
|
+
event.res.headers.set("set-cookie", newCookie);
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
const newCookieKey = _getDistinctCookieKey(name, options || {});
|
|
1076
|
+
event.res.headers.delete("set-cookie");
|
|
1077
|
+
for (const cookie of currentCookies) {
|
|
1078
|
+
if (_getDistinctCookieKey(cookie.split("=")?.[0], parseSetCookie(cookie)) === newCookieKey) continue;
|
|
1079
|
+
event.res.headers.append("set-cookie", cookie);
|
|
1080
|
+
}
|
|
1081
|
+
event.res.headers.append("set-cookie", newCookie);
|
|
1082
|
+
}
|
|
1083
|
+
function _getDistinctCookieKey(name, options) {
|
|
1084
|
+
return [
|
|
1085
|
+
name,
|
|
1086
|
+
options.domain || "",
|
|
1087
|
+
options.path || "/"
|
|
1088
|
+
].join(";");
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
//#endregion
|
|
1092
|
+
//#region src/SessionStore.ts
|
|
1093
|
+
/**
|
|
1094
|
+
* SessionStore (Driver registry)
|
|
1095
|
+
*
|
|
1096
|
+
* Register driver builders under a name and then create instances using:
|
|
1097
|
+
* SessionStore.make('file', sessionId, options)
|
|
1098
|
+
*/
|
|
1099
|
+
var SessionStore = class {
|
|
1100
|
+
static registry = /* @__PURE__ */ new Map();
|
|
1101
|
+
/**
|
|
1102
|
+
* Register a driver builder under a key (e.g. 'file', 'database', 'memory').
|
|
1103
|
+
*/
|
|
1104
|
+
static register(name, builder) {
|
|
1105
|
+
this.registry.set(name, builder);
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Create a driver instance for the given sessionId using the named builder.
|
|
1109
|
+
*
|
|
1110
|
+
* If driver not found, throws. Options is a simple key/value bag passed to the builder.
|
|
1111
|
+
*/
|
|
1112
|
+
static make(name, sessionId, options = {}) {
|
|
1113
|
+
const builder = this.registry.get(name);
|
|
1114
|
+
if (!builder) throw new Error(`Session driver "${name}" is not registered`);
|
|
1115
|
+
return builder(sessionId, options);
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
|
|
1119
|
+
//#endregion
|
|
1120
|
+
//#region src/SessionManager.ts
|
|
1121
|
+
/**
|
|
1122
|
+
* SessionManager
|
|
1123
|
+
*
|
|
1124
|
+
* Handles session initialization, ID generation, and encryption.
|
|
1125
|
+
* Each request gets a unique session namespace tied to its ID.
|
|
1126
|
+
*/
|
|
1127
|
+
var SessionManager = class SessionManager extends __h3ravel_contracts.ISessionManager {
|
|
1128
|
+
app;
|
|
1129
|
+
ctx;
|
|
1130
|
+
driver;
|
|
1131
|
+
appKey;
|
|
1132
|
+
sessionId;
|
|
1133
|
+
request;
|
|
1134
|
+
flashBag;
|
|
1135
|
+
constructor(app, driverName = "file", driverOptions = {}) {
|
|
1136
|
+
super();
|
|
1137
|
+
this.appKey = process.env.APP_KEY;
|
|
1138
|
+
if (app instanceof __h3ravel_contracts.IHttpContext) {
|
|
1139
|
+
this.request = app.request;
|
|
1140
|
+
this.ctx = app;
|
|
1141
|
+
this.app = app.app;
|
|
1142
|
+
} else {
|
|
1143
|
+
this.app = app;
|
|
1144
|
+
this.ctx = app.make("http.context");
|
|
1145
|
+
this.request = this.ctx.request;
|
|
1146
|
+
}
|
|
1147
|
+
this.sessionId = this.resolveSessionId();
|
|
1148
|
+
this.driver = SessionStore.make(driverName, driverOptions.sessionId ?? this.sessionId, driverOptions);
|
|
1149
|
+
this.flashBag = this.driver.flashBag;
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Initialize the Session Manager
|
|
1153
|
+
*
|
|
1154
|
+
* @param ctx
|
|
1155
|
+
* @returns
|
|
1156
|
+
*/
|
|
1157
|
+
static init(app) {
|
|
1158
|
+
return new SessionManager(app, config("session.driver", "file"), {
|
|
1159
|
+
cwd: config("session.files"),
|
|
1160
|
+
sessionDir: "/",
|
|
1161
|
+
dir: "/",
|
|
1162
|
+
table: config("session.table"),
|
|
1163
|
+
prefix: config("database.connections.redis.options.prefix"),
|
|
1164
|
+
client: config(`database.connections.${config("session.driver", "file")}.client`)
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Generate a secure session ID unique to the user device.
|
|
1169
|
+
*/
|
|
1170
|
+
generateSessionId() {
|
|
1171
|
+
const userAgent = this.request.getHeader("user-agent") || "";
|
|
1172
|
+
const ip = this.request.getHeader("x-forwarded-for") || this.request.ip() || "";
|
|
1173
|
+
const random = (0, crypto$1.randomBytes)(32).toString("hex");
|
|
1174
|
+
const fingerprint = (0, crypto$1.createHash)("sha256").update(`${userAgent}-${ip}`).digest("hex");
|
|
1175
|
+
return (0, crypto$1.createHmac)("sha256", this.appKey).update(`${fingerprint}-${random}`).digest("hex");
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Resolve the session ID from cookie, header, or create a new one.
|
|
1179
|
+
*/
|
|
1180
|
+
resolveSessionId() {
|
|
1181
|
+
const cookieSession = getCookie(this.ctx.event, "h3ravel_session");
|
|
1182
|
+
if (cookieSession) return cookieSession;
|
|
1183
|
+
const newId = this.generateSessionId();
|
|
1184
|
+
setCookie(this.ctx.event, "h3ravel_session", newId, {
|
|
1185
|
+
httpOnly: true,
|
|
1186
|
+
secure: true,
|
|
1187
|
+
sameSite: "lax",
|
|
1188
|
+
maxAge: 3600 * 24 * 7
|
|
1189
|
+
});
|
|
1190
|
+
return newId;
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Access the current session ID.
|
|
1194
|
+
*/
|
|
1195
|
+
id() {
|
|
1196
|
+
return this.sessionId;
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Get the current session driver
|
|
1200
|
+
*/
|
|
1201
|
+
getDriver() {
|
|
1202
|
+
return this.driver;
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Retrieve a value from the session
|
|
1206
|
+
*
|
|
1207
|
+
* @param key
|
|
1208
|
+
* @param defaultValue
|
|
1209
|
+
* @returns
|
|
1210
|
+
*/
|
|
1211
|
+
get(key, defaultValue) {
|
|
1212
|
+
return this.driver.get(key, defaultValue);
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Store a value in the session
|
|
1216
|
+
*
|
|
1217
|
+
* @param key
|
|
1218
|
+
* @param value
|
|
1219
|
+
*/
|
|
1220
|
+
set(value) {
|
|
1221
|
+
return this.driver.set(value);
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Store multiple key/value pairs
|
|
1225
|
+
*
|
|
1226
|
+
* @param values
|
|
1227
|
+
*/
|
|
1228
|
+
put(key, value) {
|
|
1229
|
+
return this.driver.put(key, value);
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Append a value to an array key
|
|
1233
|
+
*
|
|
1234
|
+
* @param key
|
|
1235
|
+
* @param value
|
|
1236
|
+
*/
|
|
1237
|
+
push(key, value) {
|
|
1238
|
+
return this.driver.push(key, value);
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Remove a key from the session
|
|
1242
|
+
*
|
|
1243
|
+
* @param key
|
|
1244
|
+
*/
|
|
1245
|
+
forget(key) {
|
|
1246
|
+
return this.driver.forget(key);
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Retrieve all session data
|
|
1250
|
+
*
|
|
1251
|
+
* @returns
|
|
1252
|
+
*/
|
|
1253
|
+
all() {
|
|
1254
|
+
return this.driver.all();
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Determine if a key exists (even if null).
|
|
1258
|
+
*
|
|
1259
|
+
* @param key
|
|
1260
|
+
* @returns
|
|
1261
|
+
*/
|
|
1262
|
+
exists(key) {
|
|
1263
|
+
return this.driver.exists(key);
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Determine if a key has a non-null value.
|
|
1267
|
+
*
|
|
1268
|
+
* @param key
|
|
1269
|
+
* @returns
|
|
1270
|
+
*/
|
|
1271
|
+
has(key) {
|
|
1272
|
+
return this.driver.has(key);
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Get only specific keys.
|
|
1276
|
+
*
|
|
1277
|
+
* @param keys
|
|
1278
|
+
* @returns
|
|
1279
|
+
*/
|
|
1280
|
+
only(keys) {
|
|
1281
|
+
return this.driver.only(keys);
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Return all keys except the specified ones.
|
|
1285
|
+
*
|
|
1286
|
+
* @param keys
|
|
1287
|
+
* @returns
|
|
1288
|
+
*/
|
|
1289
|
+
except(keys) {
|
|
1290
|
+
return this.driver.except(keys);
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Return and delete a key from the session.
|
|
1294
|
+
*
|
|
1295
|
+
* @param key
|
|
1296
|
+
* @param defaultValue
|
|
1297
|
+
* @returns
|
|
1298
|
+
*/
|
|
1299
|
+
pull(key, defaultValue = null) {
|
|
1300
|
+
return this.driver.pull(key, defaultValue);
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Increment a numeric value by amount (default 1).
|
|
1304
|
+
*
|
|
1305
|
+
* @param key
|
|
1306
|
+
* @param amount
|
|
1307
|
+
* @returns
|
|
1308
|
+
*/
|
|
1309
|
+
increment(key, amount = 1) {
|
|
1310
|
+
return this.driver.increment(key, amount);
|
|
1311
|
+
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Decrement a numeric value by amount (default 1).
|
|
1314
|
+
*
|
|
1315
|
+
* @param key
|
|
1316
|
+
* @param amount
|
|
1317
|
+
* @returns
|
|
1318
|
+
*/
|
|
1319
|
+
decrement(key, amount = 1) {
|
|
1320
|
+
return this.driver.decrement(key, amount);
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Flash a value for next request only.
|
|
1324
|
+
*
|
|
1325
|
+
* @param key
|
|
1326
|
+
* @param value
|
|
1327
|
+
*/
|
|
1328
|
+
flash(key, value) {
|
|
1329
|
+
return this.driver.flash(key, value);
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Reflash all flash data for one more cycle.
|
|
1333
|
+
*
|
|
1334
|
+
* @returns
|
|
1335
|
+
*/
|
|
1336
|
+
reflash() {
|
|
1337
|
+
return this.driver.reflash();
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Keep only selected flash data.
|
|
1341
|
+
*
|
|
1342
|
+
* @param keys
|
|
1343
|
+
* @returns
|
|
1344
|
+
*/
|
|
1345
|
+
keep(keys) {
|
|
1346
|
+
return this.driver.keep(keys);
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Store data only for current request cycle (not persisted).
|
|
1350
|
+
*
|
|
1351
|
+
* @param key
|
|
1352
|
+
* @param value
|
|
1353
|
+
*/
|
|
1354
|
+
now(key, value) {
|
|
1355
|
+
return this.driver.now(key, value);
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Regenerate session ID and persist data under new ID.
|
|
1359
|
+
*/
|
|
1360
|
+
regenerate() {
|
|
1361
|
+
return this.driver.regenerate();
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Determine if an item is not present in the session.
|
|
1365
|
+
*
|
|
1366
|
+
* @param key
|
|
1367
|
+
* @returns
|
|
1368
|
+
*/
|
|
1369
|
+
missing(key) {
|
|
1370
|
+
return this.driver.missing(key);
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Flush all session data
|
|
1374
|
+
*/
|
|
1375
|
+
flush() {
|
|
1376
|
+
return this.driver.flush();
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Invalidate the session completely and regenerate ID.
|
|
1380
|
+
*
|
|
1381
|
+
* @returns
|
|
1382
|
+
*/
|
|
1383
|
+
invalidate() {
|
|
1384
|
+
return this.driver.invalidate();
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Age flash data at the end of the request lifecycle.
|
|
1388
|
+
*
|
|
1389
|
+
* @returns
|
|
1390
|
+
*/
|
|
1391
|
+
ageFlashData() {
|
|
1392
|
+
return this.driver.ageFlashData();
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
|
|
1396
|
+
//#endregion
|
|
1397
|
+
//#region src/Providers/SessionServiceProvider.ts
|
|
1398
|
+
var SessionServiceProvider = class extends __h3ravel_support.ServiceProvider {
|
|
1399
|
+
static priority = 895;
|
|
1400
|
+
static order = "before:HttpServiceProvider";
|
|
1401
|
+
register() {
|
|
1402
|
+
/**
|
|
1403
|
+
* Register default drivers.
|
|
1404
|
+
*/
|
|
1405
|
+
SessionStore.register("file", fileBuilder);
|
|
1406
|
+
SessionStore.register("database", dbBuilder);
|
|
1407
|
+
SessionStore.register("memory", memoryBuilder);
|
|
1408
|
+
SessionStore.register("redis", redisBuilder);
|
|
1409
|
+
this.app.singleton("session", (app) => {
|
|
1410
|
+
return SessionManager.init(app);
|
|
1411
|
+
});
|
|
1412
|
+
this.app.singleton("session.store", (app) => {
|
|
1413
|
+
return app.make("session").getDriver();
|
|
1414
|
+
});
|
|
1415
|
+
this.registerCommands([MakeSessionTableCommand]);
|
|
1416
|
+
}
|
|
1417
|
+
};
|
|
1418
|
+
|
|
1419
|
+
//#endregion
|
|
1420
|
+
exports.DatabaseDriver = DatabaseDriver;
|
|
1421
|
+
exports.Driver = Driver;
|
|
1422
|
+
exports.Encryption = Encryption;
|
|
1423
|
+
exports.FileDriver = FileDriver;
|
|
1424
|
+
exports.FlashBag = FlashBag;
|
|
1425
|
+
exports.MakeSessionTableCommand = MakeSessionTableCommand;
|
|
1426
|
+
exports.MemoryDriver = MemoryDriver;
|
|
1427
|
+
exports.RedisDriver = RedisDriver;
|
|
1428
|
+
exports.SessionManager = SessionManager;
|
|
1429
|
+
exports.SessionServiceProvider = SessionServiceProvider;
|
|
1430
|
+
exports.SessionStore = SessionStore;
|
|
1431
|
+
exports.dbBuilder = dbBuilder;
|
|
1432
|
+
exports.fileBuilder = fileBuilder;
|
|
1433
|
+
exports.memoryBuilder = memoryBuilder;
|
|
1434
|
+
exports.redisBuilder = redisBuilder;
|