@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.
@@ -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>(model: Model<T>, options: Pick<ModelRouterOptions<T>, "permissions" | "populatePaths">): (req: express.Request, _res: express.Response, next: NextFunction) => Promise<void>;
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>;
@@ -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(model, options) {
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 (_c) {
203
- switch (_c.label) {
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
- _c.label = 1;
208
+ _e.label = 1;
209
209
  case 1:
210
- _c.trys.push([1, 10, , 11]);
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 (!(_c.sent())) {
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
- _c.label = 3;
253
+ _e.label = 3;
253
254
  case 3:
254
- _c.trys.push([3, 5, , 6]);
255
+ _e.trys.push([3, 5, , 6]);
255
256
  return [4 /*yield*/, populatedQuery.exec()];
256
257
  case 4:
257
- data = _c.sent();
258
+ data = _e.sent();
258
259
  return [3 /*break*/, 6];
259
260
  case 5:
260
- error_1 = _c.sent();
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 (!!data) return [3 /*break*/, 8];
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 = _c.sent();
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 (!(_c.sent())) {
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((_b = req.user) === null || _b === void 0 ? void 0 : _b.id),
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 = _c.sent();
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*/];
@@ -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.describe)("isValidObjectId", function () {
11
- (0, bun_test_1.it)("checks valid ObjectIds", function () {
12
- (0, bun_test_1.expect)((0, utils_1.isValidObjectId)("62c44da0003d9f8ee8cc925c")).toBe(true);
13
- (0, bun_test_1.expect)((0, utils_1.isValidObjectId)("620000000000000000000000")).toBe(true);
14
- // Mongoose's builtin "ObjectId.isValid" will falsely say this is an ObjectId.
15
- (0, bun_test_1.expect)((0, utils_1.isValidObjectId)("1234567890ab")).toBe(false);
16
- (0, bun_test_1.expect)((0, utils_1.isValidObjectId)("microsoft123")).toBe(false);
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-beta.1"
87
+ "version": "0.0.11"
89
88
  }