@terreno/api 0.20.2 → 0.22.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 (107) hide show
  1. package/.ai/guidelines/core.md +71 -0
  2. package/.ai/skills/mongoose-schema-safety/SKILL.md +143 -0
  3. package/README.md +54 -1
  4. package/bunfig.toml +1 -1
  5. package/dist/__tests__/versionCheckPlugin.test.js +29 -7
  6. package/dist/actions.openApi.test.js +13 -11
  7. package/dist/api.js +98 -11
  8. package/dist/api.query.test.js +31 -1
  9. package/dist/api.test.js +211 -0
  10. package/dist/auth.test.js +418 -43
  11. package/dist/betterAuth.d.ts +1 -1
  12. package/dist/consentApp.test.js +1 -0
  13. package/dist/example.js +4 -4
  14. package/dist/expressServer.d.ts +0 -22
  15. package/dist/expressServer.js +1 -125
  16. package/dist/expressServer.test.js +90 -91
  17. package/dist/githubAuth.test.js +22 -22
  18. package/dist/logger.d.ts +154 -0
  19. package/dist/logger.js +445 -26
  20. package/dist/logger.test.js +435 -0
  21. package/dist/middleware.d.ts +7 -0
  22. package/dist/middleware.js +58 -1
  23. package/dist/middleware.test.js +159 -0
  24. package/dist/models/consentForm.js +2 -1
  25. package/dist/models/consentResponse.js +2 -1
  26. package/dist/models/versionConfig.js +2 -1
  27. package/dist/openApi.test.js +10 -17
  28. package/dist/openApiBuilder.d.ts +18 -0
  29. package/dist/openApiBuilder.js +21 -0
  30. package/dist/openApiBuilder.test.js +34 -10
  31. package/dist/permissions.test.js +10 -43
  32. package/dist/populate.test.js +10 -42
  33. package/dist/realtime/changeStreamWatcher.d.ts +4 -4
  34. package/dist/realtime/changeStreamWatcher.js +2 -4
  35. package/dist/realtime/queryMatcher.d.ts +1 -1
  36. package/dist/realtime/queryMatcher.js +39 -14
  37. package/dist/realtime/types.d.ts +3 -3
  38. package/dist/requestContext.d.ts +61 -0
  39. package/dist/requestContext.js +74 -0
  40. package/dist/secretProviders.test.js +335 -0
  41. package/dist/syncConsents.test.js +2 -2
  42. package/dist/terrenoApp.d.ts +27 -15
  43. package/dist/terrenoApp.js +24 -14
  44. package/dist/terrenoApp.test.js +52 -0
  45. package/dist/tests/bunSetup.js +66 -262
  46. package/dist/tests/createTestData.d.ts +9 -0
  47. package/dist/tests/createTestData.js +272 -0
  48. package/dist/tests/models.d.ts +71 -0
  49. package/dist/tests/models.js +134 -0
  50. package/dist/tests/mongoTestSetup.d.ts +7 -0
  51. package/dist/tests/mongoTestSetup.js +150 -0
  52. package/dist/tests/testEnv.d.ts +0 -0
  53. package/dist/tests/testEnv.js +6 -0
  54. package/dist/tests/testHelper.d.ts +22 -0
  55. package/dist/tests/testHelper.js +115 -0
  56. package/dist/tests/types.d.ts +29 -0
  57. package/dist/tests/types.js +2 -0
  58. package/dist/tests.d.ts +10 -78
  59. package/dist/tests.js +24 -241
  60. package/dist/transformers.test.js +14 -50
  61. package/package.json +18 -4
  62. package/src/__snapshots__/openApiBuilder.test.ts.snap +1 -0
  63. package/src/__tests__/versionCheckPlugin.test.ts +43 -15
  64. package/src/actions.openApi.test.ts +12 -10
  65. package/src/api.query.test.ts +24 -1
  66. package/src/api.test.ts +169 -0
  67. package/src/api.ts +71 -0
  68. package/src/auth.test.ts +287 -39
  69. package/src/betterAuth.ts +1 -1
  70. package/src/consentApp.test.ts +1 -0
  71. package/src/example.ts +4 -4
  72. package/src/expressServer.test.ts +82 -85
  73. package/src/expressServer.ts +1 -213
  74. package/src/githubAuth.test.ts +22 -22
  75. package/src/logger.test.ts +466 -1
  76. package/src/logger.ts +477 -14
  77. package/src/middleware.test.ts +74 -2
  78. package/src/middleware.ts +57 -0
  79. package/src/models/consentForm.ts +3 -4
  80. package/src/models/consentResponse.ts +6 -4
  81. package/src/models/versionConfig.ts +3 -4
  82. package/src/openApi.test.ts +10 -17
  83. package/src/openApiBuilder.test.ts +27 -10
  84. package/src/openApiBuilder.ts +24 -0
  85. package/src/permissions.test.ts +8 -23
  86. package/src/populate.test.ts +7 -22
  87. package/src/realtime/changeStreamWatcher.ts +15 -10
  88. package/src/realtime/queryMatcher.ts +54 -27
  89. package/src/realtime/types.ts +4 -4
  90. package/src/requestContext.ts +86 -0
  91. package/src/secretProviders.test.ts +219 -1
  92. package/src/syncConsents.test.ts +1 -1
  93. package/src/terrenoApp.test.ts +38 -0
  94. package/src/terrenoApp.ts +37 -15
  95. package/src/tests/bunSetup.ts +22 -236
  96. package/src/tests/createTestData.ts +176 -0
  97. package/src/tests/models.ts +164 -0
  98. package/src/tests/mongoTestSetup.ts +69 -0
  99. package/src/tests/testEnv.ts +4 -0
  100. package/src/tests/testHelper.ts +57 -0
  101. package/src/tests/types.ts +35 -0
  102. package/src/tests.ts +40 -231
  103. package/src/transformers.test.ts +11 -30
  104. package/tsconfig.typedoc.json +4 -0
  105. package/dist/tests/index.d.ts +0 -1
  106. package/dist/tests/index.js +0 -17
  107. package/src/tests/index.ts +0 -1
