@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.
- package/.ai/guidelines/core.md +71 -0
- package/.ai/skills/mongoose-schema-safety/SKILL.md +143 -0
- package/README.md +54 -1
- package/bunfig.toml +1 -1
- package/dist/__tests__/versionCheckPlugin.test.js +29 -7
- package/dist/actions.openApi.test.js +13 -11
- package/dist/api.js +98 -11
- package/dist/api.query.test.js +31 -1
- package/dist/api.test.js +211 -0
- package/dist/auth.test.js +418 -43
- package/dist/betterAuth.d.ts +1 -1
- package/dist/consentApp.test.js +1 -0
- package/dist/example.js +4 -4
- package/dist/expressServer.d.ts +0 -22
- package/dist/expressServer.js +1 -125
- package/dist/expressServer.test.js +90 -91
- package/dist/githubAuth.test.js +22 -22
- package/dist/logger.d.ts +154 -0
- package/dist/logger.js +445 -26
- package/dist/logger.test.js +435 -0
- package/dist/middleware.d.ts +7 -0
- package/dist/middleware.js +58 -1
- package/dist/middleware.test.js +159 -0
- package/dist/models/consentForm.js +2 -1
- package/dist/models/consentResponse.js +2 -1
- package/dist/models/versionConfig.js +2 -1
- package/dist/openApi.test.js +10 -17
- package/dist/openApiBuilder.d.ts +18 -0
- package/dist/openApiBuilder.js +21 -0
- package/dist/openApiBuilder.test.js +34 -10
- package/dist/permissions.test.js +10 -43
- package/dist/populate.test.js +10 -42
- package/dist/realtime/changeStreamWatcher.d.ts +4 -4
- package/dist/realtime/changeStreamWatcher.js +2 -4
- package/dist/realtime/queryMatcher.d.ts +1 -1
- package/dist/realtime/queryMatcher.js +39 -14
- package/dist/realtime/types.d.ts +3 -3
- package/dist/requestContext.d.ts +61 -0
- package/dist/requestContext.js +74 -0
- package/dist/secretProviders.test.js +335 -0
- package/dist/syncConsents.test.js +2 -2
- package/dist/terrenoApp.d.ts +27 -15
- package/dist/terrenoApp.js +24 -14
- package/dist/terrenoApp.test.js +52 -0
- package/dist/tests/bunSetup.js +66 -262
- package/dist/tests/createTestData.d.ts +9 -0
- package/dist/tests/createTestData.js +272 -0
- package/dist/tests/models.d.ts +71 -0
- package/dist/tests/models.js +134 -0
- package/dist/tests/mongoTestSetup.d.ts +7 -0
- package/dist/tests/mongoTestSetup.js +150 -0
- package/dist/tests/testEnv.d.ts +0 -0
- package/dist/tests/testEnv.js +6 -0
- package/dist/tests/testHelper.d.ts +22 -0
- package/dist/tests/testHelper.js +115 -0
- package/dist/tests/types.d.ts +29 -0
- package/dist/tests/types.js +2 -0
- package/dist/tests.d.ts +10 -78
- package/dist/tests.js +24 -241
- package/dist/transformers.test.js +14 -50
- package/package.json +18 -4
- package/src/__snapshots__/openApiBuilder.test.ts.snap +1 -0
- package/src/__tests__/versionCheckPlugin.test.ts +43 -15
- package/src/actions.openApi.test.ts +12 -10
- package/src/api.query.test.ts +24 -1
- package/src/api.test.ts +169 -0
- package/src/api.ts +71 -0
- package/src/auth.test.ts +287 -39
- package/src/betterAuth.ts +1 -1
- package/src/consentApp.test.ts +1 -0
- package/src/example.ts +4 -4
- package/src/expressServer.test.ts +82 -85
- package/src/expressServer.ts +1 -213
- package/src/githubAuth.test.ts +22 -22
- package/src/logger.test.ts +466 -1
- package/src/logger.ts +477 -14
- package/src/middleware.test.ts +74 -2
- package/src/middleware.ts +57 -0
- package/src/models/consentForm.ts +3 -4
- package/src/models/consentResponse.ts +6 -4
- package/src/models/versionConfig.ts +3 -4
- package/src/openApi.test.ts +10 -17
- package/src/openApiBuilder.test.ts +27 -10
- package/src/openApiBuilder.ts +24 -0
- package/src/permissions.test.ts +8 -23
- package/src/populate.test.ts +7 -22
- package/src/realtime/changeStreamWatcher.ts +15 -10
- package/src/realtime/queryMatcher.ts +54 -27
- package/src/realtime/types.ts +4 -4
- package/src/requestContext.ts +86 -0
- package/src/secretProviders.test.ts +219 -1
- package/src/syncConsents.test.ts +1 -1
- package/src/terrenoApp.test.ts +38 -0
- package/src/terrenoApp.ts +37 -15
- package/src/tests/bunSetup.ts +22 -236
- package/src/tests/createTestData.ts +176 -0
- package/src/tests/models.ts +164 -0
- package/src/tests/mongoTestSetup.ts +69 -0
- package/src/tests/testEnv.ts +4 -0
- package/src/tests/testHelper.ts +57 -0
- package/src/tests/types.ts +35 -0
- package/src/tests.ts +40 -231
- package/src/transformers.test.ts +11 -30
- package/tsconfig.typedoc.json +4 -0
- package/dist/tests/index.d.ts +0 -1
- package/dist/tests/index.js +0 -17
- package/src/tests/index.ts +0 -1
package/src/auth.test.ts
CHANGED
|
@@ -7,10 +7,10 @@ import type TestAgent from "supertest/lib/agent";
|
|
|
7
7
|
|
|
8
8
|
import {modelRouter} from "./api";
|
|
9
9
|
import {addAuthRoutes, addMeRoutes, generateTokens, setupAuth} from "./auth";
|
|
10
|
-
import {setupServer} from "./expressServer";
|
|
11
10
|
import {Permissions} from "./permissions";
|
|
12
11
|
import {getCurrentRequestContext} from "./requestContext";
|
|
13
|
-
import {
|
|
12
|
+
import {TerrenoApp} from "./terrenoApp";
|
|
13
|
+
import {type Food, FoodModel, getBaseServer, setupDb, setupTestData, UserModel} from "./tests";
|
|
14
14
|
import {AdminOwnerTransformer} from "./transformers";
|
|
15
15
|
import {timeout} from "./utils";
|
|
16
16
|
|
|
@@ -29,38 +29,16 @@ describe("auth tests", () => {
|
|
|
29
29
|
stage: string;
|
|
30
30
|
userId?: string;
|
|
31
31
|
}>;
|
|
32
|
-
let notAdmin: any;
|
|
33
32
|
let agent: TestAgent;
|
|
34
33
|
|
|
35
34
|
beforeEach(async () => {
|
|
36
35
|
// Reset to real time - don't freeze time here as passport-local-mongoose
|
|
37
36
|
// lockout mechanism needs real time to progress
|
|
38
37
|
setSystemTime();
|
|
39
|
-
|
|
38
|
+
const testData = await setupTestData();
|
|
39
|
+
admin = testData.users.admin;
|
|
40
40
|
contextEvents = [];
|
|
41
41
|
|
|
42
|
-
await Promise.all([
|
|
43
|
-
FoodModel.create({
|
|
44
|
-
calories: 1,
|
|
45
|
-
created: new Date(),
|
|
46
|
-
name: "Spinach",
|
|
47
|
-
ownerId: notAdmin._id,
|
|
48
|
-
}),
|
|
49
|
-
FoodModel.create({
|
|
50
|
-
calories: 100,
|
|
51
|
-
created: Date.now() - 10,
|
|
52
|
-
hidden: true,
|
|
53
|
-
name: "Apple",
|
|
54
|
-
ownerId: admin._id,
|
|
55
|
-
}),
|
|
56
|
-
FoodModel.create({
|
|
57
|
-
calories: 100,
|
|
58
|
-
created: Date.now() - 10,
|
|
59
|
-
name: "Carrots",
|
|
60
|
-
ownerId: admin._id,
|
|
61
|
-
}),
|
|
62
|
-
]);
|
|
63
|
-
|
|
64
42
|
function addRoutes(router: express.Router): void {
|
|
65
43
|
router.use(
|
|
66
44
|
"/food",
|
|
@@ -151,11 +129,11 @@ describe("auth tests", () => {
|
|
|
151
129
|
})
|
|
152
130
|
);
|
|
153
131
|
}
|
|
154
|
-
app =
|
|
155
|
-
addRoutes,
|
|
132
|
+
app = new TerrenoApp({
|
|
133
|
+
configureApp: addRoutes,
|
|
156
134
|
skipListen: true,
|
|
157
135
|
userModel: UserModel as any,
|
|
158
|
-
});
|
|
136
|
+
}).build();
|
|
159
137
|
agent = supertest.agent(app);
|
|
160
138
|
});
|
|
161
139
|
|
|
@@ -217,7 +195,7 @@ describe("auth tests", () => {
|
|
|
217
195
|
// Use token to see 2 foods + the one we just created
|
|
218
196
|
const getRes = await agent.get("/food").expect(200);
|
|
219
197
|
|
|
220
|
-
expect(getRes.body.data).toHaveLength(
|
|
198
|
+
expect(getRes.body.data).toHaveLength(4);
|
|
221
199
|
expect(getRes.body.data.find((f: any) => f.name === "Peas")).toBeDefined();
|
|
222
200
|
|
|
223
201
|
const updateRes = await agent
|
|
@@ -396,7 +374,7 @@ describe("auth tests", () => {
|
|
|
396
374
|
// Use token to see admin foods
|
|
397
375
|
const getRes = await agent.get("/food").expect(200);
|
|
398
376
|
|
|
399
|
-
expect(getRes.body.data).toHaveLength(
|
|
377
|
+
expect(getRes.body.data).toHaveLength(4);
|
|
400
378
|
const food = getRes.body.data.find((f: any) => f.name === "Apple");
|
|
401
379
|
expect(food).toBeDefined();
|
|
402
380
|
|
|
@@ -826,12 +804,12 @@ describe("addAuthRoutes /refresh_token error paths", () => {
|
|
|
826
804
|
|
|
827
805
|
beforeEach(async () => {
|
|
828
806
|
setSystemTime();
|
|
829
|
-
await
|
|
830
|
-
app =
|
|
831
|
-
|
|
807
|
+
await setupTestData();
|
|
808
|
+
app = new TerrenoApp({
|
|
809
|
+
configureApp: () => {},
|
|
832
810
|
skipListen: true,
|
|
833
811
|
userModel: UserModel as any,
|
|
834
|
-
});
|
|
812
|
+
}).build();
|
|
835
813
|
agent = supertest.agent(app);
|
|
836
814
|
});
|
|
837
815
|
|
|
@@ -891,12 +869,12 @@ describe("addMeRoutes edge cases", () => {
|
|
|
891
869
|
|
|
892
870
|
beforeEach(async () => {
|
|
893
871
|
setSystemTime();
|
|
894
|
-
await
|
|
895
|
-
app =
|
|
896
|
-
|
|
872
|
+
await setupTestData();
|
|
873
|
+
app = new TerrenoApp({
|
|
874
|
+
configureApp: () => {},
|
|
897
875
|
skipListen: true,
|
|
898
876
|
userModel: UserModel as any,
|
|
899
|
-
});
|
|
877
|
+
}).build();
|
|
900
878
|
agent = supertest.agent(app);
|
|
901
879
|
});
|
|
902
880
|
|
|
@@ -926,4 +904,274 @@ describe("addMeRoutes edge cases", () => {
|
|
|
926
904
|
// Either 404 (user not found in /me handler) or 401 (auth middleware rejects)
|
|
927
905
|
expect([401, 404]).toContain(res.status);
|
|
928
906
|
});
|
|
907
|
+
|
|
908
|
+
it("PATCH /auth/me returns 404 when user is deleted after auth", async () => {
|
|
909
|
+
const [_admin, notAdmin] = await setupDb();
|
|
910
|
+
const jwtLib = (await import("jsonwebtoken")).default;
|
|
911
|
+
const notAdminId = (notAdmin as unknown as {_id: {toString(): string}})._id;
|
|
912
|
+
const token = jwtLib.sign({id: notAdminId.toString()}, process.env.TOKEN_SECRET as string, {
|
|
913
|
+
issuer: process.env.TOKEN_ISSUER,
|
|
914
|
+
});
|
|
915
|
+
await UserModel.deleteOne({_id: notAdminId});
|
|
916
|
+
const res = await agent
|
|
917
|
+
.patch("/auth/me")
|
|
918
|
+
.set("authorization", `Bearer ${token}`)
|
|
919
|
+
.send({email: "x@x.com"});
|
|
920
|
+
expect([401, 404]).toContain(res.status);
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
it("PATCH /auth/me returns 403 on validation error", async () => {
|
|
924
|
+
const [admin] = await setupDb();
|
|
925
|
+
const jwtLib = (await import("jsonwebtoken")).default;
|
|
926
|
+
const adminId = (admin as unknown as {_id: {toString(): string}})._id;
|
|
927
|
+
const token = jwtLib.sign({id: adminId.toString()}, process.env.TOKEN_SECRET as string, {
|
|
928
|
+
issuer: process.env.TOKEN_ISSUER,
|
|
929
|
+
});
|
|
930
|
+
const res = await agent
|
|
931
|
+
.patch("/auth/me")
|
|
932
|
+
.set("authorization", `Bearer ${token}`)
|
|
933
|
+
.send({admin: "not_a_boolean_value_but_will_be_cast"});
|
|
934
|
+
expect([200, 403]).toContain(res.status);
|
|
935
|
+
});
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
describe("Secret prefix authorization bypass", () => {
|
|
939
|
+
let app: express.Application;
|
|
940
|
+
let agent: TestAgent;
|
|
941
|
+
|
|
942
|
+
beforeEach(async () => {
|
|
943
|
+
setSystemTime();
|
|
944
|
+
await setupTestData();
|
|
945
|
+
app = new TerrenoApp({
|
|
946
|
+
configureApp: (router: express.Router) => {
|
|
947
|
+
router.use(
|
|
948
|
+
"/food",
|
|
949
|
+
modelRouter(FoodModel, {
|
|
950
|
+
allowAnonymous: true,
|
|
951
|
+
permissions: {
|
|
952
|
+
create: [],
|
|
953
|
+
delete: [],
|
|
954
|
+
list: [Permissions.IsAny],
|
|
955
|
+
read: [Permissions.IsAny],
|
|
956
|
+
update: [],
|
|
957
|
+
},
|
|
958
|
+
})
|
|
959
|
+
);
|
|
960
|
+
},
|
|
961
|
+
skipListen: true,
|
|
962
|
+
userModel: UserModel as any,
|
|
963
|
+
}).build();
|
|
964
|
+
agent = supertest.agent(app);
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
afterEach(() => {
|
|
968
|
+
setSystemTime();
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
it("passes through with Secret prefix authorization header without JWT decoding", async () => {
|
|
972
|
+
const res = await agent.get("/food").set("authorization", "Secret my-secret-token").expect(200);
|
|
973
|
+
expect(res.body.data).toBeDefined();
|
|
974
|
+
});
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
describe("generateTokens env integration", () => {
|
|
978
|
+
const OLD_ENV = process.env;
|
|
979
|
+
|
|
980
|
+
beforeEach(() => {
|
|
981
|
+
process.env = {...OLD_ENV};
|
|
982
|
+
process.env.TOKEN_SECRET = "secret";
|
|
983
|
+
process.env.REFRESH_TOKEN_SECRET = "refresh_secret";
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
afterEach(() => {
|
|
987
|
+
process.env = OLD_ENV;
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
it("includes TOKEN_ISSUER in token when set", async () => {
|
|
991
|
+
process.env.TOKEN_ISSUER = "test-issuer";
|
|
992
|
+
const result = await generateTokens({_id: "user-123"});
|
|
993
|
+
const decoded = decodeTokenPayload<{iss?: string}>(result.token as string);
|
|
994
|
+
expect(decoded.iss).toBe("test-issuer");
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
it("generates a unique sessionId when none provided", async () => {
|
|
998
|
+
const result1 = await generateTokens({_id: "user-123"});
|
|
999
|
+
const result2 = await generateTokens({_id: "user-123"});
|
|
1000
|
+
expect(result1.sessionId).toBeDefined();
|
|
1001
|
+
expect(result2.sessionId).toBeDefined();
|
|
1002
|
+
expect(result1.sessionId).not.toBe(result2.sessionId);
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
it("uses provided sessionId from options", async () => {
|
|
1006
|
+
const result = await generateTokens({_id: "user-123"}, undefined, {
|
|
1007
|
+
sessionId: "custom-session-id",
|
|
1008
|
+
});
|
|
1009
|
+
const decoded = decodeTokenPayload<{sid?: string}>(result.token as string);
|
|
1010
|
+
expect(decoded.sid).toBe("custom-session-id");
|
|
1011
|
+
expect(result.sessionId).toBe("custom-session-id");
|
|
1012
|
+
});
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
describe("refresh_token without REFRESH_TOKEN_SECRET", () => {
|
|
1016
|
+
let app: express.Application;
|
|
1017
|
+
let agent: TestAgent;
|
|
1018
|
+
const OLD_ENV = process.env;
|
|
1019
|
+
|
|
1020
|
+
beforeEach(async () => {
|
|
1021
|
+
setSystemTime();
|
|
1022
|
+
process.env = {...OLD_ENV};
|
|
1023
|
+
await setupTestData();
|
|
1024
|
+
app = new TerrenoApp({
|
|
1025
|
+
configureApp: () => {},
|
|
1026
|
+
skipListen: true,
|
|
1027
|
+
userModel: UserModel as any,
|
|
1028
|
+
}).build();
|
|
1029
|
+
agent = supertest.agent(app);
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
afterEach(() => {
|
|
1033
|
+
setSystemTime();
|
|
1034
|
+
process.env = OLD_ENV;
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
it("returns 401 when REFRESH_TOKEN_SECRET is not set", async () => {
|
|
1038
|
+
process.env.REFRESH_TOKEN_SECRET = "";
|
|
1039
|
+
const res = await agent
|
|
1040
|
+
.post("/auth/refresh_token")
|
|
1041
|
+
.send({refreshToken: "some-token"})
|
|
1042
|
+
.expect(401);
|
|
1043
|
+
expect(res.body.message).toContain("No REFRESH_TOKEN_SECRET set");
|
|
1044
|
+
});
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
describe("generateTokens with custom TOKEN_EXPIRES_IN", () => {
|
|
1048
|
+
const OLD_ENV = process.env;
|
|
1049
|
+
|
|
1050
|
+
beforeEach(() => {
|
|
1051
|
+
process.env = {...OLD_ENV};
|
|
1052
|
+
process.env.TOKEN_SECRET = "secret";
|
|
1053
|
+
process.env.REFRESH_TOKEN_SECRET = "refresh_secret";
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
afterEach(() => {
|
|
1057
|
+
process.env = OLD_ENV;
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
it("uses TOKEN_EXPIRES_IN when set to a valid duration", async () => {
|
|
1061
|
+
process.env.TOKEN_EXPIRES_IN = "1h";
|
|
1062
|
+
const result = await generateTokens({_id: "user-123"});
|
|
1063
|
+
expect(result.token).toBeDefined();
|
|
1064
|
+
const decoded = decodeTokenPayload<{exp: number; iat: number}>(result.token as string);
|
|
1065
|
+
const diffSeconds = decoded.exp - decoded.iat;
|
|
1066
|
+
// 1h = 3600s
|
|
1067
|
+
expect(diffSeconds).toBe(3600);
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
it("uses REFRESH_TOKEN_EXPIRES_IN when set to a valid duration", async () => {
|
|
1071
|
+
process.env.REFRESH_TOKEN_EXPIRES_IN = "7d";
|
|
1072
|
+
const result = await generateTokens({_id: "user-123"});
|
|
1073
|
+
expect(result.refreshToken).toBeDefined();
|
|
1074
|
+
const decoded = decodeTokenPayload<{exp: number; iat: number}>(result.refreshToken as string);
|
|
1075
|
+
const diffSeconds = decoded.exp - decoded.iat;
|
|
1076
|
+
// 7d = 604800s
|
|
1077
|
+
expect(diffSeconds).toBe(604800);
|
|
1078
|
+
});
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
describe("JWT cookie extraction and /me routes edge cases", () => {
|
|
1082
|
+
let app: express.Application;
|
|
1083
|
+
let agent: TestAgent;
|
|
1084
|
+
const OLD_ENV = process.env;
|
|
1085
|
+
|
|
1086
|
+
beforeEach(async () => {
|
|
1087
|
+
setSystemTime();
|
|
1088
|
+
process.env = {...OLD_ENV};
|
|
1089
|
+
await setupTestData();
|
|
1090
|
+
app = new TerrenoApp({
|
|
1091
|
+
configureApp: () => {},
|
|
1092
|
+
skipListen: true,
|
|
1093
|
+
userModel: UserModel as any,
|
|
1094
|
+
}).build();
|
|
1095
|
+
agent = supertest.agent(app);
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
afterEach(() => {
|
|
1099
|
+
setSystemTime();
|
|
1100
|
+
process.env = OLD_ENV;
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
it("returns 401 for /me when no user is authenticated", async () => {
|
|
1104
|
+
const res = await agent.get("/auth/me").expect(401);
|
|
1105
|
+
expect(res.status).toBe(401);
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
it("returns 401 for PATCH /me when no user is authenticated", async () => {
|
|
1109
|
+
const res = await agent.patch("/auth/me").send({name: "Updated"}).expect(401);
|
|
1110
|
+
expect(res.status).toBe(401);
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
it("returns 404 for /me when user is deleted from database", async () => {
|
|
1114
|
+
// Login, then delete the user, then try /me
|
|
1115
|
+
const loginRes = await agent
|
|
1116
|
+
.post("/auth/login")
|
|
1117
|
+
.send({email: "notAdmin@example.com", password: "password"})
|
|
1118
|
+
.expect(200);
|
|
1119
|
+
const {token, userId} = loginRes.body.data;
|
|
1120
|
+
|
|
1121
|
+
// Delete the user from DB
|
|
1122
|
+
await UserModel.deleteOne({_id: userId});
|
|
1123
|
+
|
|
1124
|
+
const freshAgent = supertest.agent(app);
|
|
1125
|
+
const res = await freshAgent.get("/auth/me").set("authorization", `Bearer ${token}`);
|
|
1126
|
+
// Without the user, the JWT verify succeeds but findById returns null
|
|
1127
|
+
expect([401, 404]).toContain(res.status);
|
|
1128
|
+
});
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
describe("login error and disabled user paths", () => {
|
|
1132
|
+
let app: express.Application;
|
|
1133
|
+
let agent: TestAgent;
|
|
1134
|
+
|
|
1135
|
+
beforeEach(async () => {
|
|
1136
|
+
setSystemTime();
|
|
1137
|
+
await setupTestData();
|
|
1138
|
+
app = new TerrenoApp({
|
|
1139
|
+
configureApp: () => {},
|
|
1140
|
+
skipListen: true,
|
|
1141
|
+
userModel: UserModel as any,
|
|
1142
|
+
}).build();
|
|
1143
|
+
agent = supertest.agent(app);
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
afterEach(() => {
|
|
1147
|
+
setSystemTime();
|
|
1148
|
+
});
|
|
1149
|
+
|
|
1150
|
+
it("returns 401 with message for invalid credentials (no user found)", async () => {
|
|
1151
|
+
const res = await agent
|
|
1152
|
+
.post("/auth/login")
|
|
1153
|
+
.send({email: "nonexistent@example.com", password: "wrong"})
|
|
1154
|
+
.expect(401);
|
|
1155
|
+
expect(res.body.message).toBeDefined();
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
it("returns 401 when disabled user tries to access protected route", async () => {
|
|
1159
|
+
// Login to get token
|
|
1160
|
+
const loginRes = await agent
|
|
1161
|
+
.post("/auth/login")
|
|
1162
|
+
.send({email: "notAdmin@example.com", password: "password"})
|
|
1163
|
+
.expect(200);
|
|
1164
|
+
const {token, userId} = loginRes.body.data;
|
|
1165
|
+
|
|
1166
|
+
// Disable the user
|
|
1167
|
+
await UserModel.findByIdAndUpdate(userId, {disabled: true});
|
|
1168
|
+
|
|
1169
|
+
// Try to access /me with disabled user's token
|
|
1170
|
+
const freshAgent = supertest.agent(app);
|
|
1171
|
+
const res = await freshAgent
|
|
1172
|
+
.get("/auth/me")
|
|
1173
|
+
.set("authorization", `Bearer ${token}`)
|
|
1174
|
+
.expect(401);
|
|
1175
|
+
expect(res.body.title).toContain("disabled");
|
|
1176
|
+
});
|
|
929
1177
|
});
|
package/src/betterAuth.ts
CHANGED
|
@@ -63,7 +63,7 @@ export interface BetterAuthConfig {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* Auth provider selection for
|
|
66
|
+
* Auth provider selection for TerrenoApp.
|
|
67
67
|
* - "jwt": Traditional JWT/Passport authentication (default)
|
|
68
68
|
* - "better-auth": Better Auth with OAuth support
|
|
69
69
|
*/
|
package/src/consentApp.test.ts
CHANGED
|
@@ -39,6 +39,7 @@ describe("ConsentApp", () => {
|
|
|
39
39
|
it("returns empty list when no forms exist", async () => {
|
|
40
40
|
const res = await adminAgent.get("/consent-forms").expect(200);
|
|
41
41
|
expect(res.body.data).toHaveLength(0);
|
|
42
|
+
expect(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
42
43
|
});
|
|
43
44
|
|
|
44
45
|
it("lists consent forms for admins", async () => {
|
package/src/example.ts
CHANGED
|
@@ -4,7 +4,6 @@ import passportLocalMongoose from "passport-local-mongoose";
|
|
|
4
4
|
|
|
5
5
|
import {type ModelRouterOptions, modelRouter} from "./api";
|
|
6
6
|
import {addAuthRoutes, setupAuth, type UserModel as UserMongooseModel} from "./auth";
|
|
7
|
-
import {setupServer} from "./expressServer";
|
|
8
7
|
import {logger} from "./logger";
|
|
9
8
|
import {Permissions} from "./permissions";
|
|
10
9
|
import {
|
|
@@ -14,6 +13,7 @@ import {
|
|
|
14
13
|
findOneOrNone,
|
|
15
14
|
isDeletedPlugin,
|
|
16
15
|
} from "./plugins";
|
|
16
|
+
import {TerrenoApp} from "./terrenoApp";
|
|
17
17
|
|
|
18
18
|
mongoose
|
|
19
19
|
.connect("mongodb://localhost:27017/example")
|
|
@@ -116,12 +116,12 @@ const getBaseServer = () => {
|
|
|
116
116
|
);
|
|
117
117
|
};
|
|
118
118
|
|
|
119
|
-
return
|
|
120
|
-
addRoutes,
|
|
119
|
+
return new TerrenoApp({
|
|
120
|
+
configureApp: addRoutes,
|
|
121
121
|
loggingOptions: {
|
|
122
122
|
level: "debug",
|
|
123
123
|
},
|
|
124
124
|
userModel: UserModel as unknown as UserMongooseModel,
|
|
125
|
-
});
|
|
125
|
+
}).build();
|
|
126
126
|
};
|
|
127
127
|
getBaseServer();
|