vanta-api 1.4.3 → 1.4.5
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 +1 -1
- package/src/api-features.js +81 -47
package/package.json
CHANGED
package/src/api-features.js
CHANGED
|
@@ -24,7 +24,6 @@ export class ApiFeatures {
|
|
|
24
24
|
this.pipeline = [];
|
|
25
25
|
this.manualFilters = {};
|
|
26
26
|
this.useCursor = false;
|
|
27
|
-
|
|
28
27
|
this.userRole =
|
|
29
28
|
userRole && securityConfig.accessLevels?.[userRole] ? userRole : "guest";
|
|
30
29
|
|
|
@@ -33,10 +32,16 @@ export class ApiFeatures {
|
|
|
33
32
|
|
|
34
33
|
filter() {
|
|
35
34
|
const queryFilters = this._parseQueryFilters();
|
|
35
|
+
|
|
36
|
+
const normalizedManualFilters = this._normalizeLogicalOperators(
|
|
37
|
+
this.manualFilters
|
|
38
|
+
);
|
|
39
|
+
|
|
36
40
|
const mergedFilters = this._deepMergeFilters(
|
|
37
41
|
queryFilters,
|
|
38
|
-
|
|
42
|
+
normalizedManualFilters
|
|
39
43
|
);
|
|
44
|
+
|
|
40
45
|
const sanitizedFilters = this._sanitizeFilters(mergedFilters);
|
|
41
46
|
const safeFilters = this._applySecurityFilters(sanitizedFilters);
|
|
42
47
|
|
|
@@ -49,7 +54,12 @@ export class ApiFeatures {
|
|
|
49
54
|
|
|
50
55
|
addManualFilters(filters = {}) {
|
|
51
56
|
if (filters && typeof filters === "object" && !Array.isArray(filters)) {
|
|
52
|
-
|
|
57
|
+
const normalizedFilters = this._normalizeLogicalOperators(filters);
|
|
58
|
+
|
|
59
|
+
this.manualFilters = this._deepMergeFilters(
|
|
60
|
+
this.manualFilters,
|
|
61
|
+
normalizedFilters
|
|
62
|
+
);
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
return this;
|
|
@@ -57,11 +67,9 @@ export class ApiFeatures {
|
|
|
57
67
|
|
|
58
68
|
search(fields = []) {
|
|
59
69
|
const q = this.query.q;
|
|
60
|
-
|
|
61
70
|
if (!q || !Array.isArray(fields) || !fields.length) return this;
|
|
62
71
|
|
|
63
72
|
const safeQ = this._escapeRegex(String(q).trim());
|
|
64
|
-
|
|
65
73
|
if (!safeQ) return this;
|
|
66
74
|
|
|
67
75
|
const conditions = fields
|
|
@@ -109,7 +117,6 @@ export class ApiFeatures {
|
|
|
109
117
|
|
|
110
118
|
limitFields(input = "") {
|
|
111
119
|
const rawFields = [input, this.query.fields].filter(Boolean).join(",");
|
|
112
|
-
|
|
113
120
|
if (!rawFields) return this;
|
|
114
121
|
|
|
115
122
|
const fields = rawFields
|
|
@@ -128,9 +135,7 @@ export class ApiFeatures {
|
|
|
128
135
|
|
|
129
136
|
for (const field of fields) {
|
|
130
137
|
const cleanField = field.replace(/^-/, "");
|
|
131
|
-
|
|
132
138
|
if (this._isForbiddenField(cleanField)) continue;
|
|
133
|
-
|
|
134
139
|
project[cleanField] = field.startsWith("-") ? 0 : 1;
|
|
135
140
|
}
|
|
136
141
|
|
|
@@ -513,13 +518,13 @@ export class ApiFeatures {
|
|
|
513
518
|
|
|
514
519
|
_applyPopulateSelect({ path, select, isArray }) {
|
|
515
520
|
const parsed = this._parseSelect(select);
|
|
516
|
-
|
|
517
521
|
if (!parsed.fields.length) return;
|
|
518
522
|
|
|
519
523
|
if (parsed.mode === "exclude") {
|
|
520
524
|
this.pipeline.push({
|
|
521
525
|
$unset: parsed.fields.map((field) => `${path}.${field}`),
|
|
522
526
|
});
|
|
527
|
+
|
|
523
528
|
return;
|
|
524
529
|
}
|
|
525
530
|
|
|
@@ -564,7 +569,6 @@ export class ApiFeatures {
|
|
|
564
569
|
|
|
565
570
|
_applyNestedObjectSelectInsideArray({ arrayPath, objectPath, select }) {
|
|
566
571
|
const parsed = this._parseSelect(select);
|
|
567
|
-
|
|
568
572
|
if (!parsed.fields.length) return;
|
|
569
573
|
|
|
570
574
|
if (parsed.mode === "exclude") {
|
|
@@ -573,6 +577,7 @@ export class ApiFeatures {
|
|
|
573
577
|
(field) => `${arrayPath}.${objectPath}.${field}`
|
|
574
578
|
),
|
|
575
579
|
});
|
|
580
|
+
|
|
576
581
|
return;
|
|
577
582
|
}
|
|
578
583
|
|
|
@@ -658,7 +663,6 @@ export class ApiFeatures {
|
|
|
658
663
|
|
|
659
664
|
_parseQueryFilters() {
|
|
660
665
|
const obj = { ...this.query };
|
|
661
|
-
|
|
662
666
|
RESERVED_QUERY_KEYS.forEach((key) => delete obj[key]);
|
|
663
667
|
|
|
664
668
|
const out = {};
|
|
@@ -704,50 +708,47 @@ export class ApiFeatures {
|
|
|
704
708
|
return out;
|
|
705
709
|
}
|
|
706
710
|
|
|
707
|
-
_sanitizeFilters(filters = {}) {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
) {
|
|
713
|
-
return node;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
if (node === null || node === "null") return null;
|
|
717
|
-
if (node === "true") return true;
|
|
718
|
-
if (node === "false") return false;
|
|
719
|
-
|
|
720
|
-
if (Array.isArray(node)) {
|
|
721
|
-
return node.map((item) => sanitizeNode(item, key));
|
|
722
|
-
}
|
|
711
|
+
_sanitizeFilters(filters = {}) {
|
|
712
|
+
const sanitizeNode = (node, key = "") => {
|
|
713
|
+
if (node instanceof mongoose.Types.ObjectId || node instanceof ObjectId) {
|
|
714
|
+
return node;
|
|
715
|
+
}
|
|
723
716
|
|
|
724
|
-
|
|
725
|
-
|
|
717
|
+
if (node === null || node === "null") return null;
|
|
718
|
+
if (node === "true") return true;
|
|
719
|
+
if (node === "false") return false;
|
|
726
720
|
|
|
727
|
-
|
|
728
|
-
|
|
721
|
+
if (Array.isArray(node)) {
|
|
722
|
+
return node.map((item) => sanitizeNode(item, key));
|
|
729
723
|
}
|
|
730
724
|
|
|
731
|
-
|
|
732
|
-
|
|
725
|
+
if (node && typeof node === "object") {
|
|
726
|
+
const result = {};
|
|
727
|
+
|
|
728
|
+
for (const [childKey, childVal] of Object.entries(node)) {
|
|
729
|
+
result[childKey] = sanitizeNode(childVal, childKey);
|
|
730
|
+
}
|
|
733
731
|
|
|
734
|
-
|
|
735
|
-
if (this.#isStrictObjectId(node) && this._shouldConvertToObjectId(key)) {
|
|
736
|
-
return new ObjectId(node);
|
|
732
|
+
return result;
|
|
737
733
|
}
|
|
738
734
|
|
|
739
|
-
if (
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
735
|
+
if (typeof node === "string") {
|
|
736
|
+
if (this.#isStrictObjectId(node) && this._shouldConvertToObjectId(key)) {
|
|
737
|
+
return new ObjectId(node);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (/^[0-9]+$/.test(node)) {
|
|
741
|
+
return node.length > 1 && node.startsWith("0")
|
|
742
|
+
? node
|
|
743
|
+
: parseInt(node, 10);
|
|
744
|
+
}
|
|
743
745
|
}
|
|
744
|
-
}
|
|
745
746
|
|
|
746
|
-
|
|
747
|
-
|
|
747
|
+
return node;
|
|
748
|
+
};
|
|
748
749
|
|
|
749
|
-
|
|
750
|
-
}
|
|
750
|
+
return sanitizeNode(filters);
|
|
751
|
+
}
|
|
751
752
|
|
|
752
753
|
_shouldConvertToObjectId(key = "") {
|
|
753
754
|
const cleanKey = String(key).replace(/^\$/, "").toLowerCase();
|
|
@@ -763,6 +764,39 @@ _sanitizeFilters(filters = {}) {
|
|
|
763
764
|
);
|
|
764
765
|
}
|
|
765
766
|
|
|
767
|
+
_normalizeLogicalOperators(filters = {}) {
|
|
768
|
+
if (Array.isArray(filters)) {
|
|
769
|
+
return filters.map((item) => this._normalizeLogicalOperators(item));
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (
|
|
773
|
+
!filters ||
|
|
774
|
+
typeof filters !== "object" ||
|
|
775
|
+
filters instanceof mongoose.Types.ObjectId ||
|
|
776
|
+
filters instanceof ObjectId ||
|
|
777
|
+
filters instanceof Date
|
|
778
|
+
) {
|
|
779
|
+
return filters;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const out = {};
|
|
783
|
+
|
|
784
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
785
|
+
const normalizedKey =
|
|
786
|
+
key === "and"
|
|
787
|
+
? "$and"
|
|
788
|
+
: key === "or"
|
|
789
|
+
? "$or"
|
|
790
|
+
: key === "nor"
|
|
791
|
+
? "$nor"
|
|
792
|
+
: key;
|
|
793
|
+
|
|
794
|
+
out[normalizedKey] = this._normalizeLogicalOperators(value);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
return out;
|
|
798
|
+
}
|
|
799
|
+
|
|
766
800
|
_deepMergeFilters(a = {}, b = {}) {
|
|
767
801
|
const out = { ...a };
|
|
768
802
|
|
|
@@ -799,7 +833,6 @@ _sanitizeFilters(filters = {}) {
|
|
|
799
833
|
|
|
800
834
|
for (const [key, value] of Object.entries(node)) {
|
|
801
835
|
if (this._isForbiddenField(key)) continue;
|
|
802
|
-
|
|
803
836
|
result[key] = cleanNode(value);
|
|
804
837
|
}
|
|
805
838
|
|
|
@@ -828,7 +861,6 @@ _sanitizeFilters(filters = {}) {
|
|
|
828
861
|
|
|
829
862
|
if (typeof item === "string") {
|
|
830
863
|
const trimmed = item.trim();
|
|
831
|
-
|
|
832
864
|
if (!trimmed) return;
|
|
833
865
|
|
|
834
866
|
if (trimmed.includes(".")) {
|
|
@@ -884,9 +916,11 @@ _sanitizeFilters(filters = {}) {
|
|
|
884
916
|
return input
|
|
885
917
|
.flatMap((item) => {
|
|
886
918
|
if (typeof item === "string") return this._normalizePopulateInput(item);
|
|
919
|
+
|
|
887
920
|
if (item && typeof item === "object" && item.path) {
|
|
888
921
|
return [this._normalizePopulateObject(item)];
|
|
889
922
|
}
|
|
923
|
+
|
|
890
924
|
return [];
|
|
891
925
|
})
|
|
892
926
|
.filter(Boolean);
|