package/dist/auth.test.js CHANGED
@@ -104,9 +104,9 @@ var bun_test_1 = require("bun:test");
104
104
  var supertest_1 = __importDefault(require("supertest"));
105
105
  var api_1 = require("./api");
106
106
  var auth_1 = require("./auth");
107
- var expressServer_1 = require("./expressServer");
108
107
  var permissions_1 = require("./permissions");
109
108
  var requestContext_1 = require("./requestContext");
109
+ var terrenoApp_1 = require("./terrenoApp");
110
110
  var tests_1 = require("./tests");
111
111
  var transformers_1 = require("./transformers");
112
112
  var utils_1 = require("./utils");
@@ -118,7 +118,6 @@ var decodeTokenPayload = function (token) {
118
118
  var app;
119
119
  var admin;
120
120
  var contextEvents;
121
- var notAdmin;
122
121
  var agent;
123
122
  (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
124
123
  function addRoutes(router) {
@@ -202,45 +201,23 @@ var decodeTokenPayload = function (token) {
202
201
  }); },
203
202
  }));
204
203
  }
205
- var _a;
206
- return __generator(this, function (_b) {
207
- switch (_b.label) {
204
+ var testData;
205
+ return __generator(this, function (_a) {
206
+ switch (_a.label) {
208
207
  case 0:
209
208
  // Reset to real time - don't freeze time here as passport-local-mongoose
210
209
  // lockout mechanism needs real time to progress
211
210
  (0, bun_test_1.setSystemTime)();
212
- return [4 /*yield*/, (0, tests_1.setupDb)()];
211
+ return [4 /*yield*/, (0, tests_1.setupTestData)()];
213
212
  case 1:
214
- _a = __read.apply(void 0, [_b.sent(), 2]), admin = _a[0], notAdmin = _a[1];
213
+ testData = _a.sent();
214
+ admin = testData.users.admin;
215
215
  contextEvents = [];
216
- return [4 /*yield*/, Promise.all([
217
- tests_1.FoodModel.create({
218
- calories: 1,
219
- created: new Date(),
220
- name: "Spinach",
221
- ownerId: notAdmin._id,
222
- }),
223
- tests_1.FoodModel.create({
224
- calories: 100,
225
- created: Date.now() - 10,
226
- hidden: true,
227
- name: "Apple",
228
- ownerId: admin._id,
229
- }),
230
- tests_1.FoodModel.create({
231
- calories: 100,
232
- created: Date.now() - 10,
233
- name: "Carrots",
234
- ownerId: admin._id,
235
- }),
236
- ])];
237
- case 2:
238
- _b.sent();
239
- app = (0, expressServer_1.setupServer)({
240
- addRoutes: addRoutes,
216
+ app = new terrenoApp_1.TerrenoApp({
217
+ configureApp: addRoutes,
241
218
  skipListen: true,
242
219
  userModel: tests_1.UserModel,
243
- });
220
+ }).build();
244
221
  agent = supertest_1.default.agent(app);
245
222
  return [2 /*return*/];
246
223
  }
@@ -315,7 +292,7 @@ var decodeTokenPayload = function (token) {
315
292
  return [4 /*yield*/, agent.get("/food").expect(200)];
316
293
  case 7:
317
294
  getRes = _b.sent();
318
- (0, bun_test_1.expect)(getRes.body.data).toHaveLength(3);
295
+ (0, bun_test_1.expect)(getRes.body.data).toHaveLength(4);
319
296
  (0, bun_test_1.expect)(getRes.body.data.find(function (f) { return f.name === "Peas"; })).toBeDefined();
320
297
  return [4 /*yield*/, agent
321
298
  .patch("/food/".concat(food._id))
@@ -547,7 +524,7 @@ var decodeTokenPayload = function (token) {
547
524
  return [4 /*yield*/, agent.get("/food").expect(200)];
548
525
  case 5:
549
526
  getRes = _b.sent();
550
- (0, bun_test_1.expect)(getRes.body.data).toHaveLength(3);
527
+ (0, bun_test_1.expect)(getRes.body.data).toHaveLength(4);
551
528
  food = getRes.body.data.find(function (f) { return f.name === "Apple"; });
552
529
  (0, bun_test_1.expect)(food).toBeDefined();
553
530
  return [4 /*yield*/, agent
@@ -1155,14 +1132,14 @@ var decodeTokenPayload = function (token) {
1155
1132
  switch (_a.label) {
1156
1133
  case 0:
1157
1134
  (0, bun_test_1.setSystemTime)();
1158
- return [4 /*yield*/, (0, tests_1.setupDb)()];
1135
+ return [4 /*yield*/, (0, tests_1.setupTestData)()];
1159
1136
  case 1:
1160
1137
  _a.sent();
1161
- app = (0, expressServer_1.setupServer)({
1162
- addRoutes: function () { },
1138
+ app = new terrenoApp_1.TerrenoApp({
1139
+ configureApp: function () { },
1163
1140
  skipListen: true,
1164
1141
  userModel: tests_1.UserModel,
1165
- });
1142
+ }).build();
1166
1143
  agent = supertest_1.default.agent(app);
1167
1144
  return [2 /*return*/];
1168
1145
  }
@@ -1265,14 +1242,14 @@ var decodeTokenPayload = function (token) {
1265
1242
  switch (_a.label) {
1266
1243
  case 0:
1267
1244
  (0, bun_test_1.setSystemTime)();
1268
- return [4 /*yield*/, (0, tests_1.setupDb)()];
1245
+ return [4 /*yield*/, (0, tests_1.setupTestData)()];
1269
1246
  case 1:
1270
1247
  _a.sent();
1271
- app = (0, expressServer_1.setupServer)({
1272
- addRoutes: function () { },
1248
+ app = new terrenoApp_1.TerrenoApp({
1249
+ configureApp: function () { },
1273
1250
  skipListen: true,
1274
1251
  userModel: tests_1.UserModel,
1275
- });
1252
+ }).build();
1276
1253
  agent = supertest_1.default.agent(app);
1277
1254
  return [2 /*return*/];
1278
1255
  }
@@ -1326,4 +1303,402 @@ var decodeTokenPayload = function (token) {
1326
1303
  }
1327
1304
  });
1328
1305
  }); });
1306
+ (0, bun_test_1.it)("PATCH /auth/me returns 404 when user is deleted after auth", function () { return __awaiter(void 0, void 0, void 0, function () {
1307
+ var _a, _admin, notAdmin, jwtLib, notAdminId, token, res;
1308
+ return __generator(this, function (_b) {
1309
+ switch (_b.label) {
1310
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
1311
+ case 1:
1312
+ _a = __read.apply(void 0, [_b.sent(), 2]), _admin = _a[0], notAdmin = _a[1];
1313
+ return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("jsonwebtoken")); })];
1314
+ case 2:
1315
+ jwtLib = (_b.sent()).default;
1316
+ notAdminId = notAdmin._id;
1317
+ token = jwtLib.sign({ id: notAdminId.toString() }, process.env.TOKEN_SECRET, {
1318
+ issuer: process.env.TOKEN_ISSUER,
1319
+ });
1320
+ return [4 /*yield*/, tests_1.UserModel.deleteOne({ _id: notAdminId })];
1321
+ case 3:
1322
+ _b.sent();
1323
+ return [4 /*yield*/, agent
1324
+ .patch("/auth/me")
1325
+ .set("authorization", "Bearer ".concat(token))
1326
+ .send({ email: "x@x.com" })];
1327
+ case 4:
1328
+ res = _b.sent();
1329
+ (0, bun_test_1.expect)([401, 404]).toContain(res.status);
1330
+ return [2 /*return*/];
1331
+ }
1332
+ });
1333
+ }); });
1334
+ (0, bun_test_1.it)("PATCH /auth/me returns 403 on validation error", function () { return __awaiter(void 0, void 0, void 0, function () {
1335
+ var _a, admin, jwtLib, adminId, token, res;
1336
+ return __generator(this, function (_b) {
1337
+ switch (_b.label) {
1338
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
1339
+ case 1:
1340
+ _a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
1341
+ return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("jsonwebtoken")); })];
1342
+ case 2:
1343
+ jwtLib = (_b.sent()).default;
1344
+ adminId = admin._id;
1345
+ token = jwtLib.sign({ id: adminId.toString() }, process.env.TOKEN_SECRET, {
1346
+ issuer: process.env.TOKEN_ISSUER,
1347
+ });
1348
+ return [4 /*yield*/, agent
1349
+ .patch("/auth/me")
1350
+ .set("authorization", "Bearer ".concat(token))
1351
+ .send({ admin: "not_a_boolean_value_but_will_be_cast" })];
1352
+ case 3:
1353
+ res = _b.sent();
1354
+ (0, bun_test_1.expect)([200, 403]).toContain(res.status);
1355
+ return [2 /*return*/];
1356
+ }
1357
+ });
1358
+ }); });
1359
+ });
1360
+ (0, bun_test_1.describe)("Secret prefix authorization bypass", function () {
1361
+ var app;
1362
+ var agent;
1363
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
1364
+ return __generator(this, function (_a) {
1365
+ switch (_a.label) {
1366
+ case 0:
1367
+ (0, bun_test_1.setSystemTime)();
1368
+ return [4 /*yield*/, (0, tests_1.setupTestData)()];
1369
+ case 1:
1370
+ _a.sent();
1371
+ app = new terrenoApp_1.TerrenoApp({
1372
+ configureApp: function (router) {
1373
+ router.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
1374
+ allowAnonymous: true,
1375
+ permissions: {
1376
+ create: [],
1377
+ delete: [],
1378
+ list: [permissions_1.Permissions.IsAny],
1379
+ read: [permissions_1.Permissions.IsAny],
1380
+ update: [],
1381
+ },
1382
+ }));
1383
+ },
1384
+ skipListen: true,
1385
+ userModel: tests_1.UserModel,
1386
+ }).build();
1387
+ agent = supertest_1.default.agent(app);
1388
+ return [2 /*return*/];
1389
+ }
1390
+ });
1391
+ }); });
1392
+ (0, bun_test_1.afterEach)(function () {
1393
+ (0, bun_test_1.setSystemTime)();
1394
+ });
1395
+ (0, bun_test_1.it)("passes through with Secret prefix authorization header without JWT decoding", function () { return __awaiter(void 0, void 0, void 0, function () {
1396
+ var res;
1397
+ return __generator(this, function (_a) {
1398
+ switch (_a.label) {
1399
+ case 0: return [4 /*yield*/, agent.get("/food").set("authorization", "Secret my-secret-token").expect(200)];
1400
+ case 1:
1401
+ res = _a.sent();
1402
+ (0, bun_test_1.expect)(res.body.data).toBeDefined();
1403
+ return [2 /*return*/];
1404
+ }
1405
+ });
1406
+ }); });
1407
+ });
1408
+ (0, bun_test_1.describe)("generateTokens env integration", function () {
1409
+ var OLD_ENV = process.env;
1410
+ (0, bun_test_1.beforeEach)(function () {
1411
+ process.env = __assign({}, OLD_ENV);
1412
+ process.env.TOKEN_SECRET = "secret";
1413
+ process.env.REFRESH_TOKEN_SECRET = "refresh_secret";
1414
+ });
1415
+ (0, bun_test_1.afterEach)(function () {
1416
+ process.env = OLD_ENV;
1417
+ });
1418
+ (0, bun_test_1.it)("includes TOKEN_ISSUER in token when set", function () { return __awaiter(void 0, void 0, void 0, function () {
1419
+ var result, decoded;
1420
+ return __generator(this, function (_a) {
1421
+ switch (_a.label) {
1422
+ case 0:
1423
+ process.env.TOKEN_ISSUER = "test-issuer";
1424
+ return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: "user-123" })];
1425
+ case 1:
1426
+ result = _a.sent();
1427
+ decoded = decodeTokenPayload(result.token);
1428
+ (0, bun_test_1.expect)(decoded.iss).toBe("test-issuer");
1429
+ return [2 /*return*/];
1430
+ }
1431
+ });
1432
+ }); });
1433
+ (0, bun_test_1.it)("generates a unique sessionId when none provided", function () { return __awaiter(void 0, void 0, void 0, function () {
1434
+ var result1, result2;
1435
+ return __generator(this, function (_a) {
1436
+ switch (_a.label) {
1437
+ case 0: return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: "user-123" })];
1438
+ case 1:
1439
+ result1 = _a.sent();
1440
+ return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: "user-123" })];
1441
+ case 2:
1442
+ result2 = _a.sent();
1443
+ (0, bun_test_1.expect)(result1.sessionId).toBeDefined();
1444
+ (0, bun_test_1.expect)(result2.sessionId).toBeDefined();
1445
+ (0, bun_test_1.expect)(result1.sessionId).not.toBe(result2.sessionId);
1446
+ return [2 /*return*/];
1447
+ }
1448
+ });
1449
+ }); });
1450
+ (0, bun_test_1.it)("uses provided sessionId from options", function () { return __awaiter(void 0, void 0, void 0, function () {
1451
+ var result, decoded;
1452
+ return __generator(this, function (_a) {
1453
+ switch (_a.label) {
1454
+ case 0: return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: "user-123" }, undefined, {
1455
+ sessionId: "custom-session-id",
1456
+ })];
1457
+ case 1:
1458
+ result = _a.sent();
1459
+ decoded = decodeTokenPayload(result.token);
1460
+ (0, bun_test_1.expect)(decoded.sid).toBe("custom-session-id");
1461
+ (0, bun_test_1.expect)(result.sessionId).toBe("custom-session-id");
1462
+ return [2 /*return*/];
1463
+ }
1464
+ });
1465
+ }); });
1466
+ });
1467
+ (0, bun_test_1.describe)("refresh_token without REFRESH_TOKEN_SECRET", function () {
1468
+ var app;
1469
+ var agent;
1470
+ var OLD_ENV = process.env;
1471
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
1472
+ return __generator(this, function (_a) {
1473
+ switch (_a.label) {
1474
+ case 0:
1475
+ (0, bun_test_1.setSystemTime)();
1476
+ process.env = __assign({}, OLD_ENV);
1477
+ return [4 /*yield*/, (0, tests_1.setupTestData)()];
1478
+ case 1:
1479
+ _a.sent();
1480
+ app = new terrenoApp_1.TerrenoApp({
1481
+ configureApp: function () { },
1482
+ skipListen: true,
1483
+ userModel: tests_1.UserModel,
1484
+ }).build();
1485
+ agent = supertest_1.default.agent(app);
1486
+ return [2 /*return*/];
1487
+ }
1488
+ });
1489
+ }); });
1490
+ (0, bun_test_1.afterEach)(function () {
1491
+ (0, bun_test_1.setSystemTime)();
1492
+ process.env = OLD_ENV;
1493
+ });
1494
+ (0, bun_test_1.it)("returns 401 when REFRESH_TOKEN_SECRET is not set", function () { return __awaiter(void 0, void 0, void 0, function () {
1495
+ var res;
1496
+ return __generator(this, function (_a) {
1497
+ switch (_a.label) {
1498
+ case 0:
1499
+ process.env.REFRESH_TOKEN_SECRET = "";
1500
+ return [4 /*yield*/, agent
1501
+ .post("/auth/refresh_token")
1502
+ .send({ refreshToken: "some-token" })
1503
+ .expect(401)];
1504
+ case 1:
1505
+ res = _a.sent();
1506
+ (0, bun_test_1.expect)(res.body.message).toContain("No REFRESH_TOKEN_SECRET set");
1507
+ return [2 /*return*/];
1508
+ }
1509
+ });
1510
+ }); });
1511
+ });
1512
+ (0, bun_test_1.describe)("generateTokens with custom TOKEN_EXPIRES_IN", function () {
1513
+ var OLD_ENV = process.env;
1514
+ (0, bun_test_1.beforeEach)(function () {
1515
+ process.env = __assign({}, OLD_ENV);
1516
+ process.env.TOKEN_SECRET = "secret";
1517
+ process.env.REFRESH_TOKEN_SECRET = "refresh_secret";
1518
+ });
1519
+ (0, bun_test_1.afterEach)(function () {
1520
+ process.env = OLD_ENV;
1521
+ });
1522
+ (0, bun_test_1.it)("uses TOKEN_EXPIRES_IN when set to a valid duration", function () { return __awaiter(void 0, void 0, void 0, function () {
1523
+ var result, decoded, diffSeconds;
1524
+ return __generator(this, function (_a) {
1525
+ switch (_a.label) {
1526
+ case 0:
1527
+ process.env.TOKEN_EXPIRES_IN = "1h";
1528
+ return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: "user-123" })];
1529
+ case 1:
1530
+ result = _a.sent();
1531
+ (0, bun_test_1.expect)(result.token).toBeDefined();
1532
+ decoded = decodeTokenPayload(result.token);
1533
+ diffSeconds = decoded.exp - decoded.iat;
1534
+ // 1h = 3600s
1535
+ (0, bun_test_1.expect)(diffSeconds).toBe(3600);
1536
+ return [2 /*return*/];
1537
+ }
1538
+ });
1539
+ }); });
1540
+ (0, bun_test_1.it)("uses REFRESH_TOKEN_EXPIRES_IN when set to a valid duration", function () { return __awaiter(void 0, void 0, void 0, function () {
1541
+ var result, decoded, diffSeconds;
1542
+ return __generator(this, function (_a) {
1543
+ switch (_a.label) {
1544
+ case 0:
1545
+ process.env.REFRESH_TOKEN_EXPIRES_IN = "7d";
1546
+ return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: "user-123" })];
1547
+ case 1:
1548
+ result = _a.sent();
1549
+ (0, bun_test_1.expect)(result.refreshToken).toBeDefined();
1550
+ decoded = decodeTokenPayload(result.refreshToken);
1551
+ diffSeconds = decoded.exp - decoded.iat;
1552
+ // 7d = 604800s
1553
+ (0, bun_test_1.expect)(diffSeconds).toBe(604800);
1554
+ return [2 /*return*/];
1555
+ }
1556
+ });
1557
+ }); });
1558
+ });
1559
+ (0, bun_test_1.describe)("JWT cookie extraction and /me routes edge cases", function () {
1560
+ var app;
1561
+ var agent;
1562
+ var OLD_ENV = process.env;
1563
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
1564
+ return __generator(this, function (_a) {
1565
+ switch (_a.label) {
1566
+ case 0:
1567
+ (0, bun_test_1.setSystemTime)();
1568
+ process.env = __assign({}, OLD_ENV);
1569
+ return [4 /*yield*/, (0, tests_1.setupTestData)()];
1570
+ case 1:
1571
+ _a.sent();
1572
+ app = new terrenoApp_1.TerrenoApp({
1573
+ configureApp: function () { },
1574
+ skipListen: true,
1575
+ userModel: tests_1.UserModel,
1576
+ }).build();
1577
+ agent = supertest_1.default.agent(app);
1578
+ return [2 /*return*/];
1579
+ }
1580
+ });
1581
+ }); });
1582
+ (0, bun_test_1.afterEach)(function () {
1583
+ (0, bun_test_1.setSystemTime)();
1584
+ process.env = OLD_ENV;
1585
+ });
1586
+ (0, bun_test_1.it)("returns 401 for /me when no user is authenticated", function () { return __awaiter(void 0, void 0, void 0, function () {
1587
+ var res;
1588
+ return __generator(this, function (_a) {
1589
+ switch (_a.label) {
1590
+ case 0: return [4 /*yield*/, agent.get("/auth/me").expect(401)];
1591
+ case 1:
1592
+ res = _a.sent();
1593
+ (0, bun_test_1.expect)(res.status).toBe(401);
1594
+ return [2 /*return*/];
1595
+ }
1596
+ });
1597
+ }); });
1598
+ (0, bun_test_1.it)("returns 401 for PATCH /me when no user is authenticated", function () { return __awaiter(void 0, void 0, void 0, function () {
1599
+ var res;
1600
+ return __generator(this, function (_a) {
1601
+ switch (_a.label) {
1602
+ case 0: return [4 /*yield*/, agent.patch("/auth/me").send({ name: "Updated" }).expect(401)];
1603
+ case 1:
1604
+ res = _a.sent();
1605
+ (0, bun_test_1.expect)(res.status).toBe(401);
1606
+ return [2 /*return*/];
1607
+ }
1608
+ });
1609
+ }); });
1610
+ (0, bun_test_1.it)("returns 404 for /me when user is deleted from database", function () { return __awaiter(void 0, void 0, void 0, function () {
1611
+ var loginRes, _a, token, userId, freshAgent, res;
1612
+ return __generator(this, function (_b) {
1613
+ switch (_b.label) {
1614
+ case 0: return [4 /*yield*/, agent
1615
+ .post("/auth/login")
1616
+ .send({ email: "notAdmin@example.com", password: "password" })
1617
+ .expect(200)];
1618
+ case 1:
1619
+ loginRes = _b.sent();
1620
+ _a = loginRes.body.data, token = _a.token, userId = _a.userId;
1621
+ // Delete the user from DB
1622
+ return [4 /*yield*/, tests_1.UserModel.deleteOne({ _id: userId })];
1623
+ case 2:
1624
+ // Delete the user from DB
1625
+ _b.sent();
1626
+ freshAgent = supertest_1.default.agent(app);
1627
+ return [4 /*yield*/, freshAgent.get("/auth/me").set("authorization", "Bearer ".concat(token))];
1628
+ case 3:
1629
+ res = _b.sent();
1630
+ // Without the user, the JWT verify succeeds but findById returns null
1631
+ (0, bun_test_1.expect)([401, 404]).toContain(res.status);
1632
+ return [2 /*return*/];
1633
+ }
1634
+ });
1635
+ }); });
1636
+ });
1637
+ (0, bun_test_1.describe)("login error and disabled user paths", function () {
1638
+ var app;
1639
+ var agent;
1640
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
1641
+ return __generator(this, function (_a) {
1642
+ switch (_a.label) {
1643
+ case 0:
1644
+ (0, bun_test_1.setSystemTime)();
1645
+ return [4 /*yield*/, (0, tests_1.setupTestData)()];
1646
+ case 1:
1647
+ _a.sent();
1648
+ app = new terrenoApp_1.TerrenoApp({
1649
+ configureApp: function () { },
1650
+ skipListen: true,
1651
+ userModel: tests_1.UserModel,
1652
+ }).build();
1653
+ agent = supertest_1.default.agent(app);
1654
+ return [2 /*return*/];
1655
+ }
1656
+ });
1657
+ }); });
1658
+ (0, bun_test_1.afterEach)(function () {
1659
+ (0, bun_test_1.setSystemTime)();
1660
+ });
1661
+ (0, bun_test_1.it)("returns 401 with message for invalid credentials (no user found)", function () { return __awaiter(void 0, void 0, void 0, function () {
1662
+ var res;
1663
+ return __generator(this, function (_a) {
1664
+ switch (_a.label) {
1665
+ case 0: return [4 /*yield*/, agent
1666
+ .post("/auth/login")
1667
+ .send({ email: "nonexistent@example.com", password: "wrong" })
1668
+ .expect(401)];
1669
+ case 1:
1670
+ res = _a.sent();
1671
+ (0, bun_test_1.expect)(res.body.message).toBeDefined();
1672
+ return [2 /*return*/];
1673
+ }
1674
+ });
1675
+ }); });
1676
+ (0, bun_test_1.it)("returns 401 when disabled user tries to access protected route", function () { return __awaiter(void 0, void 0, void 0, function () {
1677
+ var loginRes, _a, token, userId, freshAgent, res;
1678
+ return __generator(this, function (_b) {
1679
+ switch (_b.label) {
1680
+ case 0: return [4 /*yield*/, agent
1681
+ .post("/auth/login")
1682
+ .send({ email: "notAdmin@example.com", password: "password" })
1683
+ .expect(200)];
1684
+ case 1:
1685
+ loginRes = _b.sent();
1686
+ _a = loginRes.body.data, token = _a.token, userId = _a.userId;
1687
+ // Disable the user
1688
+ return [4 /*yield*/, tests_1.UserModel.findByIdAndUpdate(userId, { disabled: true })];
1689
+ case 2:
1690
+ // Disable the user
1691
+ _b.sent();
1692
+ freshAgent = supertest_1.default.agent(app);
1693
+ return [4 /*yield*/, freshAgent
1694
+ .get("/auth/me")
1695
+ .set("authorization", "Bearer ".concat(token))
1696
+ .expect(401)];
1697
+ case 3:
1698
+ res = _b.sent();
1699
+ (0, bun_test_1.expect)(res.body.title).toContain("disabled");
1700
+ return [2 /*return*/];
1701
+ }
1702
+ });
1703
+ }); });
1329
1704
  });
