@terreno/api 0.13.2 → 0.14.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.
Files changed (175) hide show
  1. package/dist/__tests__/versionCheckPlugin.test.js +53 -3
  2. package/dist/api.arrayOperations.test.js +1 -0
  3. package/dist/api.asyncHandler.test.d.ts +1 -0
  4. package/dist/api.asyncHandler.test.js +236 -0
  5. package/dist/api.d.ts +15 -4
  6. package/dist/api.errors.test.js +1 -0
  7. package/dist/api.hooks.test.js +1 -0
  8. package/dist/api.js +153 -104
  9. package/dist/api.query.test.js +1 -0
  10. package/dist/api.test.js +174 -0
  11. package/dist/auth.d.ts +10 -5
  12. package/dist/auth.js +163 -90
  13. package/dist/auth.test.js +159 -0
  14. package/dist/betterAuthApp.test.js +1 -0
  15. package/dist/betterAuthSetup.d.ts +5 -6
  16. package/dist/betterAuthSetup.js +17 -14
  17. package/dist/betterAuthSetup.test.js +1 -0
  18. package/dist/config.d.ts +48 -0
  19. package/dist/config.js +248 -0
  20. package/dist/config.test.d.ts +1 -0
  21. package/dist/config.test.js +328 -0
  22. package/dist/configuration.test.js +1 -0
  23. package/dist/configurationApp.d.ts +1 -1
  24. package/dist/configurationApp.js +17 -13
  25. package/dist/configurationPlugin.test.js +1 -0
  26. package/dist/consentApp.test.js +1 -0
  27. package/dist/envConfigurationPlugin.d.ts +2 -0
  28. package/dist/envConfigurationPlugin.js +173 -0
  29. package/dist/envConfigurationPlugin.test.d.ts +1 -0
  30. package/dist/envConfigurationPlugin.test.js +322 -0
  31. package/dist/errors.d.ts +18 -7
  32. package/dist/errors.js +106 -10
  33. package/dist/errors.test.js +16 -1
  34. package/dist/example.js +16 -7
  35. package/dist/expressServer.d.ts +10 -9
  36. package/dist/expressServer.js +62 -53
  37. package/dist/expressServer.test.js +53 -2
  38. package/dist/githubAuth.d.ts +2 -1
  39. package/dist/githubAuth.js +41 -26
  40. package/dist/githubAuth.test.js +1 -0
  41. package/dist/index.d.ts +4 -0
  42. package/dist/index.js +4 -0
  43. package/dist/logger.d.ts +1 -1
  44. package/dist/logger.js +42 -20
  45. package/dist/models/versionConfig.d.ts +2 -0
  46. package/dist/models/versionConfig.js +8 -0
  47. package/dist/notifiers/googleChatNotifier.js +14 -16
  48. package/dist/notifiers/googleChatNotifier.test.js +1 -0
  49. package/dist/notifiers/slackNotifier.js +16 -14
  50. package/dist/notifiers/slackNotifier.test.js +41 -3
  51. package/dist/notifiers/zoomNotifier.js +7 -10
  52. package/dist/notifiers/zoomNotifier.test.js +1 -0
  53. package/dist/openApi.d.ts +1 -1
  54. package/dist/openApi.test.js +1 -0
  55. package/dist/openApiBuilder.d.ts +39 -6
  56. package/dist/openApiBuilder.js +1 -31
  57. package/dist/openApiBuilder.test.js +1 -0
  58. package/dist/openApiValidator.js +1 -0
  59. package/dist/openApiValidator.test.js +65 -0
  60. package/dist/permissions.d.ts +4 -4
  61. package/dist/permissions.js +67 -65
  62. package/dist/permissions.middleware.test.js +1 -0
  63. package/dist/permissions.test.js +1 -0
  64. package/dist/plugins.d.ts +5 -5
  65. package/dist/plugins.js +18 -9
  66. package/dist/plugins.test.js +1 -1
  67. package/dist/populate.d.ts +15 -8
  68. package/dist/populate.js +23 -24
  69. package/dist/populate.test.js +1 -0
  70. package/dist/realtime/changeStreamWatcher.d.ts +73 -0
  71. package/dist/realtime/changeStreamWatcher.js +720 -0
  72. package/dist/realtime/index.d.ts +6 -0
  73. package/dist/realtime/index.js +27 -0
  74. package/dist/realtime/queryMatcher.d.ts +14 -0
  75. package/dist/realtime/queryMatcher.js +250 -0
  76. package/dist/realtime/queryStore.d.ts +37 -0
  77. package/dist/realtime/queryStore.js +195 -0
  78. package/dist/realtime/realtime.test.d.ts +10 -0
  79. package/dist/realtime/realtime.test.js +2158 -0
  80. package/dist/realtime/realtimeApp.d.ts +93 -0
  81. package/dist/realtime/realtimeApp.js +560 -0
  82. package/dist/realtime/registry.d.ts +40 -0
  83. package/dist/realtime/registry.js +38 -0
  84. package/dist/realtime/socketUser.d.ts +10 -0
  85. package/dist/realtime/socketUser.js +17 -0
  86. package/dist/realtime/types.d.ts +100 -0
  87. package/dist/realtime/types.js +2 -0
  88. package/dist/requestContext.d.ts +37 -0
  89. package/dist/requestContext.js +344 -0
  90. package/dist/requestContext.test.d.ts +1 -0
  91. package/dist/requestContext.test.js +241 -0
  92. package/dist/terrenoApp.d.ts +8 -0
  93. package/dist/terrenoApp.js +50 -13
  94. package/dist/terrenoApp.test.js +194 -21
  95. package/dist/terrenoPlugin.d.ts +11 -0
  96. package/dist/tests/bunSetup.js +1 -0
  97. package/dist/tests.js +1 -1
  98. package/dist/transformers.d.ts +2 -2
  99. package/dist/transformers.js +5 -3
  100. package/dist/transformers.test.js +90 -0
  101. package/dist/types/consentResponse.d.ts +6 -3
  102. package/dist/versionCheckPlugin.d.ts +2 -0
  103. package/dist/versionCheckPlugin.js +18 -12
  104. package/package.json +4 -2
  105. package/src/__tests__/versionCheckPlugin.test.ts +37 -3
  106. package/src/api.arrayOperations.test.ts +1 -0
  107. package/src/api.asyncHandler.test.ts +177 -0
  108. package/src/api.errors.test.ts +1 -0
  109. package/src/api.hooks.test.ts +1 -0
  110. package/src/api.query.test.ts +1 -0
  111. package/src/api.test.ts +132 -0
  112. package/src/api.ts +199 -84
  113. package/src/auth.test.ts +160 -0
  114. package/src/auth.ts +120 -50
  115. package/src/betterAuthApp.test.ts +1 -0
  116. package/src/betterAuthSetup.test.ts +1 -0
  117. package/src/betterAuthSetup.ts +46 -19
  118. package/src/config.test.ts +255 -0
  119. package/src/config.ts +206 -0
  120. package/src/configuration.test.ts +1 -0
  121. package/src/configurationApp.ts +59 -24
  122. package/src/configurationPlugin.test.ts +1 -0
  123. package/src/consentApp.test.ts +1 -0
  124. package/src/envConfigurationPlugin.test.ts +143 -0
  125. package/src/envConfigurationPlugin.ts +100 -0
  126. package/src/errors.test.ts +19 -1
  127. package/src/errors.ts +94 -20
  128. package/src/example.ts +46 -21
  129. package/src/express.d.ts +18 -1
  130. package/src/expressServer.test.ts +50 -2
  131. package/src/expressServer.ts +80 -50
  132. package/src/githubAuth.test.ts +1 -0
  133. package/src/githubAuth.ts +59 -38
  134. package/src/index.ts +4 -0
  135. package/src/logger.ts +47 -17
  136. package/src/models/versionConfig.ts +13 -2
  137. package/src/notifiers/googleChatNotifier.test.ts +1 -0
  138. package/src/notifiers/googleChatNotifier.ts +7 -9
  139. package/src/notifiers/slackNotifier.test.ts +29 -3
  140. package/src/notifiers/slackNotifier.ts +9 -7
  141. package/src/notifiers/zoomNotifier.test.ts +1 -0
  142. package/src/notifiers/zoomNotifier.ts +8 -11
  143. package/src/openApi.test.ts +1 -0
  144. package/src/openApi.ts +4 -4
  145. package/src/openApiBuilder.test.ts +1 -0
  146. package/src/openApiBuilder.ts +14 -11
  147. package/src/openApiValidator.test.ts +59 -0
  148. package/src/openApiValidator.ts +3 -2
  149. package/src/permissions.middleware.test.ts +1 -0
  150. package/src/permissions.test.ts +1 -0
  151. package/src/permissions.ts +30 -25
  152. package/src/plugins.test.ts +1 -1
  153. package/src/plugins.ts +21 -14
  154. package/src/populate.test.ts +1 -0
  155. package/src/populate.ts +44 -36
  156. package/src/realtime/changeStreamWatcher.ts +568 -0
  157. package/src/realtime/index.ts +34 -0
  158. package/src/realtime/queryMatcher.ts +179 -0
  159. package/src/realtime/queryStore.ts +132 -0
  160. package/src/realtime/realtime.test.ts +1755 -0
  161. package/src/realtime/realtimeApp.ts +478 -0
  162. package/src/realtime/registry.ts +64 -0
  163. package/src/realtime/socketUser.ts +25 -0
  164. package/src/realtime/types.ts +112 -0
  165. package/src/requestContext.test.ts +196 -0
  166. package/src/requestContext.ts +368 -0
  167. package/src/terrenoApp.test.ts +137 -11
  168. package/src/terrenoApp.ts +64 -17
  169. package/src/terrenoPlugin.ts +12 -0
  170. package/src/tests/bunSetup.ts +1 -0
  171. package/src/tests.ts +7 -2
  172. package/src/transformers.test.ts +70 -2
  173. package/src/transformers.ts +15 -7
  174. package/src/types/consentResponse.ts +8 -10
  175. package/src/versionCheckPlugin.ts +15 -7
