jxp 4.1.0 → 4.1.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.
@@ -2,4 +2,5 @@ export declare function validateBulkOps(ops: unknown[], options?: {
2
2
  bulk_operations_allow?: string[];
3
3
  isAdmin?: boolean;
4
4
  }): void;
5
+ export declare function requiredPermsForBulkOps(ops: unknown[]): Set<string>;
5
6
  //# sourceMappingURL=bulkwrite_guard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bulkwrite_guard.d.ts","sourceRoot":"","sources":["../../src/libs/bulkwrite_guard.ts"],"names":[],"mappings":"AAgBA,wBAAgB,eAAe,CAC9B,GAAG,EAAE,OAAO,EAAE,EACd,OAAO,CAAC,EAAE;IAAE,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC/D,IAAI,CAuBN"}
1
+ {"version":3,"file":"bulkwrite_guard.d.ts","sourceRoot":"","sources":["../../src/libs/bulkwrite_guard.ts"],"names":[],"mappings":"AAgBA,wBAAgB,eAAe,CAC9B,GAAG,EAAE,OAAO,EAAE,EACd,OAAO,CAAC,EAAE;IAAE,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC/D,IAAI,CAuBN;AAuBD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAoBnE"}
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateBulkOps = validateBulkOps;
4
+ exports.requiredPermsForBulkOps = requiredPermsForBulkOps;
4
5
  const errors = require("restify-errors");
5
6
  const DEFAULT_ALLOWED_OPS = new Set([
6
7
  "insertOne",
@@ -37,8 +38,50 @@ function validateBulkOps(ops, options) {
37
38
  }
38
39
  }
39
40
  }
41
+ function permsForBulkEntry(opName, payload) {
42
+ if (!payload || typeof payload !== "object") {
43
+ throw new errors.BadRequestError("Invalid bulk write operation payload");
44
+ }
45
+ const p = payload;
46
+ switch (opName) {
47
+ case "insertOne":
48
+ return ["c"];
49
+ case "deleteOne":
50
+ case "deleteMany":
51
+ return ["d"];
52
+ case "updateOne":
53
+ case "updateMany":
54
+ case "replaceOne":
55
+ return p.upsert === true ? ["c", "u"] : ["u"];
56
+ default:
57
+ throw new errors.BadRequestError(`Bulk operation ${opName} is not allowed`);
58
+ }
59
+ }
60
+ function requiredPermsForBulkOps(ops) {
61
+ if (!Array.isArray(ops)) {
62
+ throw new errors.BadRequestError("Bulk write operations must be an array");
63
+ }
64
+ const required = new Set();
65
+ for (const op of ops) {
66
+ if (!op || typeof op !== "object") {
67
+ throw new errors.BadRequestError("Invalid bulk write operation");
68
+ }
69
+ const keys = Object.keys(op);
70
+ if (keys.length !== 1) {
71
+ throw new errors.BadRequestError("Each bulk write entry must have exactly one operation");
72
+ }
73
+ const opName = keys[0];
74
+ const perms = permsForBulkEntry(opName, op[opName]);
75
+ for (const perm of perms) {
76
+ required.add(perm);
77
+ }
78
+ }
79
+ return required;
80
+ }
40
81
  module.exports = {
41
82
  validateBulkOps,
83
+ requiredPermsForBulkOps,
84
+ permsForBulkEntry,
42
85
  DEFAULT_ALLOWED_OPS: [...DEFAULT_ALLOWED_OPS],
43
86
  };