@@ -53,7 +53,7 @@ export interface BetterAuthConfig {
53
53
  baseURL?: string;
54
54
  }
55
55
  /**
56
- * Auth provider selection for setupServer.
56
+ * Auth provider selection for TerrenoApp.
57
57
  * - "jwt": Traditional JWT/Passport authentication (default)
58
58
  * - "better-auth": Better Auth with OAuth support
59
59
  */
@@ -118,6 +118,7 @@ var buildApp = function (consentAppOptions) {
118
118
  case 1:
119
119
  res = _a.sent();
120
120
  (0, bun_test_1.expect)(res.body.data).toHaveLength(0);
121
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
121
122
  return [2 /*return*/];
122
123
  }
123
124
  });
package/dist/example.js CHANGED
@@ -52,10 +52,10 @@ var mongoose_1 = __importStar(require("mongoose"));
52
52
  var passport_local_mongoose_1 = __importDefault(require("passport-local-mongoose"));
53
53
  var api_1 = require("./api");
54
54
  var auth_1 = require("./auth");
55
- var expressServer_1 = require("./expressServer");
56
55
  var logger_1 = require("./logger");
57
56
  var permissions_1 = require("./permissions");
58
57
  var plugins_1 = require("./plugins");
58
+ var terrenoApp_1 = require("./terrenoApp");
59
59
  mongoose_1.default
