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.
Files changed (2) hide show
  1. package/package.json +2 -1
  2. 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.6",
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"
@@ -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 manual = this._sanitizeFilters(this.manualFilters);
37
- const merged = { ...queryFilters, ...manual };
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 === 'string' ? opt.toLowerCase() : opt.path.toLowerCase();
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
- const obj = { ...this.query };
220
- // پاک کردن پارامترهای سیستماتیک
221
- ["page", "limit", "sort", "fields", "populate"].forEach(k => delete obj[k]);
222
-
223
- const out = {};
224
-
225
- for (const [rawKey, rawVal] of Object.entries(obj)) {
226
- if (typeof rawVal === 'object' && !Array.isArray(rawVal)) {
227
- out[rawKey] = {};
228
- for (let [op, val] of Object.entries(rawVal)) {
229
- const cleanOp = op.replace(/^\$/, '');
230
- if (securityConfig.allowedOperators.includes(cleanOp)) {
231
- const v = /^[0-9]+$/.test(val) ? parseInt(val, 10) : val;
232
- out[rawKey][`$${cleanOp}`] = v;
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
- out[rawKey] = rawVal;
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
- // Simple deep clone with ObjectId and boolean parsing
258
- return JSON.parse(JSON.stringify(filters), (key, val) => {
259
- if (key.endsWith("Id") && mongoose.isValidObjectId(val))
260
- return new mongoose.Types.ObjectId(val);
261
- if (val === "true") return true;
262
- if (val === "false") return false;
263
- if (/^[0-9]+$/.test(val)) return parseInt(val, 10);
264
- return val;
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) {