package/dist/api.test.js CHANGED
@@ -88,7 +88,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
88
88
  return (mod && mod.__esModule) ? mod : { "default": mod };
89
89
  };
90
90
  Object.defineProperty(exports, "__esModule", { value: true });
91
+ // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
92
+ // biome-ignore-all lint/suspicious/noImplicitAnyLet: test mock typing
91
93
  var bun_test_1 = require("bun:test");
94
+ var luxon_1 = require("luxon");
92
95
  var supertest_1 = __importDefault(require("supertest"));
93
96
  var api_1 = require("./api");
94
97
  var auth_1 = require("./auth");
@@ -2405,4 +2408,175 @@ var transformers_1 = require("./transformers");
2405
2408
  });
2406
2409
  }); });
2407
2410
  });
2411
+ (0, bun_test_1.describe)("conflict detection (If-Unmodified-Since)", function () {
2412
+ var admin;
2413
+ var _notAdmin;
2414
+ var agent;
2415
+ var spinach;
2416
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
2417
+ var _a;
2418
+ return __generator(this, function (_b) {
2419
+ switch (_b.label) {
2420
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
2421
+ case 1:
2422
+ _a = __read.apply(void 0, [_b.sent(), 2]), admin = _a[0], _notAdmin = _a[1];
2423
+ return [4 /*yield*/, tests_1.FoodModel.create({
2424
+ calories: 10,
2425
+ created: luxon_1.DateTime.fromISO("2025-06-15T12:00:00.000Z").toJSDate(),
2426
+ hidden: false,
2427
+ name: "Spinach",
2428
+ ownerId: admin._id,
2429
+ })];
2430
+ case 2:
2431
+ spinach = _b.sent();
2432
+ return [4 /*yield*/, tests_1.FoodModel.collection.updateOne({ _id: spinach._id }, { $set: { updated: luxon_1.DateTime.fromISO("2025-06-15T12:00:00.000Z").toJSDate() } })];
2433
+ case 3:
2434
+ _b.sent();
2435
+ app = (0, tests_1.getBaseServer)();
2436
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
2437
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
2438
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2439
+ permissions: {
2440
+ create: [permissions_1.Permissions.IsAny],
2441
+ delete: [permissions_1.Permissions.IsAny],
2442
+ list: [permissions_1.Permissions.IsAny],
2443
+ read: [permissions_1.Permissions.IsAny],
2444
+ update: [permissions_1.Permissions.IsAny],
2445
+ },
2446
+ }));
2447
+ server = (0, supertest_1.default)(app);
2448
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
2449
+ case 4:
2450
+ agent = _b.sent();
2451
+ return [2 /*return*/];
2452
+ }
2453
+ });
2454
+ }); });
2455
+ (0, bun_test_1.it)("returns 409 when If-Unmodified-Since is older than doc.updated", function () { return __awaiter(void 0, void 0, void 0, function () {
2456
+ var staleTimestamp, res;
2457
+ return __generator(this, function (_a) {
2458
+ switch (_a.label) {
2459
+ case 0:
2460
+ staleTimestamp = luxon_1.DateTime.fromISO("2025-06-15T11:00:00.000Z").toHTTP();
2461
+ return [4 /*yield*/, agent
2462
+ .patch("/food/".concat(spinach._id))
2463
+ .set("If-Unmodified-Since", staleTimestamp)
2464
+ .send({ name: "Should Fail" })
2465
+ .expect(409)];
2466
+ case 1:
2467
+ res = _a.sent();
2468
+ (0, bun_test_1.expect)(res.body.error).toBe("Conflict");
2469
+ (0, bun_test_1.expect)(res.body.message).toBe("Document was modified since your last read");
2470
+ (0, bun_test_1.expect)(res.body.data).toBeDefined();
2471
+ // The response should contain the current server version
2472
+ (0, bun_test_1.expect)(res.body.data.name).toBe("Spinach");
2473
+ return [2 /*return*/];
2474
+ }
2475
+ });
2476
+ }); });
2477
+ (0, bun_test_1.it)("succeeds when If-Unmodified-Since matches or is newer than doc.updated", function () { return __awaiter(void 0, void 0, void 0, function () {
2478
+ var freshTimestamp, res;
2479
+ return __generator(this, function (_a) {
2480
+ switch (_a.label) {
2481
+ case 0:
2482
+ freshTimestamp = luxon_1.DateTime.fromISO("2025-06-15T13:00:00.000Z").toHTTP();
2483
+ return [4 /*yield*/, agent
2484
+ .patch("/food/".concat(spinach._id))
2485
+ .set("If-Unmodified-Since", freshTimestamp)
2486
+ .send({ name: "Updated Spinach" })
2487
+ .expect(200)];
2488
+ case 1:
2489
+ res = _a.sent();
2490
+ (0, bun_test_1.expect)(res.body.data.name).toBe("Updated Spinach");
2491
+ return [2 /*return*/];
2492
+ }
2493
+ });
2494
+ }); });
2495
+ (0, bun_test_1.it)("succeeds normally when If-Unmodified-Since header is not present", function () { return __awaiter(void 0, void 0, void 0, function () {
2496
+ var res;
2497
+ return __generator(this, function (_a) {
2498
+ switch (_a.label) {
2499
+ case 0: return [4 /*yield*/, agent
2500
+ .patch("/food/".concat(spinach._id))
2501
+ .send({ name: "No Header Update" })
2502
+ .expect(200)];
2503
+ case 1:
2504
+ res = _a.sent();
2505
+ (0, bun_test_1.expect)(res.body.data.name).toBe("No Header Update");
2506
+ return [2 /*return*/];
2507
+ }
2508
+ });
2509
+ }); });
2510
+ (0, bun_test_1.it)("succeeds when If-Unmodified-Since exactly matches doc.updated", function () { return __awaiter(void 0, void 0, void 0, function () {
2511
+ var exactTimestamp, res;
2512
+ return __generator(this, function (_a) {
2513
+ switch (_a.label) {
2514
+ case 0:
2515
+ exactTimestamp = luxon_1.DateTime.fromISO("2025-06-15T12:00:00.000Z").toHTTP();
2516
+ return [4 /*yield*/, agent
2517
+ .patch("/food/".concat(spinach._id))
2518
+ .set("If-Unmodified-Since", exactTimestamp)
2519
+ .send({ name: "Exact Match" })
2520
+ .expect(200)];
2521
+ case 1:
2522
+ res = _a.sent();
2523
+ (0, bun_test_1.expect)(res.body.data.name).toBe("Exact Match");
2524
+ return [2 /*return*/];
2525
+ }
2526
+ });
2527
+ }); });
2528
+ (0, bun_test_1.it)("prefers precise conflict timestamp header when present", function () { return __awaiter(void 0, void 0, void 0, function () {
2529
+ var roundedStaleTimestamp, res;
2530
+ return __generator(this, function (_a) {
2531
+ switch (_a.label) {
2532
+ case 0:
2533
+ roundedStaleTimestamp = luxon_1.DateTime.fromISO("2025-06-15T11:59:59.000Z").toHTTP();
2534
+ return [4 /*yield*/, agent
2535
+ .patch("/food/".concat(spinach._id))
2536
+ .set("If-Unmodified-Since", roundedStaleTimestamp)
2537
+ .set("X-Unmodified-Since-ISO", "2025-06-15T12:00:00.750Z")
2538
+ .send({ name: "Precise Match" })
2539
+ .expect(200)];
2540
+ case 1:
2541
+ res = _a.sent();
2542
+ (0, bun_test_1.expect)(res.body.data.name).toBe("Precise Match");
2543
+ return [2 /*return*/];
2544
+ }
2545
+ });
2546
+ }); });
2547
+ (0, bun_test_1.it)("returns 409 when precise conflict timestamp is older than doc.updated", function () { return __awaiter(void 0, void 0, void 0, function () {
2548
+ return __generator(this, function (_a) {
2549
+ switch (_a.label) {
2550
+ case 0: return [4 /*yield*/, agent
2551
+ .patch("/food/".concat(spinach._id))
2552
+ .set("If-Unmodified-Since", luxon_1.DateTime.fromISO("2025-06-15T12:00:01.000Z").toHTTP())
2553
+ .set("X-Unmodified-Since-ISO", "2025-06-15T11:59:59.500Z")
2554
+ .send({ name: "Precise Stale" })
2555
+ .expect(409)];
2556
+ case 1:
2557
+ _a.sent();
2558
+ return [2 /*return*/];
2559
+ }
2560
+ });
2561
+ }); });
2562
+ (0, bun_test_1.it)("falls back to doc.created when doc.updated is unavailable", function () { return __awaiter(void 0, void 0, void 0, function () {
2563
+ var res;
2564
+ return __generator(this, function (_a) {
2565
+ switch (_a.label) {
2566
+ case 0: return [4 /*yield*/, tests_1.FoodModel.collection.updateOne({ _id: spinach._id }, { $unset: { updated: "" } })];
2567
+ case 1:
2568
+ _a.sent();
2569
+ return [4 /*yield*/, agent
2570
+ .patch("/food/".concat(spinach._id))
2571
+ .set("If-Unmodified-Since", luxon_1.DateTime.fromISO("2025-06-15T11:59:59.999Z").toHTTP())
2572
+ .send({ name: "Created Fallback" })
2573
+ .expect(409)];
2574
+ case 2:
2575
+ res = _a.sent();
2576
+ (0, bun_test_1.expect)(res.body.data.name).toBe("Spinach");
2577
+ return [2 /*return*/];
2578
+ }
2579
+ });
2580
+ }); });
2581
+ });
2408
2582
  });