60
60
  .connect("mongodb://localhost:27017/example")
61
61
  .then(function () {
@@ -119,12 +119,12 @@ var getBaseServer = function () {
119
119
  update: [permissions_1.Permissions.IsOwner],
120
120
  }, queryFields: ["name", "calories", "created", "ownerId", "hidden"] })));
121
121
  };
122
- return (0, expressServer_1.setupServer)({
123
- addRoutes: addRoutes,
122
+ return new terrenoApp_1.TerrenoApp({
123
+ configureApp: addRoutes,
124
124
  loggingOptions: {
125
125
  level: "debug",
126
126
  },
127
127
  userModel: UserModel,
128
- });
128
+ }).build();
129
129
  };
130
130
  getBaseServer();
@@ -1,10 +1,6 @@
1
- import * as Sentry from "@sentry/bun";
2
1
  import express, { type Router } from "express";
3
2
  import type jwt from "jsonwebtoken";
4
3
  import type { ModelRouterOptions } from "./api";
5
- import { type UserModel as UserMongooseModel } from "./auth";
6
- import { type GitHubAuthOptions } from "./githubAuth";
7
- import { type LoggingOptions } from "./logger";
8
4
  export declare const setupEnvironment: () => void;
9
5
  export type AddRoutes = (router: Router, options?: Partial<ModelRouterOptions<unknown>>) => void;
