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.
- package/dist/libs/bulkwrite_guard.d.ts +1 -0
- package/dist/libs/bulkwrite_guard.d.ts.map +1 -1
- package/dist/libs/bulkwrite_guard.js +43 -0
- package/dist/libs/bulkwrite_guard.js.map +1 -1
- package/dist/libs/jxp.d.ts.map +1 -1
- package/dist/libs/jxp.js +96 -50
- package/dist/libs/jxp.js.map +1 -1
- package/dist/libs/query_limits.js +9 -3
- package/dist/libs/query_limits.js.map +1 -1
- package/dist/libs/request_log.d.ts +44 -0
- package/dist/libs/request_log.d.ts.map +1 -0
- package/dist/libs/request_log.js +229 -0
- package/dist/libs/request_log.js.map +1 -0
- package/dist/libs/security.js +18 -9
- package/dist/libs/security.js.map +1 -1
- package/dist/models/test_model.js +1 -1
- package/dist/models/test_model.js.map +1 -1
- package/docs/bulk_writes.md +9 -2
- package/docs/changelog.md +55 -1
- package/package.json +2 -2
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"bulkwrite_guard.js","sourceRoot":"","sources":["../../src/libs/bulkwrite_guard.ts"],"names":[],"mappings":"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"}
|
package/dist/libs/jxp.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
-
|
|
649
|
-
|
|
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
|
-
|
|
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);
|