package/dist/auth.d.ts CHANGED
@@ -13,14 +13,17 @@ export interface User {
13
13
  }
14
14
  export interface UserModel extends Model<User> {
15
15
  createAnonymousUser?: (id?: string) => Promise<User>;
16
- postCreate?: (body: any) => Promise<void>;
16
+ postCreate?: (body: Record<string, unknown>) => Promise<void>;
17
17
  createStrategy(): any;
18
18
  serializeUser(): any;
19
19
  deserializeUser(): any;
20
20
  findByUsername(username: string, findOpts: any): any;
21
21
  }
22
- export declare function authenticateMiddleware(anonymous?: boolean): (req: any, res: any, next: any) => any;
23
- export declare function signupUser(userModel: UserModel, email: string, password: string, body?: any): Promise<any>;
22
+ export interface GenerateTokensOptions {
23
+ sessionId?: string;
24
+ }
25
+ export declare function authenticateMiddleware(anonymous?: boolean): (req: express.Request, res: express.Response, next: express.NextFunction) => any;
26
+ export declare function signupUser(userModel: UserModel, email: string, password: string, body?: Record<string, unknown>): Promise<any>;
24
27
  /**
25
28
  * Generates both an access token (JWT) and a refresh token for a given user.
26
29
  *
@@ -38,11 +41,13 @@ export declare function signupUser(userModel: UserModel, email: string, password
38
41
  * authentication providers) to reuse and customize the same token generation logic.
39
42
  * This ensures consistent and secure token issuance across different authentication flows.
40
43
  */