10
6
  export declare const logRequests: (req: any, res: any, next: express.NextFunction) => void;
@@ -15,24 +11,6 @@ export interface AuthOptions {
15
11
  generateTokenExpiration?: (user: any) => number | jwt.SignOptions["expiresIn"];
16
12
  generateRefreshTokenExpiration?: (user: any) => number | jwt.SignOptions["expiresIn"];
17
13
  }
18
- export interface SetupServerOptions {
19
- userModel: UserMongooseModel;
20
- addRoutes: AddRoutes;
21
- loggingOptions?: LoggingOptions;
22
- logRequests?: boolean;
23
- authOptions?: AuthOptions;
24
- /**
25
- * GitHub OAuth configuration. When provided, enables GitHub authentication.
26
- * Requires the user schema to have GitHub fields (use githubUserPlugin).
27
- */
28
- githubAuth?: GitHubAuthOptions;
29
- skipListen?: boolean;
30
- corsOrigin?: string | boolean | RegExp | Array<boolean | string | RegExp> | ((requestOrigin: string | undefined, callback: (err: Error | null, origin?: boolean | string | RegExp | Array<boolean | string | RegExp>) => void) => void);
31
- addMiddleware?: AddRoutes;
32
- ignoreTraces?: string[];
33
- sentryOptions?: Sentry.BunOptions;
34
- }
35
- export declare const setupServer: (options: SetupServerOptions) => express.Application;
36
14
  export declare const cronjob: (name: string, schedule: "hourly" | "minutely" | string, callback: () => void) => void;
37
15
  export interface WrapScriptOptions {
38
16
  onFinish?: (result?: unknown) => void | Promise<void>;