@terreno/api 0.12.2 → 0.13.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/dist/configurationPlugin.js +7 -1
- package/dist/consentApp.d.ts +2 -2
- package/dist/consentApp.js +2 -1
- package/dist/githubAuth.test.js +409 -0
- package/dist/models/consentForm.js +9 -1
- package/dist/notifiers/slackNotifier.d.ts +2 -2
- package/dist/notifiers/slackNotifier.js +38 -7
- package/dist/openApiEtag.js +0 -7
- package/dist/plugins.d.ts +3 -3
- package/dist/plugins.js +8 -4
- package/dist/populate.test.js +5 -1
- package/dist/scriptRunner.js +2 -2
- package/dist/secretProviders.js +4 -1
- package/dist/tests.js +1 -0
- package/dist/utils.js +13 -3
- package/package.json +1 -1
- package/src/configurationPlugin.ts +7 -1
- package/src/consentApp.ts +3 -3
- package/src/githubAuth.test.ts +327 -0
- package/src/models/consentForm.ts +10 -1
- package/src/notifiers/slackNotifier.ts +7 -6
- package/src/openApiEtag.ts +0 -7
- package/src/plugins.ts +13 -8
- package/src/populate.test.ts +45 -20
- package/src/scriptRunner.ts +2 -2
- package/src/secretProviders.ts +4 -3
- package/src/tests.ts +18 -14
- package/src/utils.ts +13 -3
|
@@ -84,7 +84,13 @@ var configurationPlugin = function (schema, options) {
|
|
|
84
84
|
// Add a sentinel field with a unique index to enforce singleton at the DB level.
|
|
85
85
|
// All config documents get _singleton: "config", and the unique index prevents duplicates.
|
|
86
86
|
schema.add({
|
|
87
|
-
_singleton: {
|
|
87
|
+
_singleton: {
|
|
88
|
+
default: "config",
|
|
89
|
+
description: "Sentinel field enforcing singleton constraint",
|
|
90
|
+
immutable: true,
|
|
91
|
+
select: false,
|
|
92
|
+
type: String,
|
|
93
|
+
},
|
|
88
94
|
});
|
|
89
95
|
schema.index({ _singleton: 1 }, { unique: true });
|
|
90
96
|
// Enforce singleton: only one document allowed (application-level guard)
|
package/dist/consentApp.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Provides admin CRUD for consent forms, read-only access to responses, and user-facing
|
|
6
6
|
* endpoints for fetching pending consents and submitting responses.
|
|
7
7
|
*/
|
|
8
|
-
import type
|
|
8
|
+
import { type Application } from "express";
|
|
9
9
|
import type { User } from "./auth";
|
|
10
10
|
import type { TerrenoPlugin } from "./terrenoPlugin";
|
|
11
11
|
import type { ConsentFormDocument } from "./types/consentForm";
|
|
@@ -29,5 +29,5 @@ export interface ConsentAppOptions {
|
|
|
29
29
|
export declare class ConsentApp implements TerrenoPlugin {
|
|
30
30
|
private options;
|
|
31
31
|
constructor(options?: ConsentAppOptions);
|
|
32
|
-
register(app:
|
|
32
|
+
register(app: Application): void;
|
|
33
33
|
}
|
package/dist/consentApp.js
CHANGED
|
@@ -55,6 +55,7 @@ var __values = (this && this.__values) || function(o) {
|
|
|
55
55
|
};
|
|
56
56
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
57
|
exports.ConsentApp = void 0;
|
|
58
|
+
var express_1 = require("express");
|
|
58
59
|
var luxon_1 = require("luxon");
|
|
59
60
|
var api_1 = require("./api");
|
|
60
61
|
var auth_1 = require("./auth");
|
|
@@ -203,7 +204,7 @@ var ConsentApp = /** @class */ (function () {
|
|
|
203
204
|
],
|
|
204
205
|
}));
|
|
205
206
|
// User-facing consent endpoints
|
|
206
|
-
var router =
|
|
207
|
+
var router = (0, express_1.Router)();
|
|
207
208
|
// GET /consents/pending - fetch pending consent forms for the current user
|
|
208
209
|
router.get("/pending", (0, auth_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
209
210
|
var user, activeForms, resolvedForms, existingResponses, respondedFormVersions, existingResponses_1, existingResponses_1_1, response, formId, respondedFormIds, respondedForms, formVersionByFormId, respondedForms_1, respondedForms_1_1, form, pendingForms, filteredOutByResolverCount, filteredOutByResponsesCount;
|
package/dist/githubAuth.test.js
CHANGED
|
@@ -77,10 +77,30 @@ var mongoose_1 = __importStar(require("mongoose"));
|
|
|
77
77
|
var passport_1 = __importDefault(require("passport"));
|
|
78
78
|
var passport_local_mongoose_1 = __importDefault(require("passport-local-mongoose"));
|
|
79
79
|
var supertest_1 = __importDefault(require("supertest"));
|
|
80
|
+
var auth_1 = require("./auth");
|
|
80
81
|
var expressServer_1 = require("./expressServer");
|
|
81
82
|
var githubAuth_1 = require("./githubAuth");
|
|
82
83
|
var logger_1 = require("./logger");
|
|
83
84
|
var plugins_1 = require("./plugins");
|
|
85
|
+
var fakeGithubOutcome = { type: "redirect", url: "http://github.com/mock" };
|
|
86
|
+
var installFakeGithubStrategy = function () {
|
|
87
|
+
var strategy = {
|
|
88
|
+
authenticate: function () {
|
|
89
|
+
var _a, _b;
|
|
90
|
+
if (fakeGithubOutcome.type === "success") {
|
|
91
|
+
this.success(fakeGithubOutcome.user);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (fakeGithubOutcome.type === "fail") {
|
|
95
|
+
this.fail((_a = fakeGithubOutcome.challenge) !== null && _a !== void 0 ? _a : { message: "auth failed" });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.redirect((_b = fakeGithubOutcome.url) !== null && _b !== void 0 ? _b : "http://github.com/mock");
|
|
99
|
+
},
|
|
100
|
+
name: "github",
|
|
101
|
+
};
|
|
102
|
+
passport_1.default.use("github", strategy);
|
|
103
|
+
};
|
|
84
104
|
// Create schema for GitHub-enabled user
|
|
85
105
|
var testUserSchema = new mongoose_1.Schema({
|
|
86
106
|
admin: { default: false, description: "Whether the user has admin privileges", type: Boolean },
|
|
@@ -729,3 +749,392 @@ var invokeGitHubVerify = function (req, accessToken, refreshToken, profile) {
|
|
|
729
749
|
});
|
|
730
750
|
}); });
|
|
731
751
|
});
|
|
752
|
+
(0, bun_test_1.describe)("GitHub callback handler (fake strategy)", function () {
|
|
753
|
+
var app;
|
|
754
|
+
var agent;
|
|
755
|
+
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
756
|
+
function addRoutes(router) {
|
|
757
|
+
router.get("/test", function (_req, res) { return res.json({ ok: true }); });
|
|
758
|
+
}
|
|
759
|
+
return __generator(this, function (_a) {
|
|
760
|
+
switch (_a.label) {
|
|
761
|
+
case 0:
|
|
762
|
+
(0, bun_test_1.setSystemTime)();
|
|
763
|
+
return [4 /*yield*/, connectDb()];
|
|
764
|
+
case 1:
|
|
765
|
+
_a.sent();
|
|
766
|
+
return [4 /*yield*/, GitHubTestUserModel.deleteMany({})];
|
|
767
|
+
case 2:
|
|
768
|
+
_a.sent();
|
|
769
|
+
app = (0, expressServer_1.setupServer)({
|
|
770
|
+
addMiddleware: function (a) {
|
|
771
|
+
// The handler reads (req as unknown as {session?: {returnTo?: string}}).session?.returnTo.
|
|
772
|
+
// setupServer does not install express-session, so prime a fake session from a request
|
|
773
|
+
// header for tests.
|
|
774
|
+
a.use(function (req, _res, next) {
|
|
775
|
+
var headerReturnTo = req.headers["x-mock-return-to"];
|
|
776
|
+
if (typeof headerReturnTo === "string") {
|
|
777
|
+
req.session = { returnTo: headerReturnTo };
|
|
778
|
+
}
|
|
779
|
+
next();
|
|
780
|
+
});
|
|
781
|
+
},
|
|
782
|
+
addRoutes: addRoutes,
|
|
783
|
+
githubAuth: {
|
|
784
|
+
allowAccountLinking: true,
|
|
785
|
+
callbackURL: "http://localhost:9000/auth/github/callback",
|
|
786
|
+
clientId: "test-client-id",
|
|
787
|
+
clientSecret: "test-client-secret",
|
|
788
|
+
},
|
|
789
|
+
skipListen: true,
|
|
790
|
+
userModel: GitHubTestUserModel,
|
|
791
|
+
});
|
|
792
|
+
// Swap the github strategy with our fake after setupServer registered it.
|
|
793
|
+
installFakeGithubStrategy();
|
|
794
|
+
agent = supertest_1.default.agent(app);
|
|
795
|
+
return [2 /*return*/];
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
}); });
|
|
799
|
+
(0, bun_test_1.afterEach)(function () {
|
|
800
|
+
(0, bun_test_1.setSystemTime)();
|
|
801
|
+
fakeGithubOutcome = { type: "redirect", url: "http://github.com/mock" };
|
|
802
|
+
});
|
|
803
|
+
(0, bun_test_1.it)("GET /auth/github/callback returns JSON tokens on success", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
804
|
+
var user, res;
|
|
805
|
+
return __generator(this, function (_a) {
|
|
806
|
+
switch (_a.label) {
|
|
807
|
+
case 0: return [4 /*yield*/, GitHubTestUserModel.create({
|
|
808
|
+
email: "cb@example.com",
|
|
809
|
+
githubId: "cb-gh-1",
|
|
810
|
+
name: "CB User",
|
|
811
|
+
})];
|
|
812
|
+
case 1:
|
|
813
|
+
user = _a.sent();
|
|
814
|
+
fakeGithubOutcome = { type: "success", user: user };
|
|
815
|
+
return [4 /*yield*/, agent.get("/auth/github/callback").expect(200)];
|
|
816
|
+
case 2:
|
|
817
|
+
res = _a.sent();
|
|
818
|
+
(0, bun_test_1.expect)(res.body.data.token).toBeDefined();
|
|
819
|
+
(0, bun_test_1.expect)(res.body.data.refreshToken).toBeDefined();
|
|
820
|
+
(0, bun_test_1.expect)(res.body.data.userId).toBeDefined();
|
|
821
|
+
return [2 /*return*/];
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
}); });
|
|
825
|
+
(0, bun_test_1.it)("GET /auth/github/callback redirects to returnTo with tokens when session.returnTo is set", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
826
|
+
var user, res;
|
|
827
|
+
return __generator(this, function (_a) {
|
|
828
|
+
switch (_a.label) {
|
|
829
|
+
case 0: return [4 /*yield*/, GitHubTestUserModel.create({
|
|
830
|
+
email: "cb2@example.com",
|
|
831
|
+
githubId: "cb-gh-2",
|
|
832
|
+
name: "CB User 2",
|
|
833
|
+
})];
|
|
834
|
+
case 1:
|
|
835
|
+
user = _a.sent();
|
|
836
|
+
fakeGithubOutcome = { type: "success", user: user };
|
|
837
|
+
return [4 /*yield*/, agent
|
|
838
|
+
.get("/auth/github/callback")
|
|
839
|
+
.set("x-mock-return-to", "https://example.com/cb")
|
|
840
|
+
.expect(302)];
|
|
841
|
+
case 2:
|
|
842
|
+
res = _a.sent();
|
|
843
|
+
(0, bun_test_1.expect)(res.headers.location).toContain("https://example.com/cb");
|
|
844
|
+
(0, bun_test_1.expect)(res.headers.location).toContain("token=");
|
|
845
|
+
(0, bun_test_1.expect)(res.headers.location).toContain("refreshToken=");
|
|
846
|
+
(0, bun_test_1.expect)(res.headers.location).toContain("userId=");
|
|
847
|
+
return [2 /*return*/];
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
}); });
|
|
851
|
+
(0, bun_test_1.it)("GET /auth/github/callback redirects on failure", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
852
|
+
var res;
|
|
853
|
+
return __generator(this, function (_a) {
|
|
854
|
+
switch (_a.label) {
|
|
855
|
+
case 0:
|
|
856
|
+
fakeGithubOutcome = { challenge: { message: "denied" }, type: "fail" };
|
|
857
|
+
return [4 /*yield*/, agent.get("/auth/github/callback").expect(302)];
|
|
858
|
+
case 1:
|
|
859
|
+
res = _a.sent();
|
|
860
|
+
(0, bun_test_1.expect)(res.headers.location).toContain("/auth/github/failure");
|
|
861
|
+
return [2 /*return*/];
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
}); });
|
|
865
|
+
(0, bun_test_1.it)("GET /auth/github/callback returns 500 when token generation fails", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
866
|
+
var user, savedSecret, res;
|
|
867
|
+
return __generator(this, function (_a) {
|
|
868
|
+
switch (_a.label) {
|
|
869
|
+
case 0: return [4 /*yield*/, GitHubTestUserModel.create({
|
|
870
|
+
email: "cb3@example.com",
|
|
871
|
+
githubId: "cb-gh-3",
|
|
872
|
+
name: "CB User 3",
|
|
873
|
+
})];
|
|
874
|
+
case 1:
|
|
875
|
+
user = _a.sent();
|
|
876
|
+
fakeGithubOutcome = { type: "success", user: user };
|
|
877
|
+
savedSecret = process.env.TOKEN_SECRET;
|
|
878
|
+
process.env.TOKEN_SECRET = "";
|
|
879
|
+
_a.label = 2;
|
|
880
|
+
case 2:
|
|
881
|
+
_a.trys.push([2, , 4, 5]);
|
|
882
|
+
return [4 /*yield*/, agent.get("/auth/github/callback").expect(500)];
|
|
883
|
+
case 3:
|
|
884
|
+
res = _a.sent();
|
|
885
|
+
(0, bun_test_1.expect)(res.body.message).toBe("Authentication failed");
|
|
886
|
+
return [3 /*break*/, 5];
|
|
887
|
+
case 4:
|
|
888
|
+
process.env.TOKEN_SECRET = savedSecret;
|
|
889
|
+
return [7 /*endfinally*/];
|
|
890
|
+
case 5: return [2 /*return*/];
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
}); });
|
|
894
|
+
});
|
|
895
|
+
(0, bun_test_1.describe)("GET /auth/github/link with JWT (fake strategy)", function () {
|
|
896
|
+
var app;
|
|
897
|
+
var agent;
|
|
898
|
+
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
899
|
+
function addRoutes(router) {
|
|
900
|
+
router.get("/test", function (_req, res) { return res.json({ ok: true }); });
|
|
901
|
+
}
|
|
902
|
+
return __generator(this, function (_a) {
|
|
903
|
+
switch (_a.label) {
|
|
904
|
+
case 0:
|
|
905
|
+
(0, bun_test_1.setSystemTime)();
|
|
906
|
+
return [4 /*yield*/, connectDb()];
|
|
907
|
+
case 1:
|
|
908
|
+
_a.sent();
|
|
909
|
+
return [4 /*yield*/, GitHubTestUserModel.deleteMany({})];
|
|
910
|
+
case 2:
|
|
911
|
+
_a.sent();
|
|
912
|
+
app = (0, expressServer_1.setupServer)({
|
|
913
|
+
addRoutes: addRoutes,
|
|
914
|
+
githubAuth: {
|
|
915
|
+
allowAccountLinking: true,
|
|
916
|
+
callbackURL: "http://localhost:9000/auth/github/callback",
|
|
917
|
+
clientId: "test-client-id",
|
|
918
|
+
clientSecret: "test-client-secret",
|
|
919
|
+
},
|
|
920
|
+
skipListen: true,
|
|
921
|
+
userModel: GitHubTestUserModel,
|
|
922
|
+
});
|
|
923
|
+
installFakeGithubStrategy();
|
|
924
|
+
agent = supertest_1.default.agent(app);
|
|
925
|
+
return [2 /*return*/];
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
}); });
|
|
929
|
+
(0, bun_test_1.afterEach)(function () {
|
|
930
|
+
(0, bun_test_1.setSystemTime)();
|
|
931
|
+
fakeGithubOutcome = { type: "redirect", url: "http://github.com/mock" };
|
|
932
|
+
});
|
|
933
|
+
(0, bun_test_1.it)("GET /auth/github/link forwards to GitHub auth when JWT is valid", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
934
|
+
var user, loginRes, res;
|
|
935
|
+
return __generator(this, function (_a) {
|
|
936
|
+
switch (_a.label) {
|
|
937
|
+
case 0: return [4 /*yield*/, GitHubTestUserModel.create({
|
|
938
|
+
email: "linkjwt@example.com",
|
|
939
|
+
name: "Link JWT User",
|
|
940
|
+
})];
|
|
941
|
+
case 1:
|
|
942
|
+
user = _a.sent();
|
|
943
|
+
return [4 /*yield*/, user.setPassword("password123")];
|
|
944
|
+
case 2:
|
|
945
|
+
_a.sent();
|
|
946
|
+
return [4 /*yield*/, user.save()];
|
|
947
|
+
case 3:
|
|
948
|
+
_a.sent();
|
|
949
|
+
return [4 /*yield*/, agent
|
|
950
|
+
.post("/auth/login")
|
|
951
|
+
.send({ email: "linkjwt@example.com", password: "password123" })
|
|
952
|
+
.expect(200)];
|
|
953
|
+
case 4:
|
|
954
|
+
loginRes = _a.sent();
|
|
955
|
+
fakeGithubOutcome = { type: "redirect", url: "http://github.com/auth" };
|
|
956
|
+
return [4 /*yield*/, agent
|
|
957
|
+
.get("/auth/github/link")
|
|
958
|
+
.set("authorization", "Bearer ".concat(loginRes.body.data.token))
|
|
959
|
+
.expect(302)];
|
|
960
|
+
case 5:
|
|
961
|
+
res = _a.sent();
|
|
962
|
+
(0, bun_test_1.expect)(res.headers.location).toBe("http://github.com/auth");
|
|
963
|
+
return [2 /*return*/];
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
}); });
|
|
967
|
+
});
|
|
968
|
+
(0, bun_test_1.describe)("DELETE /auth/github/unlink edge cases", function () {
|
|
969
|
+
var app;
|
|
970
|
+
var agent;
|
|
971
|
+
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
972
|
+
function addRoutes(router) {
|
|
973
|
+
router.get("/test", function (_req, res) { return res.json({ ok: true }); });
|
|
974
|
+
}
|
|
975
|
+
return __generator(this, function (_a) {
|
|
976
|
+
switch (_a.label) {
|
|
977
|
+
case 0:
|
|
978
|
+
(0, bun_test_1.setSystemTime)();
|
|
979
|
+
return [4 /*yield*/, connectDb()];
|
|
980
|
+
case 1:
|
|
981
|
+
_a.sent();
|
|
982
|
+
return [4 /*yield*/, GitHubTestUserModel.deleteMany({})];
|
|
983
|
+
case 2:
|
|
984
|
+
_a.sent();
|
|
985
|
+
app = (0, expressServer_1.setupServer)({
|
|
986
|
+
addRoutes: addRoutes,
|
|
987
|
+
githubAuth: {
|
|
988
|
+
allowAccountLinking: true,
|
|
989
|
+
callbackURL: "http://localhost:9000/auth/github/callback",
|
|
990
|
+
clientId: "test-client-id",
|
|
991
|
+
clientSecret: "test-client-secret",
|
|
992
|
+
},
|
|
993
|
+
skipListen: true,
|
|
994
|
+
userModel: GitHubTestUserModel,
|
|
995
|
+
});
|
|
996
|
+
installFakeGithubStrategy();
|
|
997
|
+
agent = supertest_1.default.agent(app);
|
|
998
|
+
return [2 /*return*/];
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
}); });
|
|
1002
|
+
(0, bun_test_1.afterEach)(function () {
|
|
1003
|
+
(0, bun_test_1.setSystemTime)();
|
|
1004
|
+
});
|
|
1005
|
+
(0, bun_test_1.it)("returns 400 when user has no password (no other auth method)", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1006
|
+
var user, token, res;
|
|
1007
|
+
return __generator(this, function (_a) {
|
|
1008
|
+
switch (_a.label) {
|
|
1009
|
+
case 0: return [4 /*yield*/, GitHubTestUserModel.create({
|
|
1010
|
+
email: "ghonly@example.com",
|
|
1011
|
+
githubId: "ghonly-1",
|
|
1012
|
+
githubUsername: "ghonly",
|
|
1013
|
+
})];
|
|
1014
|
+
case 1:
|
|
1015
|
+
user = _a.sent();
|
|
1016
|
+
return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: user._id })];
|
|
1017
|
+
case 2:
|
|
1018
|
+
token = (_a.sent()).token;
|
|
1019
|
+
return [4 /*yield*/, agent
|
|
1020
|
+
.delete("/auth/github/unlink")
|
|
1021
|
+
.set("authorization", "Bearer ".concat(token))
|
|
1022
|
+
.expect(400)];
|
|
1023
|
+
case 3:
|
|
1024
|
+
res = _a.sent();
|
|
1025
|
+
(0, bun_test_1.expect)(res.body.message).toContain("Cannot unlink GitHub account");
|
|
1026
|
+
return [2 /*return*/];
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
}); });
|
|
1030
|
+
(0, bun_test_1.it)("returns 500 when save throws during unlink", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1031
|
+
var user, loginRes, mockableModel, originalFindById, res;
|
|
1032
|
+
return __generator(this, function (_a) {
|
|
1033
|
+
switch (_a.label) {
|
|
1034
|
+
case 0: return [4 /*yield*/, GitHubTestUserModel.create({
|
|
1035
|
+
email: "savefail@example.com",
|
|
1036
|
+
githubId: "savefail-1",
|
|
1037
|
+
})];
|
|
1038
|
+
case 1:
|
|
1039
|
+
user = _a.sent();
|
|
1040
|
+
return [4 /*yield*/, user.setPassword("password123")];
|
|
1041
|
+
case 2:
|
|
1042
|
+
_a.sent();
|
|
1043
|
+
return [4 /*yield*/, user.save()];
|
|
1044
|
+
case 3:
|
|
1045
|
+
_a.sent();
|
|
1046
|
+
return [4 /*yield*/, agent
|
|
1047
|
+
.post("/auth/login")
|
|
1048
|
+
.send({ email: "savefail@example.com", password: "password123" })
|
|
1049
|
+
.expect(200)];
|
|
1050
|
+
case 4:
|
|
1051
|
+
loginRes = _a.sent();
|
|
1052
|
+
mockableModel = GitHubTestUserModel;
|
|
1053
|
+
originalFindById = mockableModel.findById;
|
|
1054
|
+
mockableModel.findById = function () { return ({
|
|
1055
|
+
select: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1056
|
+
return __generator(this, function (_a) {
|
|
1057
|
+
return [2 /*return*/, ({
|
|
1058
|
+
hash: "x",
|
|
1059
|
+
salt: "y",
|
|
1060
|
+
save: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1061
|
+
return __generator(this, function (_a) {
|
|
1062
|
+
throw new Error("boom");
|
|
1063
|
+
});
|
|
1064
|
+
}); },
|
|
1065
|
+
setPassword: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
1066
|
+
return [2 /*return*/, undefined];
|
|
1067
|
+
}); }); },
|
|
1068
|
+
})];
|
|
1069
|
+
});
|
|
1070
|
+
}); },
|
|
1071
|
+
}); };
|
|
1072
|
+
_a.label = 5;
|
|
1073
|
+
case 5:
|
|
1074
|
+
_a.trys.push([5, , 7, 8]);
|
|
1075
|
+
return [4 /*yield*/, agent
|
|
1076
|
+
.delete("/auth/github/unlink")
|
|
1077
|
+
.set("authorization", "Bearer ".concat(loginRes.body.data.token))
|
|
1078
|
+
.expect(500)];
|
|
1079
|
+
case 6:
|
|
1080
|
+
res = _a.sent();
|
|
1081
|
+
(0, bun_test_1.expect)(res.body.message).toBe("Failed to unlink GitHub account");
|
|
1082
|
+
return [3 /*break*/, 8];
|
|
1083
|
+
case 7:
|
|
1084
|
+
mockableModel.findById = originalFindById;
|
|
1085
|
+
return [7 /*endfinally*/];
|
|
1086
|
+
case 8: return [2 /*return*/];
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
}); });
|
|
1090
|
+
});
|
|
1091
|
+
(0, bun_test_1.describe)("GitHub strategy verify callback edge cases", function () {
|
|
1092
|
+
var testApp = { get: function () { }, use: function () { } };
|
|
1093
|
+
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1094
|
+
return __generator(this, function (_a) {
|
|
1095
|
+
switch (_a.label) {
|
|
1096
|
+
case 0: return [4 /*yield*/, connectDb()];
|
|
1097
|
+
case 1:
|
|
1098
|
+
_a.sent();
|
|
1099
|
+
return [4 /*yield*/, GitHubTestUserModel.deleteMany({})];
|
|
1100
|
+
case 2:
|
|
1101
|
+
_a.sent();
|
|
1102
|
+
return [2 /*return*/];
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
}); });
|
|
1106
|
+
(0, bun_test_1.it)("returns 404 when linking a user whose record disappears", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1107
|
+
var existingUser, req, result;
|
|
1108
|
+
var _a;
|
|
1109
|
+
return __generator(this, function (_b) {
|
|
1110
|
+
switch (_b.label) {
|
|
1111
|
+
case 0: return [4 /*yield*/, GitHubTestUserModel.create({
|
|
1112
|
+
email: "gone@example.com",
|
|
1113
|
+
})];
|
|
1114
|
+
case 1:
|
|
1115
|
+
existingUser = _b.sent();
|
|
1116
|
+
(0, githubAuth_1.setupGitHubAuth)(testApp, GitHubTestUserModel, {
|
|
1117
|
+
allowAccountLinking: true,
|
|
1118
|
+
callbackURL: "http://localhost:9000/auth/github/callback",
|
|
1119
|
+
clientId: "id",
|
|
1120
|
+
clientSecret: "secret",
|
|
1121
|
+
});
|
|
1122
|
+
// Delete user before verify runs to hit the 404 path.
|
|
1123
|
+
return [4 /*yield*/, GitHubTestUserModel.deleteOne({ _id: existingUser._id })];
|
|
1124
|
+
case 2:
|
|
1125
|
+
// Delete user before verify runs to hit the 404 path.
|
|
1126
|
+
_b.sent();
|
|
1127
|
+
req = { user: existingUser };
|
|
1128
|
+
return [4 /*yield*/, invokeGitHubVerify(req, "access", "refresh", {
|
|
1129
|
+
id: "gh-missing-1",
|
|
1130
|
+
username: "missing",
|
|
1131
|
+
})];
|
|
1132
|
+
case 3:
|
|
1133
|
+
result = _b.sent();
|
|
1134
|
+
(0, bun_test_1.expect)(result.err).toBeDefined();
|
|
1135
|
+
(0, bun_test_1.expect)((_a = result.err) === null || _a === void 0 ? void 0 : _a.status).toBe(404);
|
|
1136
|
+
return [2 /*return*/];
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
}); });
|
|
1140
|
+
});
|
|
@@ -6,6 +6,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.ConsentForm = void 0;
|
|
7
7
|
var mongoose_1 = __importDefault(require("mongoose"));
|
|
8
8
|
var plugins_1 = require("../plugins");
|
|
9
|
+
var consentFormTypeValues = [
|
|
10
|
+
"agreement",
|
|
11
|
+
"privacy",
|
|
12
|
+
"hipaa",
|
|
13
|
+
"research",
|
|
14
|
+
"terms",
|
|
15
|
+
"custom",
|
|
16
|
+
];
|
|
9
17
|
var consentFormSchema = new mongoose_1.default.Schema({
|
|
10
18
|
active: {
|
|
11
19
|
default: false,
|
|
@@ -96,7 +104,7 @@ var consentFormSchema = new mongoose_1.default.Schema({
|
|
|
96
104
|
},
|
|
97
105
|
type: {
|
|
98
106
|
description: "Category of consent form",
|
|
99
|
-
enum:
|
|
107
|
+
enum: consentFormTypeValues,
|
|
100
108
|
required: true,
|
|
101
109
|
type: String,
|
|
102
110
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export declare
|
|
1
|
+
export declare const sendToSlack: (text: string, { slackChannel, shouldThrow, env, url, }?: {
|
|
2
2
|
slackChannel?: string;
|
|
3
3
|
shouldThrow?: boolean;
|
|
4
4
|
env?: string;
|
|
5
5
|
url?: string;
|
|
6
|
-
})
|
|
6
|
+
}) => Promise<void>;
|
|
@@ -68,11 +68,36 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
68
68
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
72
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
73
|
+
if (!m) return o;
|
|
74
|
+
var i = m.call(o), r, ar = [], e;
|
|
75
|
+
try {
|
|
76
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
77
|
+
}
|
|
78
|
+
catch (error) { e = { error: error }; }
|
|
79
|
+
finally {
|
|
80
|
+
try {
|
|
81
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
82
|
+
}
|
|
83
|
+
finally { if (e) throw e.error; }
|
|
84
|
+
}
|
|
85
|
+
return ar;
|
|
86
|
+
};
|
|
87
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
88
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
89
|
+
if (ar || !(i in from)) {
|
|
90
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
91
|
+
ar[i] = from[i];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
95
|
+
};
|
|
71
96
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
72
97
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
73
98
|
};
|
|
74
99
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
75
|
-
exports.sendToSlack =
|
|
100
|
+
exports.sendToSlack = void 0;
|
|
76
101
|
var Sentry = __importStar(require("@sentry/bun"));
|
|
77
102
|
var axios_1 = __importDefault(require("axios"));
|
|
78
103
|
var errors_1 = require("../errors");
|
|
@@ -81,9 +106,13 @@ var logger_1 = require("../logger");
|
|
|
81
106
|
// If `url` is provided, it will be used directly instead of looking up from environment.
|
|
82
107
|
// DEPRECATED: Looking up webhook URLs from the SLACK_WEBHOOKS environment variable by channel name
|
|
83
108
|
// is deprecated and will be removed in a future version. Please pass the `url` parameter directly.
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
|
|
109
|
+
var sendToSlack = function (text_1) {
|
|
110
|
+
var args_1 = [];
|
|
111
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
112
|
+
args_1[_i - 1] = arguments[_i];
|
|
113
|
+
}
|
|
114
|
+
return __awaiter(void 0, __spreadArray([text_1], __read(args_1), false), void 0, function (text, _a) {
|
|
115
|
+
var slackWebhookUrl, slackWebhooksString, slackWebhooks, channel, formattedText, error_1, errorObj;
|
|
87
116
|
var _b, _c, _d;
|
|
88
117
|
var _e = _a === void 0 ? {} : _a, slackChannel = _e.slackChannel, _f = _e.shouldThrow, shouldThrow = _f === void 0 ? false : _f, env = _e.env, url = _e.url;
|
|
89
118
|
return __generator(this, function (_g) {
|
|
@@ -121,12 +150,13 @@ function sendToSlack(text_1) {
|
|
|
121
150
|
return [3 /*break*/, 4];
|
|
122
151
|
case 3:
|
|
123
152
|
error_1 = _g.sent();
|
|
124
|
-
|
|
153
|
+
errorObj = error_1;
|
|
154
|
+
logger_1.logger.error("Error posting to slack: ".concat((_c = errorObj.text) !== null && _c !== void 0 ? _c : errorObj.message));
|
|
125
155
|
Sentry.captureException(error_1);
|
|
126
156
|
if (shouldThrow) {
|
|
127
157
|
throw new errors_1.APIError({
|
|
128
158
|
status: 500,
|
|
129
|
-
title: "Error posting to slack: ".concat((_d =
|
|
159
|
+
title: "Error posting to slack: ".concat((_d = errorObj.text) !== null && _d !== void 0 ? _d : errorObj.message),
|
|
130
160
|
});
|
|
131
161
|
}
|
|
132
162
|
return [3 /*break*/, 4];
|
|
@@ -134,4 +164,5 @@ function sendToSlack(text_1) {
|
|
|
134
164
|
}
|
|
135
165
|
});
|
|
136
166
|
});
|
|
137
|
-
}
|
|
167
|
+
};
|
|
168
|
+
exports.sendToSlack = sendToSlack;
|
package/dist/openApiEtag.js
CHANGED
|
@@ -11,27 +11,20 @@ var node_crypto_1 = __importDefault(require("node:crypto"));
|
|
|
11
11
|
* to intercept requests to /openapi.json and add conditional request support.
|
|
12
12
|
*/
|
|
13
13
|
var openApiEtagMiddleware = function (req, res, next) {
|
|
14
|
-
// Only handle GET requests to /openapi.json
|
|
15
14
|
if (req.method !== "GET" || req.path !== "/openapi.json") {
|
|
16
15
|
next();
|
|
17
16
|
return;
|
|
18
17
|
}
|
|
19
|
-
// Store original res.json to intercept the response
|
|
20
18
|
var originalJson = res.json.bind(res);
|
|
21
19
|
res.json = function (body) {
|
|
22
|
-
// Generate ETag based on the JSON content
|
|
23
20
|
var jsonString = JSON.stringify(body);
|
|
24
21
|
var etag = "\"".concat(node_crypto_1.default.createHash("sha256").update(jsonString).digest("hex").substring(0, 16), "\"");
|
|
25
|
-
// Set ETag header
|
|
26
22
|
res.set("ETag", etag);
|
|
27
|
-
// Check If-None-Match header for conditional requests
|
|
28
23
|
var ifNoneMatch = req.get("If-None-Match");
|
|
29
24
|
if (ifNoneMatch === etag) {
|
|
30
|
-
// Resource hasn't changed, return 304 Not Modified
|
|
31
25
|
res.status(304).end();
|
|
32
26
|
return res;
|
|
33
27
|
}
|
|
34
|
-
// Resource has changed or no conditional header, return the content
|
|
35
28
|
return originalJson(body);
|
|
36
29
|
};
|
|
37
30
|
next();
|
package/dist/plugins.d.ts
CHANGED
|
@@ -69,10 +69,10 @@ export interface FindExactlyOnePlugin<T> {
|
|
|
69
69
|
findExactlyOne(query: Record<string, any>, errorArgs?: Partial<APIErrorConstructor>): Promise<Document & T>;
|
|
70
70
|
}
|
|
71
71
|
export declare class DateOnly extends SchemaType {
|
|
72
|
-
constructor(key: string, options: SchemaTypeOptions<
|
|
72
|
+
constructor(key: string, options: SchemaTypeOptions<Date>);
|
|
73
73
|
handleSingle(val: any): Date | undefined;
|
|
74
74
|
$conditionalHandlers: any;
|
|
75
75
|
castForQuery($conditional: any, val: any, context: any): Date | undefined;
|
|
76
|
-
cast(val:
|
|
77
|
-
get(val:
|
|
76
|
+
cast(val: unknown): Date | undefined;
|
|
77
|
+
get(val: unknown): this;
|
|
78
78
|
}
|
package/dist/plugins.js
CHANGED
|
@@ -154,13 +154,13 @@ var createdUpdatedPlugin = function (schema) {
|
|
|
154
154
|
}
|
|
155
155
|
// If we aren't specifying created, use now.
|
|
156
156
|
if (!this.created) {
|
|
157
|
-
this.created =
|
|
157
|
+
this.created = luxon_1.DateTime.now().toJSDate();
|
|
158
158
|
}
|
|
159
159
|
// All writes change the updated time.
|
|
160
|
-
this.updated =
|
|
160
|
+
this.updated = luxon_1.DateTime.now().toJSDate();
|
|
161
161
|
});
|
|
162
162
|
schema.pre(/save|updateOne|insertMany/, function () {
|
|
163
|
-
void this.updateOne({}, { $set: { updated:
|
|
163
|
+
void this.updateOne({}, { $set: { updated: luxon_1.DateTime.now().toJSDate() } });
|
|
164
164
|
});
|
|
165
165
|
};
|
|
166
166
|
exports.createdUpdatedPlugin = createdUpdatedPlugin;
|
|
@@ -313,6 +313,7 @@ var DateOnly = /** @class */ (function (_super) {
|
|
|
313
313
|
// When using $gt, $gte, $lt, $lte, etc, we need to cast the value to a Date
|
|
314
314
|
DateOnly.prototype.castForQuery = function ($conditional, val, context) {
|
|
315
315
|
if ($conditional == null) {
|
|
316
|
+
// noExplicitAny: applySetters is an internal Mongoose SchemaType method not in public type definitions
|
|
316
317
|
return this.applySetters(val, context);
|
|
317
318
|
}
|
|
318
319
|
var handler = this.$conditionalHandlers[$conditional];
|
|
@@ -345,10 +346,13 @@ var DateOnly = /** @class */ (function (_super) {
|
|
|
345
346
|
throw new mongoose_1.Error.CastError("DateOnly", val, this.path, new Error("Value is not a valid date"));
|
|
346
347
|
};
|
|
347
348
|
DateOnly.prototype.get = function (val) {
|
|
348
|
-
return (val instanceof Date
|
|
349
|
+
return (val instanceof Date
|
|
350
|
+
? luxon_1.DateTime.fromJSDate(val).startOf("day").toJSDate()
|
|
351
|
+
: val);
|
|
349
352
|
};
|
|
350
353
|
return DateOnly;
|
|
351
354
|
}(mongoose_1.SchemaType));
|
|
352
355
|
exports.DateOnly = DateOnly;
|
|
353
356
|
// Register DateOnly with Mongoose's Schema.Types
|
|
357
|
+
// noExplicitAny: DateOnly is a custom SchemaType not declared in Mongoose's Schema.Types interface
|
|
354
358
|
mongoose_1.default.Schema.Types.DateOnly = DateOnly;
|