@terreno/api 0.0.11-beta.1 → 0.0.11
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/bunfig.toml +2 -3
- package/dist/api.d.ts +14 -3
- package/dist/api.js +68 -44
- package/dist/api.test.js +166 -2051
- package/dist/permissions.d.ts +1 -1
- package/dist/permissions.js +25 -17
- package/dist/utils.test.js +7 -169
- package/package.json +1 -2
- package/src/api.test.ts +142 -1736
- package/src/api.ts +61 -19
- package/src/permissions.ts +14 -4
- package/src/utils.test.ts +9 -189
- package/CLAUDE.md +0 -107
package/dist/permissions.d.ts
CHANGED
|
@@ -23,4 +23,4 @@ export declare const Permissions: {
|
|
|
23
23
|
IsOwnerOrReadOnly: (method: RESTMethod, user?: User, obj?: any) => boolean;
|
|
24
24
|
};
|
|
25
25
|
export declare function checkPermissions<T>(method: RESTMethod, permissions: PermissionMethod<T>[], user?: User, obj?: T): Promise<boolean>;
|
|
26
|
-
export declare function permissionMiddleware<T>(
|
|
26
|
+
export declare function permissionMiddleware<T>(baseModel: Model<T>, options: Pick<ModelRouterOptions<T>, "permissions" | "populatePaths" | "discriminatorKey">): (req: express.Request, _res: express.Response, next: NextFunction) => Promise<void>;
|
package/dist/permissions.js
CHANGED
|
@@ -194,20 +194,20 @@ function checkPermissions(method, permissions, user, obj) {
|
|
|
194
194
|
// Check the permissions for a given model and method. If the method is a read, update, or delete,
|
|
195
195
|
// finds the relevant object, checks the permissions, and attaches the object to the request as
|
|
196
196
|
// req.obj.
|
|
197
|
-
function permissionMiddleware(
|
|
197
|
+
function permissionMiddleware(baseModel, options) {
|
|
198
198
|
var _this = this;
|
|
199
199
|
return function (req, _res, next) { return __awaiter(_this, void 0, void 0, function () {
|
|
200
|
-
var method, reqMethod, builtQuery, populatedQuery, data, error_1, hiddenDoc, error, reason, error, error_2;
|
|
201
|
-
var _a, _b;
|
|
202
|
-
return __generator(this, function (
|
|
203
|
-
switch (
|
|
200
|
+
var method, reqMethod, model, builtQuery, populatedQuery, data, error_1, hiddenDoc, error, reason, error, error_2;
|
|
201
|
+
var _a, _b, _c, _d;
|
|
202
|
+
return __generator(this, function (_e) {
|
|
203
|
+
switch (_e.label) {
|
|
204
204
|
case 0:
|
|
205
205
|
if (req.method === "OPTIONS") {
|
|
206
206
|
return [2 /*return*/, next()];
|
|
207
207
|
}
|
|
208
|
-
|
|
208
|
+
_e.label = 1;
|
|
209
209
|
case 1:
|
|
210
|
-
|
|
210
|
+
_e.trys.push([1, 10, , 11]);
|
|
211
211
|
method = void 0;
|
|
212
212
|
reqMethod = req.method.toLowerCase();
|
|
213
213
|
if (reqMethod === "post") {
|
|
@@ -233,10 +233,11 @@ function permissionMiddleware(model, options) {
|
|
|
233
233
|
title: "Method ".concat(req.method, " not allowed"),
|
|
234
234
|
});
|
|
235
235
|
}
|
|
236
|
+
model = (0, api_1.getModel)(baseModel, req.body, options);
|
|
236
237
|
return [4 /*yield*/, checkPermissions(method, options.permissions[method], req.user)];
|
|
237
238
|
case 2:
|
|
238
239
|
// All methods check for permissions.
|
|
239
|
-
if (!(
|
|
240
|
+
if (!(_e.sent())) {
|
|
240
241
|
throw new errors_1.APIError({
|
|
241
242
|
status: 405,
|
|
242
243
|
title: "Access to ".concat(method.toUpperCase(), " on ").concat(model.modelName, " ") +
|
|
@@ -249,27 +250,34 @@ function permissionMiddleware(model, options) {
|
|
|
249
250
|
builtQuery = model.findById(req.params.id);
|
|
250
251
|
populatedQuery = (0, api_1.addPopulateToQuery)(builtQuery, options.populatePaths);
|
|
251
252
|
data = void 0;
|
|
252
|
-
|
|
253
|
+
_e.label = 3;
|
|
253
254
|
case 3:
|
|
254
|
-
|
|
255
|
+
_e.trys.push([3, 5, , 6]);
|
|
255
256
|
return [4 /*yield*/, populatedQuery.exec()];
|
|
256
257
|
case 4:
|
|
257
|
-
data =
|
|
258
|
+
data = _e.sent();
|
|
258
259
|
return [3 /*break*/, 6];
|
|
259
260
|
case 5:
|
|
260
|
-
error_1 =
|
|
261
|
+
error_1 = _e.sent();
|
|
261
262
|
throw new errors_1.APIError({
|
|
262
263
|
error: error_1,
|
|
263
264
|
status: 500,
|
|
264
265
|
title: "GET failed on ".concat(req.params.id),
|
|
265
266
|
});
|
|
266
267
|
case 6:
|
|
267
|
-
if (
|
|
268
|
+
if (!(!data || (["update", "delete"].includes(method) && (data === null || data === void 0 ? void 0 : data.__t) && !((_b = req.body) === null || _b === void 0 ? void 0 : _b.__t)))) return [3 /*break*/, 8];
|
|
269
|
+
// For discriminated models, return 404 without checking hidden state
|
|
270
|
+
if (["update", "delete"].includes(method) && (data === null || data === void 0 ? void 0 : data.__t) && !((_c = req.body) === null || _c === void 0 ? void 0 : _c.__t)) {
|
|
271
|
+
throw new errors_1.APIError({
|
|
272
|
+
status: 404,
|
|
273
|
+
title: "Document ".concat(req.params.id, " not found for model ").concat(model.modelName),
|
|
274
|
+
});
|
|
275
|
+
}
|
|
268
276
|
return [4 /*yield*/, model.collection.findOne({
|
|
269
277
|
_id: new mongoose_1.default.Types.ObjectId(req.params.id),
|
|
270
278
|
})];
|
|
271
279
|
case 7:
|
|
272
|
-
hiddenDoc =
|
|
280
|
+
hiddenDoc = _e.sent();
|
|
273
281
|
if (!hiddenDoc) {
|
|
274
282
|
Sentry.captureMessage("Document ".concat(req.params.id, " not found for model ").concat(model.modelName));
|
|
275
283
|
error = new errors_1.APIError({
|
|
@@ -304,16 +312,16 @@ function permissionMiddleware(model, options) {
|
|
|
304
312
|
});
|
|
305
313
|
case 8: return [4 /*yield*/, checkPermissions(method, options.permissions[method], req.user, data)];
|
|
306
314
|
case 9:
|
|
307
|
-
if (!(
|
|
315
|
+
if (!(_e.sent())) {
|
|
308
316
|
throw new errors_1.APIError({
|
|
309
317
|
status: 403,
|
|
310
|
-
title: "Access to GET on ".concat(model.modelName, ":").concat(req.params.id, " denied for ").concat((
|
|
318
|
+
title: "Access to GET on ".concat(model.modelName, ":").concat(req.params.id, " denied for ").concat((_d = req.user) === null || _d === void 0 ? void 0 : _d.id),
|
|
311
319
|
});
|
|
312
320
|
}
|
|
313
321
|
req.obj = data;
|
|
314
322
|
return [2 /*return*/, next()];
|
|
315
323
|
case 10:
|
|
316
|
-
error_2 =
|
|
324
|
+
error_2 = _e.sent();
|
|
317
325
|
logger_1.logger.error("Permissions error: ".concat(error_2 instanceof Error ? error_2.message : error_2));
|
|
318
326
|
return [2 /*return*/, next(error_2)];
|
|
319
327
|
case 11: return [2 /*return*/];
|
package/dist/utils.test.js
CHANGED
|
@@ -1,176 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
var bun_test_1 = require("bun:test");
|
|
7
|
-
var mongoose_1 = __importDefault(require("mongoose"));
|
|
8
4
|
var utils_1 = require("./utils");
|
|
9
5
|
(0, bun_test_1.describe)("utils", function () {
|
|
10
|
-
(0, bun_test_1.
|
|
11
|
-
(0, bun_test_1.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
(0, bun_test_1.expect)((0, utils_1.isValidObjectId)("62c44da0003d9f8ee8cc925x")).toBe(false);
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
(0, bun_test_1.describe)("checkModelsStrict", function () {
|
|
21
|
-
(0, bun_test_1.it)("throws error when toObject.virtuals is not true", function () {
|
|
22
|
-
// Create a schema without toObject.virtuals
|
|
23
|
-
var testSchema = new mongoose_1.default.Schema({ name: String });
|
|
24
|
-
testSchema.set("strict", "throw");
|
|
25
|
-
// Not setting toObject.virtuals
|
|
26
|
-
if (mongoose_1.default.models.ToObjectTestModel) {
|
|
27
|
-
delete mongoose_1.default.models.ToObjectTestModel;
|
|
28
|
-
}
|
|
29
|
-
mongoose_1.default.model("ToObjectTestModel", testSchema);
|
|
30
|
-
try {
|
|
31
|
-
// This should throw because ToObjectTestModel doesn't have toObject.virtuals
|
|
32
|
-
(0, bun_test_1.expect)(function () { return (0, utils_1.checkModelsStrict)(); }).toThrow("toObject.virtuals not set to true");
|
|
33
|
-
}
|
|
34
|
-
finally {
|
|
35
|
-
delete mongoose_1.default.models.ToObjectTestModel;
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
(0, bun_test_1.it)("throws error when toJSON.virtuals is not true", function () {
|
|
39
|
-
// Create a schema with toObject.virtuals but without toJSON.virtuals
|
|
40
|
-
var testSchema = new mongoose_1.default.Schema({ name: String });
|
|
41
|
-
testSchema.set("toObject", { virtuals: true });
|
|
42
|
-
testSchema.set("strict", "throw");
|
|
43
|
-
// Not setting toJSON.virtuals
|
|
44
|
-
if (mongoose_1.default.models.ToJsonTestModel) {
|
|
45
|
-
delete mongoose_1.default.models.ToJsonTestModel;
|
|
46
|
-
}
|
|
47
|
-
mongoose_1.default.model("ToJsonTestModel", testSchema);
|
|
48
|
-
// Use spyOn to intercept modelNames and return only our test model
|
|
49
|
-
var spy = (0, bun_test_1.spyOn)(mongoose_1.default, "modelNames").mockReturnValue(["ToJsonTestModel"]);
|
|
50
|
-
try {
|
|
51
|
-
(0, bun_test_1.expect)(function () { return (0, utils_1.checkModelsStrict)(); }).toThrow("toJSON.virtuals not set to true");
|
|
52
|
-
}
|
|
53
|
-
finally {
|
|
54
|
-
spy.mockRestore();
|
|
55
|
-
delete mongoose_1.default.models.ToJsonTestModel;
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
(0, bun_test_1.it)("throws error when strict mode is not set to throw", function () {
|
|
59
|
-
// Create a schema with virtuals but without strict mode
|
|
60
|
-
var testSchema = new mongoose_1.default.Schema({ name: String });
|
|
61
|
-
testSchema.set("toObject", { virtuals: true });
|
|
62
|
-
testSchema.set("toJSON", { virtuals: true });
|
|
63
|
-
// Not setting strict to "throw"
|
|
64
|
-
if (mongoose_1.default.models.StrictTestModel) {
|
|
65
|
-
delete mongoose_1.default.models.StrictTestModel;
|
|
66
|
-
}
|
|
67
|
-
mongoose_1.default.model("StrictTestModel", testSchema);
|
|
68
|
-
var spy = (0, bun_test_1.spyOn)(mongoose_1.default, "modelNames").mockReturnValue(["StrictTestModel"]);
|
|
69
|
-
try {
|
|
70
|
-
(0, bun_test_1.expect)(function () { return (0, utils_1.checkModelsStrict)(); }).toThrow("is not set to strict mode");
|
|
71
|
-
}
|
|
72
|
-
finally {
|
|
73
|
-
spy.mockRestore();
|
|
74
|
-
delete mongoose_1.default.models.StrictTestModel;
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
(0, bun_test_1.it)("passes when all checks pass", function () {
|
|
78
|
-
// Create a properly configured schema
|
|
79
|
-
var testSchema = new mongoose_1.default.Schema({ name: String });
|
|
80
|
-
testSchema.set("toObject", { virtuals: true });
|
|
81
|
-
testSchema.set("toJSON", { virtuals: true });
|
|
82
|
-
testSchema.set("strict", "throw");
|
|
83
|
-
if (mongoose_1.default.models.GoodTestModel) {
|
|
84
|
-
delete mongoose_1.default.models.GoodTestModel;
|
|
85
|
-
}
|
|
86
|
-
mongoose_1.default.model("GoodTestModel", testSchema);
|
|
87
|
-
var spy = (0, bun_test_1.spyOn)(mongoose_1.default, "modelNames").mockReturnValue(["GoodTestModel"]);
|
|
88
|
-
try {
|
|
89
|
-
(0, bun_test_1.expect)(function () { return (0, utils_1.checkModelsStrict)(); }).not.toThrow();
|
|
90
|
-
}
|
|
91
|
-
finally {
|
|
92
|
-
spy.mockRestore();
|
|
93
|
-
delete mongoose_1.default.models.GoodTestModel;
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
(0, bun_test_1.it)("skips strict mode check for ignored models", function () {
|
|
97
|
-
// Create a properly configured model
|
|
98
|
-
var goodSchema = new mongoose_1.default.Schema({ name: String });
|
|
99
|
-
goodSchema.set("toObject", { virtuals: true });
|
|
100
|
-
goodSchema.set("toJSON", { virtuals: true });
|
|
101
|
-
goodSchema.set("strict", "throw");
|
|
102
|
-
if (mongoose_1.default.models.GoodModel) {
|
|
103
|
-
delete mongoose_1.default.models.GoodModel;
|
|
104
|
-
}
|
|
105
|
-
mongoose_1.default.model("GoodModel", goodSchema);
|
|
106
|
-
// Create a model without strict mode that we'll ignore
|
|
107
|
-
var badSchema = new mongoose_1.default.Schema({ name: String });
|
|
108
|
-
badSchema.set("toObject", { virtuals: true });
|
|
109
|
-
badSchema.set("toJSON", { virtuals: true });
|
|
110
|
-
// Not setting strict - should fail unless ignored
|
|
111
|
-
if (mongoose_1.default.models.IgnoredModel) {
|
|
112
|
-
delete mongoose_1.default.models.IgnoredModel;
|
|
113
|
-
}
|
|
114
|
-
mongoose_1.default.model("IgnoredModel", badSchema);
|
|
115
|
-
var spy = (0, bun_test_1.spyOn)(mongoose_1.default, "modelNames").mockReturnValue(["GoodModel", "IgnoredModel"]);
|
|
116
|
-
try {
|
|
117
|
-
// Without ignoring, should throw for IgnoredModel
|
|
118
|
-
(0, bun_test_1.expect)(function () { return (0, utils_1.checkModelsStrict)(); }).toThrow("is not set to strict mode");
|
|
119
|
-
// With ignoring IgnoredModel, should pass
|
|
120
|
-
(0, bun_test_1.expect)(function () { return (0, utils_1.checkModelsStrict)(["IgnoredModel"]); }).not.toThrow();
|
|
121
|
-
}
|
|
122
|
-
finally {
|
|
123
|
-
spy.mockRestore();
|
|
124
|
-
delete mongoose_1.default.models.GoodModel;
|
|
125
|
-
delete mongoose_1.default.models.IgnoredModel;
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
(0, bun_test_1.it)("handles multiple models and validates all", function () {
|
|
129
|
-
// Create three properly configured models
|
|
130
|
-
var schema1 = new mongoose_1.default.Schema({ name: String });
|
|
131
|
-
schema1.set("toObject", { virtuals: true });
|
|
132
|
-
schema1.set("toJSON", { virtuals: true });
|
|
133
|
-
schema1.set("strict", "throw");
|
|
134
|
-
var schema2 = new mongoose_1.default.Schema({ value: Number });
|
|
135
|
-
schema2.set("toObject", { virtuals: true });
|
|
136
|
-
schema2.set("toJSON", { virtuals: true });
|
|
137
|
-
schema2.set("strict", "throw");
|
|
138
|
-
var schema3 = new mongoose_1.default.Schema({ active: Boolean });
|
|
139
|
-
schema3.set("toObject", { virtuals: true });
|
|
140
|
-
schema3.set("toJSON", { virtuals: true });
|
|
141
|
-
schema3.set("strict", "throw");
|
|
142
|
-
if (mongoose_1.default.models.MultiModel1)
|
|
143
|
-
delete mongoose_1.default.models.MultiModel1;
|
|
144
|
-
if (mongoose_1.default.models.MultiModel2)
|
|
145
|
-
delete mongoose_1.default.models.MultiModel2;
|
|
146
|
-
if (mongoose_1.default.models.MultiModel3)
|
|
147
|
-
delete mongoose_1.default.models.MultiModel3;
|
|
148
|
-
mongoose_1.default.model("MultiModel1", schema1);
|
|
149
|
-
mongoose_1.default.model("MultiModel2", schema2);
|
|
150
|
-
mongoose_1.default.model("MultiModel3", schema3);
|
|
151
|
-
var spy = (0, bun_test_1.spyOn)(mongoose_1.default, "modelNames").mockReturnValue([
|
|
152
|
-
"MultiModel1",
|
|
153
|
-
"MultiModel2",
|
|
154
|
-
"MultiModel3",
|
|
155
|
-
]);
|
|
156
|
-
try {
|
|
157
|
-
(0, bun_test_1.expect)(function () { return (0, utils_1.checkModelsStrict)(); }).not.toThrow();
|
|
158
|
-
}
|
|
159
|
-
finally {
|
|
160
|
-
spy.mockRestore();
|
|
161
|
-
delete mongoose_1.default.models.MultiModel1;
|
|
162
|
-
delete mongoose_1.default.models.MultiModel2;
|
|
163
|
-
delete mongoose_1.default.models.MultiModel3;
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
(0, bun_test_1.it)("handles empty model list", function () {
|
|
167
|
-
var spy = (0, bun_test_1.spyOn)(mongoose_1.default, "modelNames").mockReturnValue([]);
|
|
168
|
-
try {
|
|
169
|
-
(0, bun_test_1.expect)(function () { return (0, utils_1.checkModelsStrict)(); }).not.toThrow();
|
|
170
|
-
}
|
|
171
|
-
finally {
|
|
172
|
-
spy.mockRestore();
|
|
173
|
-
}
|
|
174
|
-
});
|
|
6
|
+
(0, bun_test_1.it)("checks valid ObjectIds", function () {
|
|
7
|
+
(0, bun_test_1.expect)((0, utils_1.isValidObjectId)("62c44da0003d9f8ee8cc925c")).toBe(true);
|
|
8
|
+
(0, bun_test_1.expect)((0, utils_1.isValidObjectId)("620000000000000000000000")).toBe(true);
|
|
9
|
+
// Mongoose's builtin "ObjectId.isValid" will falsely say this is an ObjectId.
|
|
10
|
+
(0, bun_test_1.expect)((0, utils_1.isValidObjectId)("1234567890ab")).toBe(false);
|
|
11
|
+
(0, bun_test_1.expect)((0, utils_1.isValidObjectId)("microsoft123")).toBe(false);
|
|
12
|
+
(0, bun_test_1.expect)((0, utils_1.isValidObjectId)("62c44da0003d9f8ee8cc925x")).toBe(false);
|
|
175
13
|
});
|
|
176
14
|
});
|
package/package.json
CHANGED
|
@@ -81,9 +81,8 @@
|
|
|
81
81
|
"lint:fix": "biome check --write ./src",
|
|
82
82
|
"lint:unsafefix": "biome check --fix --unsafe ./src",
|
|
83
83
|
"test": "bun test --preload ./src/tests/bunSetup.ts",
|
|
84
|
-
"test:ci": "bun test --preload ./src/tests/bunSetup.ts",
|
|
85
84
|
"updateSnapshot": "bun test --update-snapshots"
|
|
86
85
|
},
|
|
87
86
|
"types": "dist/index.d.ts",
|
|
88
|
-
"version": "0.0.11
|
|
87
|
+
"version": "0.0.11"
|
|
89
88
|
}
|