vanta-api 1.1.6 → 1.1.8
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/package.json +2 -1
- package/src/api-features.js +80 -43
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vanta-api",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8",
|
|
4
4
|
"description": "Advanced API features and security configuration for Node.js/MongoDB.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"author": "Alireza Aghaee",
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
|
+
"bson": "^6.10.3",
|
|
22
23
|
"mongoose": "^7.0.0",
|
|
23
24
|
"pluralize": "^8.0.0",
|
|
24
25
|
"winston": "^3.0.0"
|
package/src/api-features.js
CHANGED
|
@@ -33,8 +33,10 @@ export class ApiFeatures {
|
|
|
33
33
|
filter() {
|
|
34
34
|
// Parse and sanitize both query and manual filters
|
|
35
35
|
const queryFilters = this._parseQueryFilters();
|
|
36
|
-
const
|
|
37
|
-
|
|
36
|
+
const merged = this._sanitizeFilters({
|
|
37
|
+
...queryFilters,
|
|
38
|
+
...this.manualFilters,
|
|
39
|
+
});
|
|
38
40
|
const safe = this._applySecurityFilters(merged);
|
|
39
41
|
|
|
40
42
|
if (Object.keys(safe).length) {
|
|
@@ -117,7 +119,7 @@ export class ApiFeatures {
|
|
|
117
119
|
|
|
118
120
|
// Apply lookups
|
|
119
121
|
for (const opt of final) {
|
|
120
|
-
const field = typeof opt ===
|
|
122
|
+
const field = typeof opt === "string" ? opt : opt.path;
|
|
121
123
|
const proj =
|
|
122
124
|
typeof opt === "object" && opt.select
|
|
123
125
|
? opt.select.split(" ").reduce((a, f) => {
|
|
@@ -215,54 +217,89 @@ export class ApiFeatures {
|
|
|
215
217
|
});
|
|
216
218
|
}
|
|
217
219
|
|
|
218
|
-
_parseQueryFilters() {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
220
|
+
_parseQueryFilters() {
|
|
221
|
+
const obj = { ...this.query };
|
|
222
|
+
// پاک کردن پارامترهای سیستماتیک
|
|
223
|
+
["page", "limit", "sort", "fields", "populate"].forEach(
|
|
224
|
+
(k) => delete obj[k]
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const out = {};
|
|
228
|
+
|
|
229
|
+
for (const [rawKey, rawVal] of Object.entries(obj)) {
|
|
230
|
+
if (typeof rawVal === "object" && !Array.isArray(rawVal)) {
|
|
231
|
+
out[rawKey] = {};
|
|
232
|
+
for (let [op, val] of Object.entries(rawVal)) {
|
|
233
|
+
const cleanOp = op.replace(/^\$/, "");
|
|
234
|
+
if (securityConfig.allowedOperators.includes(cleanOp)) {
|
|
235
|
+
const v = /^[0-9]+$/.test(val) ? parseInt(val, 10) : val;
|
|
236
|
+
out[rawKey][`$${cleanOp}`] = v;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
} else if (/^\w+\[\$?\w+\]$/.test(rawKey)) {
|
|
240
|
+
const [, field, op] = rawKey.match(/^(\w+)\[\$?(\w+)\]$/);
|
|
241
|
+
if (securityConfig.allowedOperators.includes(op)) {
|
|
242
|
+
const v = /^[0-9]+$/.test(rawVal) ? parseInt(rawVal, 10) : rawVal;
|
|
243
|
+
out[field] = { [`$${op}`]: v };
|
|
233
244
|
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
else if (/^\w+\[\$?\w+\]$/.test(rawKey)) {
|
|
237
|
-
const [, field, op] = rawKey.match(/^(\w+)\[\$?(\w+)\]$/);
|
|
238
|
-
if (securityConfig.allowedOperators.includes(op)) {
|
|
239
|
-
const v = /^[0-9]+$/.test(rawVal) ? parseInt(rawVal, 10) : rawVal;
|
|
240
|
-
out[field] = { [`$${op}`]: v };
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
if (typeof rawVal === "string" && rawVal.includes(",")) {
|
|
245
|
-
out[rawKey] = rawVal.split(",");
|
|
246
245
|
} else {
|
|
247
|
-
|
|
246
|
+
if (typeof rawVal === "string" && rawVal.includes(",")) {
|
|
247
|
+
out[rawKey] = rawVal.split(",");
|
|
248
|
+
} else {
|
|
249
|
+
out[rawKey] = rawVal;
|
|
250
|
+
}
|
|
248
251
|
}
|
|
249
252
|
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return out;
|
|
253
|
-
}
|
|
254
253
|
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
255
256
|
|
|
256
257
|
_sanitizeFilters(filters) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
258
|
+
const resultObj = {};
|
|
259
|
+
const resualt = Object.entries(filters).map((el) => {
|
|
260
|
+
const [keyObj, val] = el;
|
|
261
|
+
if (
|
|
262
|
+
typeof val === "object" &&
|
|
263
|
+
(this.#isStrictObjectId(val["$eq"]) ||
|
|
264
|
+
this.#isStrictObjectId(val["eq"]))
|
|
265
|
+
) {
|
|
266
|
+
const newVal = { ...val };
|
|
267
|
+
if (this.#isStrictObjectId(val["$eq"])) {
|
|
268
|
+
newVal["$eq"] = new ObjectId(val["$eq"]);
|
|
269
|
+
}
|
|
270
|
+
if (this.#isStrictObjectId(val["eq"])) {
|
|
271
|
+
newVal["eq"] = new ObjectId(val["eq"]);
|
|
272
|
+
}
|
|
273
|
+
resultObj[keyObj] = newVal;
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (val === "true") {
|
|
277
|
+
resultObj[keyObj] = true;
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (val === "false") {
|
|
281
|
+
resultObj[keyObj] = false;
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (typeof val === "string" && /^[0-9]+$/.test(val)) {
|
|
285
|
+
resultObj[keyObj] = parseInt(val, 10);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (typeof val === "string" && this.#isStrictObjectId(val)) {
|
|
289
|
+
resultObj[keyObj] = new ObjectId(val);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
resultObj[keyObj] = val;
|
|
265
293
|
});
|
|
294
|
+
return resultObj;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
#isStrictObjectId(id) {
|
|
298
|
+
return (
|
|
299
|
+
typeof id === "string" &&
|
|
300
|
+
mongoose.Types.ObjectId.isValid(id) &&
|
|
301
|
+
new mongoose.Types.ObjectId(id).toString() === id
|
|
302
|
+
);
|
|
266
303
|
}
|
|
267
304
|
|
|
268
305
|
_applySecurityFilters(filters) {
|