@terreno/api 0.13.0 → 0.13.2
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/api.js +9 -0
- package/dist/api.test.js +2 -2
- 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 +8 -9
- package/dist/models/versionConfig.d.ts +1 -1
- package/dist/notifiers/slackNotifier.d.ts +2 -2
- package/dist/notifiers/slackNotifier.js +38 -7
- package/dist/plugins.d.ts +3 -3
- package/dist/plugins.js +8 -4
- package/dist/populate.test.js +5 -1
- package/dist/secretProviders.js +4 -1
- package/dist/tests.js +1 -0
- package/dist/transformers.d.ts +5 -5
- package/dist/transformers.js +38 -37
- package/dist/utils.js +13 -3
- package/package.json +1 -1
- package/src/api.test.ts +2 -2
- package/src/api.ts +9 -0
- package/src/consentApp.ts +3 -3
- package/src/githubAuth.test.ts +327 -0
- package/src/models/consentForm.ts +8 -10
- package/src/models/versionConfig.ts +1 -1
- package/src/notifiers/slackNotifier.ts +7 -6
- package/src/openApiEtag.ts +1 -1
- package/src/plugins.ts +13 -8
- package/src/populate.test.ts +45 -20
- package/src/secretProviders.ts +4 -3
- package/src/tests.ts +18 -14
- package/src/transformers.ts +32 -30
- package/src/utils.ts +13 -3
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;
|
package/dist/populate.test.js
CHANGED
|
@@ -92,6 +92,7 @@ var tests_1 = require("./tests");
|
|
|
92
92
|
(0, bun_test_1.describe)("populate functions", function () {
|
|
93
93
|
var admin;
|
|
94
94
|
var notAdmin;
|
|
95
|
+
// noExplicitAny: typing as HydratedDocument<Food> causes cascading errors on populated field access patterns (e.g. populated.ownerId.name)
|
|
95
96
|
var spinach;
|
|
96
97
|
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
97
98
|
var _a, _b;
|
|
@@ -228,7 +229,9 @@ var tests_1 = require("./tests");
|
|
|
228
229
|
});
|
|
229
230
|
(0, bun_test_1.it)("replaces Mixed fields with only description", function () {
|
|
230
231
|
var schema = new mongoose_1.Schema({ data: { description: "any data", type: mongoose_1.Schema.Types.Mixed } });
|
|
231
|
-
var properties = {
|
|
232
|
+
var properties = {
|
|
233
|
+
data: { description: "any data", type: "object" },
|
|
234
|
+
};
|
|
232
235
|
(0, populate_1.fixMixedFields)(schema, properties);
|
|
233
236
|
(0, bun_test_1.expect)(properties.data).toEqual({ description: "any data" });
|
|
234
237
|
});
|
|
@@ -338,6 +341,7 @@ var tests_1 = require("./tests");
|
|
|
338
341
|
populatePaths: [{ fields: ["__proto__.polluted"], path: "ownerId" }],
|
|
339
342
|
});
|
|
340
343
|
(0, bun_test_1.expect)(result.properties).toBeDefined();
|
|
344
|
+
// noExplicitAny: testing that prototype pollution did not add a 'polluted' property to Object.prototype
|
|
341
345
|
(0, bun_test_1.expect)(Object.prototype.polluted).toBeUndefined();
|
|
342
346
|
var ownerProps = result.properties.ownerId.properties;
|
|
343
347
|
(0, bun_test_1.expect)(ownerProps).toBeDefined();
|
package/dist/secretProviders.js
CHANGED
|
@@ -168,7 +168,10 @@ var GcpSecretProvider = /** @class */ (function () {
|
|
|
168
168
|
case 4:
|
|
169
169
|
SecretManagerServiceClient = (_b = mod.SecretManagerServiceClient) !== null && _b !== void 0 ? _b : (_c = mod.default) === null || _c === void 0 ? void 0 : _c.SecretManagerServiceClient;
|
|
170
170
|
if (!SecretManagerServiceClient) {
|
|
171
|
-
throw new
|
|
171
|
+
throw new errors_1.APIError({
|
|
172
|
+
status: 500,
|
|
173
|
+
title: "SecretManagerServiceClient not found in @google-cloud/secret-manager module",
|
|
174
|
+
});
|
|
172
175
|
}
|
|
173
176
|
this.client = new SecretManagerServiceClient();
|
|
174
177
|
_d.label = 5;
|
package/dist/tests.js
CHANGED
|
@@ -156,6 +156,7 @@ var foodSchema = new mongoose_1.Schema({
|
|
|
156
156
|
type: mongoose_1.Schema.Types.ObjectId,
|
|
157
157
|
},
|
|
158
158
|
],
|
|
159
|
+
// noExplicitAny: DateOnly is a custom SchemaType not recognized by Mongoose's built-in type definitions
|
|
159
160
|
expiration: { description: "Expiration date of the food", type: plugins_1.DateOnly },
|
|
160
161
|
hidden: {
|
|
161
162
|
default: false,
|
package/dist/transformers.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface TerrenoTransformer<T> {
|
|
|
6
6
|
transform?: (obj: Partial<T>, method: "create" | "update", user?: User) => Partial<T> | undefined;
|
|
7
7
|
serialize?: (obj: T, user?: User) => Partial<T> | undefined;
|
|
8
8
|
}
|
|
9
|
-
export declare
|
|
9
|
+
export declare const AdminOwnerTransformer: <T>(options: {
|
|
10
10
|
anonReadFields?: string[];
|
|
11
11
|
authReadFields?: string[];
|
|
12
12
|
ownerReadFields?: string[];
|
|
@@ -15,11 +15,11 @@ export declare function AdminOwnerTransformer<T>(options: {
|
|
|
15
15
|
authWriteFields?: string[];
|
|
16
16
|
ownerWriteFields?: string[];
|
|
17
17
|
adminWriteFields?: string[];
|
|
18
|
-
})
|
|
19
|
-
export declare
|
|
20
|
-
export declare
|
|
18
|
+
}) => TerrenoTransformer<T>;
|
|
19
|
+
export declare const transform: <T>(options: ModelRouterOptions<T>, data: Partial<T> | Partial<T>[], method: "create" | "update", user?: User) => Partial<T> | (Partial<T> | undefined)[] | undefined;
|
|
20
|
+
export declare const serialize: <T>(req: express.Request, options: ModelRouterOptions<T>, data: (Document & T) | (Document & T)[]) => Partial<T> | (Partial<T> | undefined)[] | undefined;
|
|
21
21
|
/**
|
|
22
22
|
* Default response handler for modelRouter. Calls toObject on each doc and returns the result,
|
|
23
23
|
* using transformers.serializer if provided.
|
|
24
24
|
*/
|
|
25
|
-
export declare
|
|
25
|
+
export declare const defaultResponseHandler: <T>(doc: (Document & T) | (Document & T)[] | null, method: "list" | "create" | "read" | "update", request: express.Request, options: ModelRouterOptions<T>) => Promise<Partial<T> | (Partial<T> | undefined)[] | null | undefined>;
|
package/dist/transformers.js
CHANGED
|
@@ -72,13 +72,10 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
|
72
72
|
return to.concat(ar || Array.prototype.slice.call(from));
|
|
73
73
|
};
|
|
74
74
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
75
|
-
exports.AdminOwnerTransformer =
|
|
76
|
-
exports.transform = transform;
|
|
77
|
-
exports.serialize = serialize;
|
|
78
|
-
exports.defaultResponseHandler = defaultResponseHandler;
|
|
75
|
+
exports.defaultResponseHandler = exports.serialize = exports.transform = exports.AdminOwnerTransformer = void 0;
|
|
79
76
|
var errors_1 = require("./errors");
|
|
80
77
|
var logger_1 = require("./logger");
|
|
81
|
-
function
|
|
78
|
+
var getUserType = function (user, obj) {
|
|
82
79
|
if (user === null || user === void 0 ? void 0 : user.admin) {
|
|
83
80
|
return "admin";
|
|
84
81
|
}
|
|
@@ -89,9 +86,9 @@ function getUserType(user, obj) {
|
|
|
89
86
|
return "auth";
|
|
90
87
|
}
|
|
91
88
|
return "anon";
|
|
92
|
-
}
|
|
93
|
-
function
|
|
94
|
-
function
|
|
89
|
+
};
|
|
90
|
+
var AdminOwnerTransformer = function (options) {
|
|
91
|
+
var pickFields = function (obj, fields) {
|
|
95
92
|
var e_1, _a;
|
|
96
93
|
var newData = {};
|
|
97
94
|
try {
|
|
@@ -110,7 +107,7 @@ function AdminOwnerTransformer(options) {
|
|
|
110
107
|
finally { if (e_1) throw e_1.error; }
|
|
111
108
|
}
|
|
112
109
|
return newData;
|
|
113
|
-
}
|
|
110
|
+
};
|
|
114
111
|
return {
|
|
115
112
|
serialize: function (obj, user) {
|
|
116
113
|
var _a, _b, _c, _d;
|
|
@@ -126,7 +123,6 @@ function AdminOwnerTransformer(options) {
|
|
|
126
123
|
}
|
|
127
124
|
return pickFields(obj, __spreadArray(__spreadArray([], __read(((_d = options.anonReadFields) !== null && _d !== void 0 ? _d : [])), false), ["id"], false));
|
|
128
125
|
},
|
|
129
|
-
// TODO: Migrate AdminOwnerTransform to use pre-hooks.
|
|
130
126
|
transform: function (obj, _method, user) {
|
|
131
127
|
var _a, _b, _c, _d;
|
|
132
128
|
var userType = getUserType(user, obj);
|
|
@@ -145,13 +141,17 @@ function AdminOwnerTransformer(options) {
|
|
|
145
141
|
}
|
|
146
142
|
var unallowedFields = Object.keys(obj).filter(function (k) { return !allowedFields.includes(k); });
|
|
147
143
|
if (unallowedFields.length) {
|
|
148
|
-
throw new
|
|
144
|
+
throw new errors_1.APIError({
|
|
145
|
+
status: 403,
|
|
146
|
+
title: "User of type ".concat(userType, " cannot write fields: ").concat(unallowedFields.join(", ")),
|
|
147
|
+
});
|
|
149
148
|
}
|
|
150
149
|
return obj;
|
|
151
150
|
},
|
|
152
151
|
};
|
|
153
|
-
}
|
|
154
|
-
|
|
152
|
+
};
|
|
153
|
+
exports.AdminOwnerTransformer = AdminOwnerTransformer;
|
|
154
|
+
var transform = function (options, data, method, user) {
|
|
155
155
|
var _a, _b;
|
|
156
156
|
if (!((_a = options.transformer) === null || _a === void 0 ? void 0 : _a.transform)) {
|
|
157
157
|
return data;
|
|
@@ -163,8 +163,9 @@ function transform(options, data, method, user) {
|
|
|
163
163
|
return transformFn(data, method, user);
|
|
164
164
|
}
|
|
165
165
|
return data.map(function (d) { return transformFn(d, method, user); });
|
|
166
|
-
}
|
|
167
|
-
|
|
166
|
+
};
|
|
167
|
+
exports.transform = transform;
|
|
168
|
+
var serialize = function (req, options, data) {
|
|
168
169
|
var _a;
|
|
169
170
|
var serializeFn = function (serializeData, serializeUser) {
|
|
170
171
|
var _a, _b;
|
|
@@ -190,30 +191,30 @@ function serialize(req, options, data) {
|
|
|
190
191
|
return serializeFn(data, req.user);
|
|
191
192
|
}
|
|
192
193
|
return data.map(function (d) { return serializeFn(d, req.user); });
|
|
193
|
-
}
|
|
194
|
+
};
|
|
195
|
+
exports.serialize = serialize;
|
|
194
196
|
/**
|
|
195
197
|
* Default response handler for modelRouter. Calls toObject on each doc and returns the result,
|
|
196
198
|
* using transformers.serializer if provided.
|
|
197
199
|
*/
|
|
198
|
-
function
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return [2 /*return*/];
|
|
217
|
-
});
|
|
200
|
+
var defaultResponseHandler = function (doc, method, request, options) { return __awaiter(void 0, void 0, void 0, function () {
|
|
201
|
+
var errorObj;
|
|
202
|
+
return __generator(this, function (_a) {
|
|
203
|
+
if (!doc) {
|
|
204
|
+
return [2 /*return*/, null];
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
return [2 /*return*/, (0, exports.serialize)(request, options, doc)];
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
errorObj = error;
|
|
211
|
+
throw new errors_1.APIError({
|
|
212
|
+
error: errorObj,
|
|
213
|
+
status: 400,
|
|
214
|
+
title: "Error serializing ".concat(method, " response: ").concat(errorObj.message),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return [2 /*return*/];
|
|
218
218
|
});
|
|
219
|
-
}
|
|
219
|
+
}); };
|
|
220
|
+
exports.defaultResponseHandler = defaultResponseHandler;
|
package/dist/utils.js
CHANGED
|
@@ -82,6 +82,7 @@ var __values = (this && this.__values) || function(o) {
|
|
|
82
82
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
83
83
|
exports.checkModelsStrict = exports.timeout = exports.isValidObjectId = void 0;
|
|
84
84
|
var mongoose_1 = __importStar(require("mongoose"));
|
|
85
|
+
var errors_1 = require("./errors");
|
|
85
86
|
var logger_1 = require("./logger");
|
|
86
87
|
// A better version of mongoose's ObjectId.isValid,
|
|
87
88
|
// which falsely will say any 12 character string is valid.
|
|
@@ -119,16 +120,25 @@ var checkModelsStrict = function (ignoredModels) {
|
|
|
119
120
|
var model = models_1_1.value;
|
|
120
121
|
var schema = mongoose_1.default.model(model).schema;
|
|
121
122
|
if (((_b = schema.get("toObject")) === null || _b === void 0 ? void 0 : _b.virtuals) !== true) {
|
|
122
|
-
throw new
|
|
123
|
+
throw new errors_1.APIError({
|
|
124
|
+
status: 500,
|
|
125
|
+
title: "Model ".concat(model, " toObject.virtuals not set to true"),
|
|
126
|
+
});
|
|
123
127
|
}
|
|
124
128
|
if (((_c = schema.get("toJSON")) === null || _c === void 0 ? void 0 : _c.virtuals) !== true) {
|
|
125
|
-
throw new
|
|
129
|
+
throw new errors_1.APIError({
|
|
130
|
+
status: 500,
|
|
131
|
+
title: "Model ".concat(model, " toJSON.virtuals not set to true"),
|
|
132
|
+
});
|
|
126
133
|
}
|
|
127
134
|
if (ignoredModels.includes(model)) {
|
|
128
135
|
continue;
|
|
129
136
|
}
|
|
130
137
|
if (schema.get("strict") !== "throw") {
|
|
131
|
-
throw new
|
|
138
|
+
throw new errors_1.APIError({
|
|
139
|
+
status: 500,
|
|
140
|
+
title: "Model ".concat(model, " is not set to strict mode."),
|
|
141
|
+
});
|
|
132
142
|
}
|
|
133
143
|
}
|
|
134
144
|
}
|
package/package.json
CHANGED
package/src/api.test.ts
CHANGED
|
@@ -576,7 +576,7 @@ describe("@terreno/api", () => {
|
|
|
576
576
|
);
|
|
577
577
|
server = supertest(app);
|
|
578
578
|
|
|
579
|
-
const res = await server.post("/food").send({calories: 15, name: "Broccoli"}).expect(
|
|
579
|
+
const res = await server.post("/food").send({calories: 15, name: "Broccoli"}).expect(403);
|
|
580
580
|
expect(res.body.title).toContain("cannot write fields");
|
|
581
581
|
});
|
|
582
582
|
|
|
@@ -1324,7 +1324,7 @@ describe("@terreno/api", () => {
|
|
|
1324
1324
|
);
|
|
1325
1325
|
server = supertest(app);
|
|
1326
1326
|
|
|
1327
|
-
const res = await server.post("/food").send({calories: 15, name: "Broccoli"}).expect(
|
|
1327
|
+
const res = await server.post("/food").send({calories: 15, name: "Broccoli"}).expect(403);
|
|
1328
1328
|
expect(res.body.title).toContain("cannot write fields");
|
|
1329
1329
|
});
|
|
1330
1330
|
|
package/src/api.ts
CHANGED
|
@@ -528,6 +528,9 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
528
528
|
try {
|
|
529
529
|
body = transform<T>(options, req.body, "create", req.user);
|
|
530
530
|
} catch (error: any) {
|
|
531
|
+
if (isAPIError(error)) {
|
|
532
|
+
throw error;
|
|
533
|
+
}
|
|
531
534
|
throw new APIError({
|
|
532
535
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
533
536
|
error,
|
|
@@ -827,6 +830,9 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
827
830
|
try {
|
|
828
831
|
body = transform<T>(options, req.body, "update", req.user);
|
|
829
832
|
} catch (error: any) {
|
|
833
|
+
if (isAPIError(error)) {
|
|
834
|
+
throw error;
|
|
835
|
+
}
|
|
830
836
|
throw new APIError({
|
|
831
837
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
832
838
|
error,
|
|
@@ -1080,6 +1086,9 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
|
|
|
1080
1086
|
try {
|
|
1081
1087
|
body = transform<T>(options, body, "update", req.user) as Partial<T>;
|
|
1082
1088
|
} catch (error: any) {
|
|
1089
|
+
if (isAPIError(error)) {
|
|
1090
|
+
throw error;
|
|
1091
|
+
}
|
|
1083
1092
|
throw new APIError({
|
|
1084
1093
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
1085
1094
|
error,
|
package/src/consentApp.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* endpoints for fetching pending consents and submitting responses.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type
|
|
9
|
+
import {type Application, Router} from "express";
|
|
10
10
|
import {DateTime} from "luxon";
|
|
11
11
|
import {asyncHandler, modelRouter} from "./api";
|
|
12
12
|
import type {User} from "./auth";
|
|
@@ -47,7 +47,7 @@ export class ConsentApp implements TerrenoPlugin {
|
|
|
47
47
|
this.options = options;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
register(app:
|
|
50
|
+
register(app: Application): void {
|
|
51
51
|
const {auditTrail, resolveConsentForms, aiConfig} = this.options;
|
|
52
52
|
|
|
53
53
|
// Admin CRUD for consent forms
|
|
@@ -192,7 +192,7 @@ export class ConsentApp implements TerrenoPlugin {
|
|
|
192
192
|
);
|
|
193
193
|
|
|
194
194
|
// User-facing consent endpoints
|
|
195
|
-
const router =
|
|
195
|
+
const router = Router();
|
|
196
196
|
|
|
197
197
|
// GET /consents/pending - fetch pending consent forms for the current user
|
|
198
198
|
router.get(
|