44
87
  //# sourceMappingURL=bulkwrite_guard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"bulkwrite_guard.js","sourceRoot":"","sources":["../../src/libs/bulkwrite_guard.ts"],"names":[],"mappings":";;AAgBA,0CA0BC;AA1CD,MAAM,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAEzC,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IACnC,WAAW;IACX,WAAW;IACX,YAAY;IACZ,WAAW;CACX,CAAC,CAAC;AAEH,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC;AAE7D,SAAS,aAAa,CAAC,MAAiB;IACvC,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,mBAAmB,CAAC;IAC1D,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC;AAED,SAAgB,eAAe,CAC9B,GAAc,EACd,OAAiE;IAEjE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,MAAM,CAAC,eAAe,CAAC,wCAAwC,CAAC,CAAC;IAC5E,CAAC;IACD,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAE1C,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;QACtB,IAAI,CAAC,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;YACnC,MAAM,IAAI,MAAM,CAAC,eAAe,CAAC,8BAA8B,CAAC,CAAC;QAClE,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,EAA6B,CAAC,CAAC;QACxD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,MAAM,CAAC,eAAe,CAAC,uDAAuD,CAAC,CAAC;QAC3F,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACvB,IAAI,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAC5C,MAAM,IAAI,MAAM,CAAC,cAAc,CAAC,kBAAkB,MAAM,iBAAiB,CAAC,CAAC;QAC5E,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACzD,MAAM,IAAI,MAAM,CAAC,eAAe,CAAC,kBAAkB,MAAM,iBAAiB,CAAC,CAAC;QAC7E,CAAC;IACF,CAAC;AACF,CAAC;AAED,MAAM,CAAC,OAAO,GAAG;IAChB,eAAe;IACf,mBAAmB,EAAE,CAAC,GAAG,mBAAmB,CAAC;CAC7C,CAAC"}