41
- export declare const generateTokens: (user: any, authOptions?: AuthOptions) => Promise<{
44
+ export declare const generateTokens: (user: unknown, authOptions?: AuthOptions, options?: GenerateTokensOptions) => Promise<{
42
45
  refreshToken: null;
43
46
  token: null;
47
+ sessionId?: undefined;
44
48
  } | {
45
- refreshToken: any;
49
+ refreshToken: string | undefined;
50
+ sessionId: string;
46
51
  token: string;
47
52
  }>;
48
53
  export declare function setupAuth(app: express.Application, userModel: UserModel): void;
package/dist/auth.js CHANGED
@@ -57,6 +57,31 @@ var __rest = (this && this.__rest) || function (s, e) {
57
57
  }
58
58
  return t;
59
59
  };
60
+ var __read = (this && this.__read) || function (o, n) {
61
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
62
+ if (!m) return o;
63
+ var i = m.call(o), r, ar = [], e;
64
+ try {
65
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
66
+ }
67
+ catch (error) { e = { error: error }; }
68
+ finally {
69
+ try {
70
+ if (r && !r.done && (m = i["return"])) m.call(i);
71
+ }
72
+ finally { if (e) throw e.error; }
73
+ }
74
+ return ar;
75
+ };
76
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
77
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
78
+ if (ar || !(i in from)) {
79
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
80
+ ar[i] = from[i];
81
+ }
82
+ }
83
+ return to.concat(ar || Array.prototype.slice.call(from));
84
+ };
60
85
  var __importDefault = (this && this.__importDefault) || function (mod) {
61
86
  return (mod && mod.__esModule) ? mod : { "default": mod };
62
87
  };