1
+ {"version":3,"file":"bulkwrite_guard.js","sourceRoot":"","sources":["../../src/libs/bulkwrite_guard.ts"],"names":[],"mappings":";;AAgBA,0CA0BC;AAuBD,0DAoBC;AArFD,MAAM,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAEzC,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IACnC,WAAW;IACX,WAAW;IACX,YAAY;IACZ,WAAW;CACX,CAAC,CAAC;AAEH,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC;AAE7D,SAAS,aAAa,CAAC,MAAiB;IACvC,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,mBAAmB,CAAC;IAC1D,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC;AAED,SAAgB,eAAe,CAC9B,GAAc,EACd,OAAiE;IAEjE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,MAAM,CAAC,eAAe,CAAC,wCAAwC,CAAC,CAAC;IAC5E,CAAC;IACD,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAE1C,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;QACtB,IAAI,CAAC,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;YACnC,MAAM,IAAI,MAAM,CAAC,eAAe,CAAC,8BAA8B,CAAC,CAAC;QAClE,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,EAA6B,CAAC,CAAC;QACxD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,MAAM,CAAC,eAAe,CAAC,uDAAuD,CAAC,CAAC;QAC3F,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACvB,IAAI,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAC5C,MAAM,IAAI,MAAM,CAAC,cAAc,CAAC,kBAAkB,MAAM,iBAAiB,CAAC,CAAC;QAC5E,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACzD,MAAM,IAAI,MAAM,CAAC,eAAe,CAAC,kBAAkB,MAAM,iBAAiB,CAAC,CAAC;QAC7E,CAAC;IACF,CAAC;AACF,CAAC;AAED,SAAS,iBAAiB,CAAC,MAAc,EAAE,OAAgB;IAC1D,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC7C,MAAM,IAAI,MAAM,CAAC,eAAe,CAAC,sCAAsC,CAAC,CAAC;IAC1E,CAAC;IACD,MAAM,CAAC,GAAG,OAAkC,CAAC;IAE7C,QAAQ,MAAM,EAAE,CAAC;QAChB,KAAK,WAAW;YACf,OAAO,CAAC,GAAG,CAAC,CAAC;QACd,KAAK,WAAW,CAAC;QACjB,KAAK,YAAY;YAChB,OAAO,CAAC,GAAG,CAAC,CAAC;QACd,KAAK,WAAW,CAAC;QACjB,KAAK,YAAY,CAAC;QAClB,KAAK,YAAY;YAChB,OAAO,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAC/C;YACC,MAAM,IAAI,MAAM,CAAC,eAAe,CAAC,kBAAkB,MAAM,iBAAiB,CAAC,CAAC;IAC9E,CAAC;AACF,CAAC;AAED,SAAgB,uBAAuB,CAAC,GAAc;IACrD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,MAAM,CAAC,eAAe,CAAC,wCAAwC,CAAC,CAAC;IAC5E,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;QACtB,IAAI,CAAC,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;YACnC,MAAM,IAAI,MAAM,CAAC,eAAe,CAAC,8BAA8B,CAAC,CAAC;QAClE,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,EAA6B,CAAC,CAAC;QACxD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,MAAM,CAAC,eAAe,CAAC,uDAAuD,CAAC,CAAC;QAC3F,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACvB,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,EAAG,EAA8B,CAAC,MAAM,CAAC,CAAC,CAAC;QACjF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAC1B,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;IACF,CAAC;IACD,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,OAAO,GAAG;IAChB,eAAe;IACf,uBAAuB;IACvB,iBAAiB;IACjB,mBAAmB,EAAE,CAAC,GAAG,mBAAmB,CAAC;CAC7C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"jxp.d.ts","sourceRoot":"","sources":["../../src/libs/jxp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AA65BrD,QAAA,MAAM,GAAG,GAAa,SAAS,SAAS,QAwXvC,CAAC;AAEF,SAAS,GAAG,CAAC"}
1
+ {"version":3,"file":"jxp.d.ts","sourceRoot":"","sources":["../../src/libs/jxp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAw+BrD,QAAA,MAAM,GAAG,GAAa,SAAS,SAAS,QAgYvC,CAAC;AAEF,SAAS,GAAG,CAAC"}
package/dist/libs/jxp.js CHANGED
@@ -27,6 +27,7 @@ const call_guard = require("./call_guard");
27
27
  const response_sanitize = require("./response_sanitize");
28
28
  const link_index = require("./link_index");
29
29
  const { safeErrorMessage } = require("./safe_error");
30
+ const { logRequestError, logAndThrow } = require("./request_log");
30
31
  const schemaModule = require("./schema");
31
32
  global.JXPSchema = schemaModule.default || schemaModule;
32
33
  var models = {};
@@ -39,10 +40,12 @@ function getStripFields(req) {
39
40
  function getSecurityOpts(req) {
40
41
  return req.config?.security || {};
41
42
  }
42
- function advancedQueryAllowed(Model, kind) {
43
+ function advancedQueryAllowed(Model, kind, options) {
43
44
  const opts = Model.schema.opts;
44
45
  const aq = opts?.advanced_queries;
45
46
  if (kind === "bulkwrite") {
47
+ if (options?.isAdmin)
48
+ return true;
46
49
  return aq?.bulkwrite === true;
47
50
  }
48
51
  if (kind === "aggregate") {
@@ -50,13 +53,33 @@ function advancedQueryAllowed(Model, kind) {
50
53
  }
51
54
  return aq?.query !== false;
52
55
  }
56
+ const middlewareBulkWriteAllowed = (req, res, next) => {
57
+ if (!advancedQueryAllowed(req.Model, "bulkwrite", { isAdmin: !!res.user?.admin })) {
58
+ const err = new errors.ForbiddenError(`POST /bulkwrite is disabled for model ${req.modelname}`);
59
+ logRequestError(req, res, err, "bulkwrite disabled");
60
+ return next(err);
61
+ }
62
+ next();
63
+ };
64
+ const middlewareAdvancedQueryAllowed = (kind) => {
65
+ return (req, res, next) => {
66
+ if (!advancedQueryAllowed(req.Model, kind)) {
67
+ const err = new errors.ForbiddenError(`POST /${kind} is disabled for model ${req.modelname}`);
68
+ logRequestError(req, res, err, `${kind} disabled`);
69
+ return next(err);
70
+ }
71
+ next();
72
+ };
73
+ };
53
74
  // Middleware
54
75
  const middlewareModel = (req, res, next) => {
55
76
  const modelname = req.params.modelname;
56
77
  req.modelname = modelname;
57
78
  req.Model = models[modelname];
58
79
  if (!req.Model) {
59
- throw new errors.NotFoundError(`Model ${modelname} not found`);
80
+ const err = new errors.NotFoundError(`Model ${modelname} not found`);
81
+ logRequestError(req, res, err, "model");
82
+ return next(err);
60
83
  }
61
84
  return next();
62
85
  };
@@ -64,7 +87,9 @@ const middlewarePasswords = (req, res, next) => {
64
87
  if (req.body && req.body.password) {
65
88
  if (req.query.password_override) {
66
89
  if (!res.user?.admin) {
67
- throw new errors.ForbiddenError("password_override requires admin");
90
+ const err = new errors.ForbiddenError("password_override requires admin");
91
+ logRequestError(req, res, err, "password_override");
92
+ return next(err);
68
93
  }
69
94
  }
70
95
  else {
@@ -96,7 +121,7 @@ const outputJSON = async (req, res) => {
96
121
  res.send(res.result);
97
122
  }
98
123
  catch (err) {
99
- console.error(new Date(), err);
124
+ logRequestError(req, res, err, "outputJSON");
100
125
  throw new errors.InternalServerError(safeErrorMessage(err));
101
126
  }
102
127
  };
@@ -120,7 +145,7 @@ const outputCSV = (req, res, next) => {
120
145
  next();
121
146
  }
122
147
  catch (err) {
123
- console.error(err);
148
+ logRequestError(req, res, err, "outputCSV");
124
149
  throw new errors.InternalServerError(safeErrorMessage(err));
125
150
  }
126
151
  };
@@ -134,8 +159,7 @@ const actionGet = async (req, res) => {
134
159
  filters = query_sanitize.sanitizeFilter(filters, getSecurityOpts(req));
135
160
  }
136
161
  catch (err) {
137
- console.trace(new Date(), err);
138
- // Preserve BadRequestError, convert others to InternalServerError
162
+ logRequestError(req, res, err, "filter");
139
163
  if (err instanceof errors.BadRequestError) {
140
164
  throw err;
141
165
  }
@@ -181,7 +205,7 @@ const actionGet = async (req, res) => {
181
205
  if (count >= 0) {
182
206
  result.count = count;
183
207
  }
184
- const effectiveLimit = query_limits.enforceListLimit(req, estimatedCount);
208
+ const effectiveLimit = query_limits.enforceListLimit(req, estimatedCount, res);
185
209
  query_limits.applyListPagination(q, result, req, effectiveLimit, count >= 0 ? count : 0, changeUrlParams);
186
210
  if (req.query.sort) {
187
211
  q.sort(req.query.sort);
@@ -226,10 +250,12 @@ const actionGet = async (req, res) => {
226
250
  console.timeEnd(opname);
227
251
  }
228
252
  catch (err) {
229
- console.error(new Date(), err);
230
253
  if (debug)
231
254
  console.timeEnd(opname);
232
- // Preserve BadRequestError, convert others to InternalServerError
255
+ if (!(err instanceof errors.BadRequestError) &&
256
+ !(err instanceof errors.ForbiddenError)) {
257
+ logRequestError(req, res, err, "get");
258
+ }
233
259
  if (err instanceof errors.BadRequestError) {
234
260
  throw err;
235
261
  }
@@ -251,7 +277,7 @@ const actionGetOne = async (req, res) => {
251
277
  console.timeEnd(opname);
252
278
  }
253
279
  catch (err) {
254
- console.error(new Date(), err);
280
+ logRequestError(req, res, err, "getOne");
255
281
  if (debug)
256
282
  console.timeEnd(opname);
257
283
  if (err.code)
@@ -286,7 +312,7 @@ const actionPost = async (req, res) => {
286
312
  console.timeEnd(opname);
287
313
  }
288
314
  catch (err) {
289
- console.error(new Date(), err);
315
+ logRequestError(req, res, err, "post");
290
316
  if (debug)
291
317
  console.timeEnd(opname);
292
318
  if (err.code)
@@ -300,8 +326,7 @@ const actionPut = async (req, res) => {
300
326
  try {
301
327
  let item = await req.Model.findById(req.params.item_id);
302
328
  if (!item) {
303
- console.error(new Date(), "Document not found");
304
- throw new errors.NotFoundError(`Document ${req.params.item_id} not found on ${req.modelname}`);
329
+ logAndThrow(req, res, new errors.NotFoundError(`Document ${req.params.item_id} not found on ${req.modelname}`), "put");
305
330
  }
306
331
  _populateItem(item, datamunging.deserialize(req.body));
307
332
  _versionItem(item);
@@ -326,7 +351,7 @@ const actionPut = async (req, res) => {
326
351
  console.timeEnd(opname);
327
352
  }
328
353
  catch (err) {
329
- console.error(new Date(), err);
354
+ logRequestError(req, res, err, "put");
330
355
  if (debug)
331
356
  console.timeEnd(opname);
332
357
  if (err.code)
@@ -366,7 +391,7 @@ const actionUpdate = async (req, res) => {
366
391
  console.timeEnd(opname);
367
392
  }
368
393
  catch (err) {
369
- console.error(new Date(), err);
394
+ logRequestError(req, res, err, "update");
370
395
  if (debug)
371
396
  console.timeEnd(opname);
372
397
  if (err.code)
@@ -400,7 +425,7 @@ const actionDelete = async (req, res) => {
400
425
  }
401
426
  }
402
427
  else {
403
- throw new errors.ConflictError(`Parent link item exists in ${linked_model.modelname}/${linked_model.field}`);
428
+ logAndThrow(req, res, new errors.ConflictError(`Parent link item exists in ${linked_model.modelname}/${linked_model.field}`), "delete");
404
429
  }
405
430
  }
406
431
  });
@@ -429,7 +454,9 @@ const actionDelete = async (req, res) => {
429
454
  console.timeEnd(opname);
430
455
  }
431
456
  catch (err) {
432
- console.error(new Date(), err);
457
+ if (!(err instanceof errors.ConflictError)) {
458
+ logRequestError(req, res, err, "delete");
459
+ }
433
460
  if (debug)
434
461
  console.timeEnd(opname);
435
462
  if (err.code)
@@ -446,7 +473,7 @@ const actionCount = async (req, res) => {
446
473
  filters = query_sanitize.sanitizeFilter(filters, getSecurityOpts(req));
447
474
  }
448
475
  catch (err) {
449
- console.trace(new Date(), err);
476
+ logRequestError(req, res, err, "filter");
450
477
  if (err instanceof errors.BadRequestError || err instanceof errors.ForbiddenError) {
451
478
  throw err;
452
479
  }
@@ -466,7 +493,7 @@ const actionCount = async (req, res) => {
466
493
  console.timeEnd(opname);
467
494
  }
468
495
  catch (err) {
469
- console.error(new Date(), err);
496
+ logRequestError(req, res, err, "count");
470
497
  if (debug)
471
498
  console.timeEnd(opname);
472
499
  if (err.code)
@@ -483,7 +510,7 @@ const actionCall = async (req, res) => {
483
510
  res.json(result);
484
511
  }
485
512
  catch (err) {
486
- console.error(new Date(), err);
513
+ logRequestError(req, res, err, "call");
487
514
  if (err.code)
488
515
  throw err;
489
516
  if (err instanceof errors.ForbiddenError || err instanceof errors.NotFoundError) {
@@ -508,7 +535,7 @@ const actionCallItem = async (req, res) => {
508
535
  res.json(result);
509
536
  }
510
537
  catch (err) {
511
- console.trace(err);
538
+ logRequestError(req, res, err, "call");
512
539
  if (err.code)
513
540
  throw err;
514
541
  if (err instanceof errors.ForbiddenError || err instanceof errors.NotFoundError) {
@@ -519,15 +546,22 @@ const actionCallItem = async (req, res) => {
519
546
  };
520
547
  // Actions (verbs)
521
548
  const actionQuery = async (req, res) => {
522
- if (!advancedQueryAllowed(req.Model, "query")) {
523
- throw new errors.ForbiddenError(`POST /query is disabled for model ${req.modelname}`);
524
- }
525
549
  if (!req.body || !req.body.query || typeof req.body.query !== "object") {
526
- throw new errors.BadRequestError("Query missing or not of type object");
550
+ logAndThrow(req, res, new errors.BadRequestError("Query missing or not of type object"), "query");
527
551
  }
528
552
  const opname = `query ${req.modelname} ${ops++}`;
529
553
  console.time(opname);
530
- const sanitizedQuery = query_sanitize.sanitizeFilter(req.body.query, getSecurityOpts(req));
554
+ let sanitizedQuery;
555
+ try {
556
+ sanitizedQuery = query_sanitize.sanitizeFilter(req.body.query, getSecurityOpts(req));
557
+ }
558
+ catch (err) {
559
+ logRequestError(req, res, err, "query_sanitize");
560
+ if (err instanceof errors.BadRequestError || err instanceof errors.ForbiddenError) {
561
+ throw err;
562
+ }
563
+ throw new errors.InternalServerError(safeErrorMessage(err));
564
+ }
531
565
  let query = [sanitizedQuery];
532
566
  let checkDeleted = { "$or": [{ _deleted: false }, { _deleted: null }] };
533
567
  if (!req.query.showDeleted) {
@@ -545,7 +579,7 @@ const actionQuery = async (req, res) => {
545
579
  result.count = count;
546
580
  }
547
581
  const estimatedCount = await req.Model.estimatedDocumentCount();
548
- const effectiveLimit = query_limits.enforceListLimit(req, estimatedCount);
582
+ const effectiveLimit = query_limits.enforceListLimit(req, estimatedCount, res);
549
583
  query_limits.applyListPagination(q, result, req, effectiveLimit, count >= 0 ? count : 0, changeUrlParams);
550
584
  if (req.query.sort) {
551
585
  q.sort(req.query.sort);
@@ -587,9 +621,12 @@ const actionQuery = async (req, res) => {
587
621
  res.json(result);
588
622
  }
589
623
  catch (err) {
590
- console.error(new Date(), err);
591
624
  if (debug)
592
625
  console.timeEnd(opname);
626
+ if (!(err instanceof errors.BadRequestError) &&
627
+ !(err instanceof errors.ForbiddenError)) {
628
+ logRequestError(req, res, err, "query");
629
+ }
593
630
  if (err instanceof errors.BadRequestError || err instanceof errors.ForbiddenError) {
594
631
  throw err;
595
632
  }
@@ -600,19 +637,24 @@ const actionQuery = async (req, res) => {
600
637
  };
601
638
  // Actions (verbs)
602
639
  const actionAggregate = async (req, res) => {
603
- if (!advancedQueryAllowed(req.Model, "aggregate")) {
604
- throw new errors.ForbiddenError(`POST /aggregate is disabled for model ${req.modelname}`);
605
- }
606
- let query = (req.body.query) ? req.body.query : req.body; // Don't require to embed in query anymore
640
+ let query = req.body?.query ? req.body.query : req.body;
607
641
  if (!query || !Array.isArray(query)) {
608
- console.error("query missing or not of type array");
609
- throw new errors.BadRequestError("Query missing or not of type array");
642
+ logAndThrow(req, res, new errors.BadRequestError("Query missing or not of type array"), "aggregate");
610
643
  }
611
644
  query = query_manipulation.fix_query(query);
612
- aggregate_guard.validatePipeline(query, {
613
- aggregate_stages_allow: getSecurityOpts(req).aggregate_stages_allow,
614
- isAdmin: res.user?.admin,
615
- });
645
+ try {
646
+ aggregate_guard.validatePipeline(query, {
647
+ aggregate_stages_allow: getSecurityOpts(req).aggregate_stages_allow,
648
+ isAdmin: res.user?.admin,
649
+ });
650
+ }
651
+ catch (err) {
652
+ logRequestError(req, res, err, "aggregate_guard");
653
+ if (err instanceof errors.BadRequestError || err instanceof errors.ForbiddenError) {
654
+ throw err;
655
+ }
656
+ throw new errors.InternalServerError(safeErrorMessage(err));
657
+ }
616
658
  const opname = `aggregate ${req.modelname} ${ops++}`;
617
659
  console.time(opname);
618
660
  try {
@@ -630,9 +672,12 @@ const actionAggregate = async (req, res) => {
630
672
  res.json(result);
631
673
  }
632
674
  catch (err) {
633
- console.error(new Date(), err);
634
675
  if (debug)
635
676
  console.timeEnd(opname);
677
+ if (!(err instanceof errors.BadRequestError) &&
678
+ !(err instanceof errors.ForbiddenError)) {
679
+ logRequestError(req, res, err, "aggregate");
680
+ }
636
681
  if (err instanceof errors.BadRequestError || err instanceof errors.ForbiddenError) {
637
682
  throw err;
638
683
  }
@@ -641,12 +686,10 @@ const actionAggregate = async (req, res) => {
641
686
  };
642
687
  // Actions (verbs)
643
688
  const actionBulkWrite = async (req, res) => {
644
- if (!advancedQueryAllowed(req.Model, "bulkwrite")) {
645
- throw new errors.ForbiddenError(`POST /bulkwrite is disabled for model ${req.modelname}`);
646
- }
647
689
  if (!req.body || !Array.isArray(req.body)) {
648
- console.error("query missing or not of type array");
649
- throw new errors.BadRequestError("Query missing or not of type array");
690
+ const err = new errors.BadRequestError("Query missing or not of type array");
691
+ logRequestError(req, res, err, "bulkwrite");
692
+ throw err;
650
693
  }
651
694
  const opname = `bulkwrite ${req.modelname} ${ops++}`;
652
695
  console.time(opname);
@@ -664,7 +707,7 @@ const actionBulkWrite = async (req, res) => {
664
707
  res.json(result);
665
708
  }
666
709
  catch (err) {
667
- console.error(new Date(), err);
710
+ logRequestError(req, res, err, "bulkwrite");
668
711
  if (debug)
669
712
  console.timeEnd(opname);
670
713
  if (err instanceof errors.BadRequestError || err instanceof errors.ForbiddenError) {
@@ -736,7 +779,6 @@ const getOne = async (Model, item_id, params, options) => {
736
779
  return response_sanitize.sanitizeDocument(item);
737
780
  }
738
781
  catch (err) {
739
- console.error(err);
740
782
  if (err.code)
741
783
  throw err;
742
784
  throw new errors.InternalServerError(safeErrorMessage(err));
@@ -1114,9 +1156,9 @@ const JXP = function (options) {
1114
1156
  // CSV endpoints
1115
1157
  server.get("/csv/:modelname", middlewareModel, security.login, security.auth, config.pre_hooks.get, actionGet, outputCSV);
1116
1158
  // Query endpoints
1117
- server.post("/query/:modelname", middlewareModel, security.login, security.auth, config.pre_hooks.get, actionQuery);
1118
- server.post("/aggregate/:modelname", middlewareModel, security.login, security.auth, config.pre_hooks.get, actionAggregate);
1119
- server.post("/bulkwrite/:modelname", middlewareModel, security.login, security.bulkAuth, config.pre_hooks.get, actionBulkWrite);
1159
+ server.post("/query/:modelname", middlewareModel, security.login, security.auth, middlewareAdvancedQueryAllowed("query"), config.pre_hooks.get, actionQuery);
1160
+ server.post("/aggregate/:modelname", middlewareModel, security.login, security.auth, middlewareAdvancedQueryAllowed("aggregate"), config.pre_hooks.get, actionAggregate);
1161
+ server.post("/bulkwrite/:modelname", middlewareModel, security.login, middlewareBulkWriteAllowed, security.bulkAuth, config.pre_hooks.get, actionBulkWrite);
1120
1162
  server.post("/update/:modelname/:item_id", middlewareModel, security.login, security.auth, middlewarePasswords, middlewareCheckAdmin, config.pre_hooks.update, actionUpdate, cache.clearAll);
1121
1163
  /* Batch routes - ROLLED BACK FOR NOW */
1122
1164
  // server.post('/batch/create/:modelname', middlewareModel, security.login, security.auth, actionBatch);
@@ -1167,6 +1209,10 @@ const JXP = function (options) {
1167
1209
  server.post("/setup/data", setup.checkUserDoesNotExist, setup.data_setup);
1168
1210
  /* Websocket */
1169
1211
  server.on("upgrade", ws.upgrade);
1212
+ server.on("restError", (req, res, err, callback) => {
1213
+ logRequestError(req, res, err);
1214
+ return callback();
1215
+ });
1170
1216
  /* Cache */
1171
1217
  server.get("/cache/stats", security.login, security.admin_only, cache.stats, outputJSON);
1172
1218
  server.get("/cache/clear", security.login, security.admin_only, cache.clearAll, outputJSON);