@@ -67,6 +92,7 @@ exports.signupUser = signupUser;
67
92
  exports.setupAuth = setupAuth;
68
93
  exports.addAuthRoutes = addAuthRoutes;
69
94
  exports.addMeRoutes = addMeRoutes;
95
+ var node_crypto_1 = require("node:crypto");
70
96
  var express_1 = __importDefault(require("express"));
71
97
  var jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
72
98
  var ms_1 = __importDefault(require("ms"));
@@ -76,6 +102,7 @@ var passport_jwt_1 = require("passport-jwt");
76
102
  var passport_local_1 = require("passport-local");
77
103
  var errors_1 = require("./errors");
78
104
  var logger_1 = require("./logger");
105
+ var requestContext_1 = require("./requestContext");
79
106
  function authenticateMiddleware(anonymous) {
80
107
  if (anonymous === void 0) { anonymous = false; }
81
108
  var strategies = ["jwt"];
@@ -96,36 +123,37 @@ function authenticateMiddleware(anonymous) {
96
123
  }
97
124
  function signupUser(userModel, email, password, body) {
98
125
  return __awaiter(this, void 0, void 0, function () {
99
- var _email, _password, bodyRest, user, error_1, error_2;
100
- return __generator(this, function (_a) {
101
- switch (_a.label) {
126
+ var _a, _email, _password, bodyRest, user, error_1, error_2, message;
127
+ return __generator(this, function (_b) {
128
+ switch (_b.label) {
102
129
  case 0:
103
- _email = body.email, _password = body.password, bodyRest = __rest(body, ["email", "password"]);
104
- _a.label = 1;
130
+ _a = body !== null && body !== void 0 ? body : {}, _email = _a.email, _password = _a.password, bodyRest = __rest(_a, ["email", "password"]);
131
+ _b.label = 1;
105
132
  case 1:
106
- _a.trys.push([1, 8, , 9]);
133
+ _b.trys.push([1, 8, , 9]);
107
134
  return [4 /*yield*/, userModel.register(__assign({ email: email }, bodyRest), password)];
108
135
  case 2:
109
- user = _a.sent();
136
+ user = _b.sent();
110
137
  if (!user.postCreate) return [3 /*break*/, 6];
111
- _a.label = 3;
138
+ _b.label = 3;
112
139
  case 3:
113
- _a.trys.push([3, 5, , 6]);
140
+ _b.trys.push([3, 5, , 6]);
114
141
  return [4 /*yield*/, user.postCreate(bodyRest)];
115
142
  case 4:
116
- _a.sent();
143
+ _b.sent();
117
144
  return [3 /*break*/, 6];
118
145
  case 5:
119
- error_1 = _a.sent();
146
+ error_1 = _b.sent();
120
147
  logger_1.logger.error("Error in user.postCreate: ".concat(error_1));
121
148
  throw error_1;
122
149
  case 6: return [4 /*yield*/, user.save()];
123
150
  case 7:
124
- _a.sent();
151
+ _b.sent();
125
152
  return [2 /*return*/, user];
126
153
  case 8:
127
- error_2 = _a.sent();
128
- throw new errors_1.APIError({ title: error_2.message });
154
+ error_2 = _b.sent();
155
+ message = (0, errors_1.errorMessage)(error_2);
156
+ throw new errors_1.APIError({ title: message });
129
157
  case 9: return [2 /*return*/];
130
158
  }
131
159
  });
@@ -148,71 +176,81 @@ function signupUser(userModel, email, password, body) {
148
176
  * authentication providers) to reuse and customize the same token generation logic.
149
177
  * This ensures consistent and secure token issuance across different authentication flows.
150
178
  */
151
- var generateTokens = function (user, authOptions) { return __awaiter(void 0, void 0, void 0, function () {
152
- var tokenSecretOrKey, payload, tokenOptions, token, refreshTokenSecretOrKey, refreshToken, refreshTokenOptions;
153
- return __generator(this, function (_a) {
154
- tokenSecretOrKey = process.env.TOKEN_SECRET;
155
- if (!tokenSecretOrKey) {
156
- throw new Error("TOKEN_SECRET must be set in env.");
157
- }
158
- if (!(user === null || user === void 0 ? void 0 : user._id)) {
159
- logger_1.logger.warn("No user found for token generation");
160
- return [2 /*return*/, { refreshToken: null, token: null }];
161
- }
162
- payload = { id: user._id.toString() };
163
- if (authOptions === null || authOptions === void 0 ? void 0 : authOptions.generateJWTPayload) {
164
- payload = __assign(__assign({}, authOptions.generateJWTPayload(user)), payload);
165
- }
166
- tokenOptions = {
167
- expiresIn: "15m",
168
- };
169
- if (authOptions === null || authOptions === void 0 ? void 0 : authOptions.generateTokenExpiration) {
170
- tokenOptions.expiresIn = authOptions.generateTokenExpiration(user);
171
- }
172
- else if (process.env.TOKEN_EXPIRES_IN) {
173
- try {
174
- // this call to ms is purely for validation of the env variable. If it is invalid,
175
- // we want to be able to log the error and use the default.
176
- (0, ms_1.default)(process.env.TOKEN_EXPIRES_IN);
177
- tokenOptions.expiresIn = process.env.TOKEN_EXPIRES_IN;
179
+ var generateTokens = function (user_1, authOptions_1) {
180
+ var args_1 = [];
181
+ for (var _i = 2; _i < arguments.length; _i++) {
182
+ args_1[_i - 2] = arguments[_i];
183
+ }
184
+ return __awaiter(void 0, __spreadArray([user_1, authOptions_1], __read(args_1), false), void 0, function (user, authOptions, options) {
185
+ var tokenSecretOrKey, tokenUser, sessionId, payload, tokenOptions, token, refreshTokenSecretOrKey, refreshToken, refreshTokenOptions;
186
+ var _a;
187
+ if (options === void 0) { options = {}; }
188
+ return __generator(this, function (_b) {
189
+ tokenSecretOrKey = process.env.TOKEN_SECRET;
190
+ if (!tokenSecretOrKey) {
191
+ throw new Error("TOKEN_SECRET must be set in env.");
178
192
  }
179
- catch (error) {
180
- // This error will result in using the default value above of 15m.
181
- logger_1.logger.error(error);
193
+ tokenUser = user;
194
+ if (!(tokenUser === null || tokenUser === void 0 ? void 0 : tokenUser._id)) {
195
+ logger_1.logger.warn("No user found for token generation");
196
+ return [2 /*return*/, { refreshToken: null, token: null }];
182
197
  }
183
- }
184
- if (process.env.TOKEN_ISSUER) {
185
- tokenOptions.issuer = process.env.TOKEN_ISSUER;
186
- }
187
- token = jsonwebtoken_1.default.sign(payload, tokenSecretOrKey, tokenOptions);
188
- refreshTokenSecretOrKey = process.env.REFRESH_TOKEN_SECRET;
189
- if (refreshTokenSecretOrKey) {
190
- refreshTokenOptions = {
191
- expiresIn: "30d",
198
+ sessionId = (_a = options.sessionId) !== null && _a !== void 0 ? _a : (0, node_crypto_1.randomUUID)();
199
+ payload = { id: String(tokenUser._id), sid: sessionId };
200
+ if (authOptions === null || authOptions === void 0 ? void 0 : authOptions.generateJWTPayload) {
201
+ payload = __assign(__assign({}, authOptions.generateJWTPayload(user)), payload);
202
+ }
203
+ tokenOptions = {
204
+ expiresIn: "15m",
192
205
  };
193
- if (authOptions === null || authOptions === void 0 ? void 0 : authOptions.generateRefreshTokenExpiration) {
194
- refreshTokenOptions.expiresIn = authOptions.generateRefreshTokenExpiration(user);
206
+ if (authOptions === null || authOptions === void 0 ? void 0 : authOptions.generateTokenExpiration) {
207
+ tokenOptions.expiresIn = authOptions.generateTokenExpiration(user);
195
208
  }
196
- else if (process.env.REFRESH_TOKEN_EXPIRES_IN) {
209
+ else if (process.env.TOKEN_EXPIRES_IN) {
197
210
  try {
198
211
  // this call to ms is purely for validation of the env variable. If it is invalid,
199
212
  // we want to be able to log the error and use the default.
200
- (0, ms_1.default)(process.env.REFRESH_TOKEN_EXPIRES_IN);
201
- refreshTokenOptions.expiresIn = process.env.REFRESH_TOKEN_EXPIRES_IN;
213
+ (0, ms_1.default)(process.env.TOKEN_EXPIRES_IN);
214
+ tokenOptions.expiresIn = process.env.TOKEN_EXPIRES_IN;
202
215
  }
203
216
  catch (error) {
204
- // This error will result in using the default value above of 30d.
217
+ // This error will result in using the default value above of 15m.
205
218
  logger_1.logger.error(error);
206
219
  }
207
220
  }
208
- refreshToken = jsonwebtoken_1.default.sign(payload, refreshTokenSecretOrKey, refreshTokenOptions);
209
- }
210
- else {
211
- logger_1.logger.info("REFRESH_TOKEN_SECRET not set so refresh tokens will not be issued");
212
- }
213
- return [2 /*return*/, { refreshToken: refreshToken, token: token }];
221
+ if (process.env.TOKEN_ISSUER) {
222
+ tokenOptions.issuer = process.env.TOKEN_ISSUER;
223
+ }
224
+ token = jsonwebtoken_1.default.sign(payload, tokenSecretOrKey, tokenOptions);
225
+ refreshTokenSecretOrKey = process.env.REFRESH_TOKEN_SECRET;
226
+ if (refreshTokenSecretOrKey) {
227
+ refreshTokenOptions = {
228
+ expiresIn: "30d",
229
+ };
230
+ if (authOptions === null || authOptions === void 0 ? void 0 : authOptions.generateRefreshTokenExpiration) {
231
+ refreshTokenOptions.expiresIn = authOptions.generateRefreshTokenExpiration(user);
232
+ }
233
+ else if (process.env.REFRESH_TOKEN_EXPIRES_IN) {
234
+ try {
235
+ // this call to ms is purely for validation of the env variable. If it is invalid,
236
+ // we want to be able to log the error and use the default.
237
+ (0, ms_1.default)(process.env.REFRESH_TOKEN_EXPIRES_IN);
238
+ refreshTokenOptions.expiresIn = process.env.REFRESH_TOKEN_EXPIRES_IN;
239
+ }
240
+ catch (error) {
241
+ // This error will result in using the default value above of 30d.
242
+ logger_1.logger.error(error);
243
+ }
244
+ }
245
+ refreshToken = jsonwebtoken_1.default.sign(payload, refreshTokenSecretOrKey, refreshTokenOptions);
246
+ }
247
+ else {
248
+ logger_1.logger.info("REFRESH_TOKEN_SECRET not set so refresh tokens will not be issued");
249
+ }
250
+ return [2 /*return*/, { refreshToken: refreshToken, sessionId: sessionId, token: token }];
251
+ });
214
252
  });
215
- }); };
253
+ };
216
254
  exports.generateTokens = generateTokens;
217
255
  // TODO allow customization
218
256
  function setupAuth(app, userModel) {
@@ -274,6 +312,7 @@ function setupAuth(app, userModel) {
274
312
  return __generator(this, function (_a) {
275
313
  switch (_a.label) {
276
314
  case 0:
315
+ user = null;
277
316
  if (!jwtPayload) {
278
317
  return [2 /*return*/, done(null, false)];
279
318
  }
@@ -309,17 +348,17 @@ function setupAuth(app, userModel) {
309
348
  // but passport doesn't give us req.user early enough.
310
349
  function decodeJWTMiddleware(req, res, next) {
311
350
  return __awaiter(this, void 0, void 0, function () {
312
- var token, decoded, userText, details, _a, error_5;
313
- var _b, _c, _d, _e;
314
- return __generator(this, function (_f) {
315
- switch (_f.label) {
351
+ var token, decoded, userText, expiredAt, message, details, sessionId, user, error_5;
352
+ var _a, _b, _c, _d;
353
+ return __generator(this, function (_e) {
354
+ switch (_e.label) {
316
355
  case 0:
317
356
  if (!process.env.TOKEN_SECRET) {
318
357
  return [2 /*return*/, next()];
319
358
  }
320
359
  // Allow requests with a "Secret" prefix to pass through since this is a string value,
321
360
  // not a jwt that needs to be decoded
322
- if (((_c = (_b = req === null || req === void 0 ? void 0 : req.headers) === null || _b === void 0 ? void 0 : _b.authorization) === null || _c === void 0 ? void 0 : _c.split(" ")[0]) === "Secret") {
361
+ if (((_b = (_a = req === null || req === void 0 ? void 0 : req.headers) === null || _a === void 0 ? void 0 : _a.authorization) === null || _b === void 0 ? void 0 : _b.split(" ")[0]) === "Secret") {
323
362
  return [2 /*return*/, next()];
324
363
  }
325
364
  token = customTokenExtractor(req);
@@ -334,26 +373,37 @@ function setupAuth(app, userModel) {
334
373
  });
335
374
  }
336
375
  catch (error) {
337
- userText = ((_d = req.user) === null || _d === void 0 ? void 0 : _d._id) ? " for user ".concat(req.user._id, " ") : "";
338
- details = "[jwt] Error decoding token".concat(userText, ": ").concat(error, ", expired at ").concat(error === null || error === void 0 ? void 0 : error.expiredAt, ", current time: ").concat(Date.now());
376
+ userText = ((_c = req.user) === null || _c === void 0 ? void 0 : _c._id) ? " for user ".concat(req.user._id, " ") : "";
377
+ expiredAt = error && typeof error === "object" && "expiredAt" in error
378
+ ? error.expiredAt
379
+ : undefined;
380
+ message = (0, errors_1.errorMessage)(error);
381
+ details = "[jwt] Error decoding token".concat(userText, ": ").concat(error, ", expired at ").concat(expiredAt, ", current time: ").concat(Date.now());
339
382
  logger_1.logger.debug(details);
340
- return [2 /*return*/, res.status(401).json({ details: details, message: error === null || error === void 0 ? void 0 : error.message })];
383
+ return [2 /*return*/, res.status(401).json({ details: details, message: message })];
384
+ }
385
+ if (!(decoded === null || decoded === void 0 ? void 0 : decoded.id)) return [3 /*break*/, 4];
386
+ sessionId = (0, requestContext_1.getSessionIdFromJwtPayload)(decoded);
387
+ req.authTokenPayload = decoded;
388
+ if (sessionId) {
389
+ req.sessionId = sessionId;
390
+ (0, requestContext_1.setRequestContext)({ sessionId: sessionId });
341
391
  }
342
- if (!decoded.id) return [3 /*break*/, 4];
343
- _f.label = 1;
392
+ _e.label = 1;
344
393
  case 1:
345
- _f.trys.push([1, 3, , 4]);
346
- _a = req;
394
+ _e.trys.push([1, 3, , 4]);
347
395
  return [4 /*yield*/, userModel.findById(decoded.id)];
348
396
  case 2:
349
- _a.user = _f.sent();
350
- if ((_e = req.user) === null || _e === void 0 ? void 0 : _e.disabled) {
397
+ user = _e.sent();
398
+ req.user = user;
399
+ (0, requestContext_1.updateRequestContextFromRequest)(req, res);
400
+ if ((_d = req.user) === null || _d === void 0 ? void 0 : _d.disabled) {
351
401
  logger_1.logger.warn("[jwt] User ".concat(req.user.id, " is disabled"));
352
402
  return [2 /*return*/, res.status(401).json({ status: 401, title: "User is disabled" })];
353
403
  }
354
404
  return [3 /*break*/, 4];
355
405
  case 3:
356
- error_5 = _f.sent();
406
+ error_5 = _e.sent();
357
407
  logger_1.logger.warn("[jwt] Error finding user from id: ".concat(error_5));
358
408
  return [3 /*break*/, 4];
359
409
  case 4: return [2 /*return*/, next()];
@@ -362,6 +412,7 @@ function setupAuth(app, userModel) {
362
412
  });
363
413
  }
364
414
  app.use(decodeJWTMiddleware);
415
+ // biome-ignore lint/suspicious/noExplicitAny: express 5 type for urlencoded doesn't match RequestHandler
365
416
  app.use(express_1.default.urlencoded({ extended: false }));
366
417
  }
367
418
  function addAuthRoutes(app, userModel, authOptions) {
@@ -389,6 +440,10 @@ function addAuthRoutes(app, userModel, authOptions) {
389
440
  return [4 /*yield*/, (0, exports.generateTokens)(user, authOptions)];
390
441
  case 1:
391
442
  tokens = _a.sent();
443
+ if (tokens.sessionId) {
444
+ (0, requestContext_1.setRequestContext)({ sessionId: tokens.sessionId, userId: String(user._id) });
445
+ res.setHeader("X-Session-ID", tokens.sessionId);
446
+ }
392
447
  return [2 /*return*/, res.json({
393
448
  data: { refreshToken: tokens.refreshToken, token: tokens.token, userId: user === null || user === void 0 ? void 0 : user._id },
394
449
  })];
@@ -399,7 +454,7 @@ function addAuthRoutes(app, userModel, authOptions) {
399
454
  });
400
455
  }); });
401
456
  router.post("/refresh_token", function (req, res) { return __awaiter(_this, void 0, void 0, function () {
402
- var refreshTokenSecretOrKey, decoded, user, tokens;
457
+ var refreshTokenSecretOrKey, decoded, message, user, sessionId, tokens;
403
458
  var _a, _b, _c, _d;
404
459
  return __generator(this, function (_e) {
405
460
  switch (_e.label) {
@@ -420,15 +475,24 @@ function addAuthRoutes(app, userModel, authOptions) {
420
475
  }
421
476
  catch (error) {
422
477
  logger_1.logger.error("Error refreshing token for user ".concat((_c = req.user) === null || _c === void 0 ? void 0 : _c.id, ": ").concat(error));
423
- return [2 /*return*/, res.status(401).json({ message: error === null || error === void 0 ? void 0 : error.message })];
478
+ message = (0, errors_1.errorMessage)(error);
479
+ return [2 /*return*/, res.status(401).json({ message: message })];
424
480
  }
425
481
  if (!(decoded === null || decoded === void 0 ? void 0 : decoded.id)) return [3 /*break*/, 3];
426
482
  return [4 /*yield*/, userModel.findById(decoded.id)];
427
483
  case 1:
428
484
  user = _e.sent();
429
- return [4 /*yield*/, (0, exports.generateTokens)(user, authOptions)];
485
+ sessionId = (0, requestContext_1.getSessionIdFromJwtPayload)(decoded);
486
+ return [4 /*yield*/, (0, exports.generateTokens)(user, authOptions, { sessionId: sessionId })];
430
487
  case 2:
431
488
  tokens = _e.sent();
489
+ if (tokens.sessionId) {
490
+ (0, requestContext_1.setRequestContext)({
491
+ sessionId: tokens.sessionId,
492
+ userId: (user === null || user === void 0 ? void 0 : user._id) ? String(user._id) : undefined,
493
+ });
494
+ res.setHeader("X-Session-ID", tokens.sessionId);
495
+ }
432
496
  logger_1.logger.debug("Refreshed token for ".concat(user === null || user === void 0 ? void 0 : user.id));
433
497
  return [2 /*return*/, res.json({ data: { refreshToken: tokens.refreshToken, token: tokens.token } })];
434
498
  case 3:
@@ -441,13 +505,21 @@ function addAuthRoutes(app, userModel, authOptions) {
441
505
  if (!signupDisabled) {
442
506
  router.post("/signup", passport_1.default.authenticate("signup", { failWithError: true, session: false }), function (req, res) { return __awaiter(_this, void 0, void 0, function () {
443
507
  var tokens;
444
- return __generator(this, function (_a) {
445
- switch (_a.label) {
508
+ var _a, _b;
509
+ return __generator(this, function (_c) {
510
+ switch (_c.label) {
446
511
  case 0: return [4 /*yield*/, (0, exports.generateTokens)(req.user, authOptions)];
447
512
  case 1:
448
- tokens = _a.sent();
513
+ tokens = _c.sent();
514
+ if (tokens.sessionId) {
515
+ (0, requestContext_1.setRequestContext)({
516
+ sessionId: tokens.sessionId,
517
+ userId: ((_a = req.user) === null || _a === void 0 ? void 0 : _a._id) ? String(req.user._id) : undefined,
518
+ });
519
+ res.setHeader("X-Session-ID", tokens.sessionId);
520
+ }
449
521
  return [2 /*return*/, res.json({
450
- data: { refreshToken: tokens.refreshToken, token: tokens.token, userId: req.user._id },
522
+ data: { refreshToken: tokens.refreshToken, token: tokens.token, userId: (_b = req.user) === null || _b === void 0 ? void 0 : _b._id },
451
523
  })];
452
524
  }
453
525
  });
@@ -483,7 +555,7 @@ function addMeRoutes(app, userModel, _authOptions) {
483
555
  });
484
556
  }); });
485
557
  router.patch("/me", authenticateMiddleware(), function (req, res) { return __awaiter(_this, void 0, void 0, function () {
486
- var doc, dataObject, error_6;
558
+ var doc, dataObject, error_6, message;
487
559
  var _a;
488
560
  return __generator(this, function (_b) {
489
561
  switch (_b.label) {
@@ -509,7 +581,8 @@ function addMeRoutes(app, userModel, _authOptions) {
509
581
  return [2 /*return*/, res.json({ data: dataObject })];
510
582
  case 4:
511
583
  error_6 = _b.sent();
512
- return [2 /*return*/, res.status(403).send({ message: error_6.message })];
584
+ message = (0, errors_1.errorMessage)(error_6);
585
+ return [2 /*return*/, res.status(403).send({ message: message })];
513
586
  case 5: return [2 /*return*/];
514
587
  }
515
588
  });