spice-js 2.6.6 → 2.6.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/build/bootstrap/cache.js +5 -1
- package/build/models/SpiceModel.js +115 -44
- package/build/utility/Security.js +51 -0
- package/package.json +1 -2
- package/src/bootstrap/cache.js +8 -1
- package/src/models/SpiceModel.js +151 -81
- package/src/utility/Security.js +49 -0
package/build/bootstrap/cache.js
CHANGED
|
@@ -16,7 +16,11 @@ function _connect() {
|
|
|
16
16
|
|
|
17
17
|
for (var key of Object.keys(spice.config.cache.providers)) {
|
|
18
18
|
spice.cache_providers[key] = new spice.config.cache.providers[key](spice.config.cache.drivers[key] || {});
|
|
19
|
-
spice.cache_providers[key].initialize()
|
|
19
|
+
spice.cache_providers[key].initialize().then(() => {
|
|
20
|
+
console.log("Redis initialized successfully");
|
|
21
|
+
}).catch(err => {
|
|
22
|
+
console.error("Error initializing Redis:", err);
|
|
23
|
+
});
|
|
20
24
|
}
|
|
21
25
|
} catch (e) {
|
|
22
26
|
console.log(e.stack);
|
|
@@ -7,6 +7,8 @@ var _2 = require("..");
|
|
|
7
7
|
|
|
8
8
|
var _ResourceLifecycleTriggered = _interopRequireDefault(require("../events/events/ResourceLifecycleTriggered"));
|
|
9
9
|
|
|
10
|
+
var _Security = require("../utility/Security");
|
|
11
|
+
|
|
10
12
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
11
13
|
|
|
12
14
|
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
@@ -29,6 +31,7 @@ var SDate = require("sonover-date"),
|
|
|
29
31
|
_hooks = Symbol(),
|
|
30
32
|
_disable_lifecycle_events = Symbol(),
|
|
31
33
|
_external_modifier_loaded = Symbol(),
|
|
34
|
+
_skip_cache = Symbol(),
|
|
32
35
|
_serializers = Symbol(); //const _type = Symbol("type");
|
|
33
36
|
|
|
34
37
|
|
|
@@ -51,7 +54,7 @@ class SpiceModel {
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
try {
|
|
54
|
-
var _args$args, _args2, _args2$args;
|
|
57
|
+
var _args$args, _args2, _args2$args, _args3, _args3$args;
|
|
55
58
|
|
|
56
59
|
var dbtype = spice.config.database.connections[args.connection].type || "couchbase";
|
|
57
60
|
|
|
@@ -63,6 +66,7 @@ class SpiceModel {
|
|
|
63
66
|
this[_external_modifier_loaded] = false;
|
|
64
67
|
this[_disable_lifecycle_events] = ((_args$args = args.args) == null ? void 0 : _args$args.disable_lifecycle_events) || false;
|
|
65
68
|
this[_ctx] = (_args2 = args) == null ? void 0 : (_args2$args = _args2.args) == null ? void 0 : _args2$args.ctx;
|
|
69
|
+
this[_skip_cache] = ((_args3 = args) == null ? void 0 : (_args3$args = _args3.args) == null ? void 0 : _args3$args.skip_cache) || false;
|
|
66
70
|
this[_hooks] = {
|
|
67
71
|
create: {
|
|
68
72
|
before: [],
|
|
@@ -432,10 +436,23 @@ class SpiceModel {
|
|
|
432
436
|
return return_string;
|
|
433
437
|
}
|
|
434
438
|
|
|
435
|
-
|
|
436
|
-
var _spice$
|
|
439
|
+
shouldUseCache(resource_type) {
|
|
440
|
+
var _spice$config$cache;
|
|
441
|
+
|
|
442
|
+
// If '_skip_cache' property of this object is true, then we shouldn't cache.
|
|
443
|
+
if (this[_skip_cache] == true) {
|
|
444
|
+
return false;
|
|
445
|
+
} // If the system configuration for spice has a cache status set to "disable",
|
|
446
|
+
// then we shouldn't cache, so return false.
|
|
447
|
+
|
|
437
448
|
|
|
438
|
-
|
|
449
|
+
if (((_spice$config$cache = spice.config.cache) == null ? void 0 : _spice$config$cache.status) == "disabled") {
|
|
450
|
+
return false;
|
|
451
|
+
} // If 'spice.cache[resource_type]' is not undefined,
|
|
452
|
+
// it implies that this resource type is already in the cache or is cacheable.
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
return spice.cache[resource_type] != undefined;
|
|
439
456
|
}
|
|
440
457
|
|
|
441
458
|
getCacheConfig(resource_type) {
|
|
@@ -480,11 +497,13 @@ class SpiceModel {
|
|
|
480
497
|
setMonitor() {
|
|
481
498
|
var current_time = new Date().getTime();
|
|
482
499
|
var obj = this.getCacheProviderObject();
|
|
483
|
-
|
|
500
|
+
var key = "monitor::" + this.type;
|
|
501
|
+
var value = {
|
|
484
502
|
time: current_time
|
|
485
|
-
}
|
|
503
|
+
};
|
|
504
|
+
obj.set(key, value, {
|
|
486
505
|
ttl: 0
|
|
487
|
-
});
|
|
506
|
+
});
|
|
488
507
|
}
|
|
489
508
|
|
|
490
509
|
get(args) {
|
|
@@ -497,11 +516,14 @@ class SpiceModel {
|
|
|
497
516
|
}
|
|
498
517
|
|
|
499
518
|
if (_.isString(args.id)) {
|
|
500
|
-
|
|
519
|
+
if (args.skip_hooks != true) {
|
|
520
|
+
yield _this4.run_hook(args, "get", "before");
|
|
521
|
+
}
|
|
522
|
+
|
|
501
523
|
var key = _this4.type + "::" + args.id;
|
|
502
524
|
var results = {};
|
|
503
525
|
|
|
504
|
-
if (_this4.
|
|
526
|
+
if (_this4.shouldUseCache(_this4.type)) {
|
|
505
527
|
var cached_results = yield _this4.getCacheProviderObject(_this4.type).get(key);
|
|
506
528
|
results = cached_results == null ? void 0 : cached_results.value;
|
|
507
529
|
|
|
@@ -525,8 +547,14 @@ class SpiceModel {
|
|
|
525
547
|
}
|
|
526
548
|
|
|
527
549
|
if (results.deleted == undefined || results.deleted == false) {
|
|
528
|
-
|
|
529
|
-
|
|
550
|
+
if (args.skip_read_serialize != true && args.skip_serialize != true) {
|
|
551
|
+
results = yield _this4.do_serialize(results, "read", {}, args, (yield _this4.propsToBeRemoved(results)));
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (args.skip_hooks != true) {
|
|
555
|
+
yield _this4.run_hook(results, "get", "after");
|
|
556
|
+
}
|
|
557
|
+
|
|
530
558
|
return results;
|
|
531
559
|
} else {
|
|
532
560
|
throw new Error(_this4.type + " does not exist");
|
|
@@ -560,7 +588,9 @@ class SpiceModel {
|
|
|
560
588
|
args = {};
|
|
561
589
|
}
|
|
562
590
|
|
|
563
|
-
|
|
591
|
+
if (args.skip_hooks != true) {
|
|
592
|
+
yield _this6.run_hook(_this6, "list", "before");
|
|
593
|
+
}
|
|
564
594
|
|
|
565
595
|
_.remove(args.ids, o => o == undefined);
|
|
566
596
|
|
|
@@ -569,7 +599,7 @@ class SpiceModel {
|
|
|
569
599
|
var results = [];
|
|
570
600
|
|
|
571
601
|
if (args.ids.length > 0) {
|
|
572
|
-
if (_this6.
|
|
602
|
+
if (_this6.shouldUseCache(_this6.type)) {
|
|
573
603
|
var cached_results = yield _this6.getCacheProviderObject(_this6.type).get(key);
|
|
574
604
|
results = cached_results == null ? void 0 : cached_results.value;
|
|
575
605
|
|
|
@@ -588,8 +618,14 @@ class SpiceModel {
|
|
|
588
618
|
|
|
589
619
|
_.remove(results, o => o.type != _this6.type);
|
|
590
620
|
|
|
591
|
-
|
|
592
|
-
|
|
621
|
+
if (args.skip_read_serialize != true && args.skip_serialize != true) {
|
|
622
|
+
results = yield _this6.do_serialize(results, "read", {}, args, (yield _this6.propsToBeRemoved(results)));
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (args.skip_hooks != true) {
|
|
626
|
+
yield _this6.run_hook(results, "list", "after", args.context);
|
|
627
|
+
}
|
|
628
|
+
|
|
593
629
|
return results;
|
|
594
630
|
} catch (e) {
|
|
595
631
|
console.log(e.stack);
|
|
@@ -649,34 +685,33 @@ class SpiceModel {
|
|
|
649
685
|
new: _this8,
|
|
650
686
|
id: args.id
|
|
651
687
|
};
|
|
652
|
-
|
|
653
|
-
if (args.skip_hooks != true) {
|
|
654
|
-
yield _this8.run_hook(cover_obj, "update", "before", results);
|
|
655
|
-
}
|
|
656
|
-
|
|
657
688
|
var form;
|
|
658
689
|
|
|
659
690
|
if (args.skip_write_serialize != true && args.skip_serialize != true) {
|
|
660
|
-
|
|
691
|
+
cover_obj.new = yield _this8.do_serialize(_this8, "write", cover_obj.new, args);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (args.skip_hooks != true) {
|
|
695
|
+
yield _this8.run_hook(cover_obj, "update", "before", results);
|
|
661
696
|
}
|
|
662
697
|
|
|
663
|
-
var db_data =
|
|
698
|
+
var db_data = cover_obj.new || _this8;
|
|
664
699
|
yield _this8.database.update(args.id, db_data);
|
|
665
700
|
|
|
666
701
|
_this8.setMonitor();
|
|
667
702
|
|
|
703
|
+
if (args.skip_read_serialize != true && args.skip_serialize != true) {
|
|
704
|
+
cover_obj.new = yield _this8.do_serialize(cover_obj.new, "read", {}, args, (yield _this8.propsToBeRemoved(cover_obj.new)));
|
|
705
|
+
}
|
|
706
|
+
|
|
668
707
|
if (args.skip_hooks != true) {
|
|
669
708
|
yield _this8.run_hook(_extends({}, _this8, {
|
|
670
709
|
id: args.id
|
|
671
710
|
}), "update", "after", results);
|
|
672
711
|
}
|
|
673
712
|
|
|
674
|
-
if (args.skip_read_serialize != true && args.skip_serialize != true) {
|
|
675
|
-
form = yield _this8.do_serialize(form, "read", {}, args, (yield _this8.propsToBeRemoved(form)));
|
|
676
|
-
}
|
|
677
|
-
|
|
678
713
|
_this8.id = args.id;
|
|
679
|
-
return _extends({},
|
|
714
|
+
return _extends({}, cover_obj.new, {
|
|
680
715
|
id: args.id
|
|
681
716
|
});
|
|
682
717
|
} catch (e) {
|
|
@@ -709,16 +744,28 @@ class SpiceModel {
|
|
|
709
744
|
id = args.id;
|
|
710
745
|
}
|
|
711
746
|
|
|
712
|
-
|
|
713
|
-
|
|
747
|
+
if (args.skip_write_serialize != true && args.skip_serialize != true) {
|
|
748
|
+
workingForm = yield _this9.do_serialize(workingForm, "write", {}, args);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (args.skip_hooks != true) {
|
|
752
|
+
yield _this9.run_hook(workingForm, "create", "before");
|
|
753
|
+
}
|
|
754
|
+
|
|
714
755
|
var results = yield _this9.database.insert(id, workingForm, args.expiry);
|
|
715
756
|
|
|
716
757
|
_this9.setMonitor();
|
|
717
758
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
}
|
|
721
|
-
|
|
759
|
+
if (args.skip_read_serialize != true && args.skip_serialize != true) {
|
|
760
|
+
results = yield _this9.do_serialize(results, "read", {}, args, (yield _this9.propsToBeRemoved(results)));
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (args.skip_hooks != true) {
|
|
764
|
+
yield _this9.run_hook(_extends({}, results, {
|
|
765
|
+
id
|
|
766
|
+
}), "create", "after");
|
|
767
|
+
}
|
|
768
|
+
|
|
722
769
|
return _extends({}, results, {
|
|
723
770
|
id
|
|
724
771
|
});
|
|
@@ -742,7 +789,10 @@ class SpiceModel {
|
|
|
742
789
|
var results = yield _this10.database.get(args.id);
|
|
743
790
|
|
|
744
791
|
try {
|
|
745
|
-
|
|
792
|
+
if (args.skip_hooks != true) {
|
|
793
|
+
yield _this10.run_hook(args, "delete", "before");
|
|
794
|
+
}
|
|
795
|
+
|
|
746
796
|
var delete_response = {};
|
|
747
797
|
|
|
748
798
|
if (args.hard) {
|
|
@@ -755,7 +805,10 @@ class SpiceModel {
|
|
|
755
805
|
delete_response = yield _this10.database.update(args.id, "");
|
|
756
806
|
}
|
|
757
807
|
|
|
758
|
-
|
|
808
|
+
if (args.skip_hooks != true) {
|
|
809
|
+
yield _this10.run_hook(results, "delete", "after", results);
|
|
810
|
+
}
|
|
811
|
+
|
|
759
812
|
return {};
|
|
760
813
|
} catch (e) {
|
|
761
814
|
console.log(e.stack);
|
|
@@ -807,6 +860,10 @@ class SpiceModel {
|
|
|
807
860
|
}
|
|
808
861
|
}
|
|
809
862
|
|
|
863
|
+
if ((0, _Security.hasSQLInjection)(query)) {
|
|
864
|
+
return [];
|
|
865
|
+
}
|
|
866
|
+
|
|
810
867
|
if (args.limit) {
|
|
811
868
|
args.limit = Number(args.limit);
|
|
812
869
|
}
|
|
@@ -819,7 +876,9 @@ class SpiceModel {
|
|
|
819
876
|
args.sort = "created_at DESC";
|
|
820
877
|
}
|
|
821
878
|
|
|
822
|
-
|
|
879
|
+
if (args.skip_hooks != true) {
|
|
880
|
+
yield _this11.run_hook(_this11, "list", "before");
|
|
881
|
+
}
|
|
823
882
|
|
|
824
883
|
function removeSpaceAndSpecialCharacters(str) {
|
|
825
884
|
return str.replace(/[^a-zA-Z0-9]/g, "");
|
|
@@ -830,7 +889,7 @@ class SpiceModel {
|
|
|
830
889
|
|
|
831
890
|
if (args.is_custom_query && args.is_custom_query === "true") {
|
|
832
891
|
if (args.ids.length > 0) {
|
|
833
|
-
if (_this11.
|
|
892
|
+
if (_this11.shouldUseCache(_this11.type)) {
|
|
834
893
|
var cached_results = yield _this11.getCacheProviderObject(_this11.type).get(key);
|
|
835
894
|
results = cached_results == null ? void 0 : cached_results.value;
|
|
836
895
|
|
|
@@ -848,7 +907,7 @@ class SpiceModel {
|
|
|
848
907
|
}
|
|
849
908
|
} else {
|
|
850
909
|
if (args.is_full_text && args.is_full_text === "true") {
|
|
851
|
-
if (_this11.
|
|
910
|
+
if (_this11.shouldUseCache(_this11.type)) {
|
|
852
911
|
var _cached_results = yield _this11.getCacheProviderObject(_this11.type).get(key);
|
|
853
912
|
|
|
854
913
|
results = _cached_results == null ? void 0 : _cached_results.value;
|
|
@@ -865,12 +924,15 @@ class SpiceModel {
|
|
|
865
924
|
results = yield _this11.database.full_text_search(_this11.type, query || "", args.limit, args.offset);
|
|
866
925
|
}
|
|
867
926
|
} else {
|
|
868
|
-
if (_this11.
|
|
927
|
+
if (_this11.shouldUseCache(_this11.type)) {
|
|
869
928
|
var _cached_results2 = yield _this11.getCacheProviderObject(_this11.type).get(key);
|
|
870
929
|
|
|
871
930
|
results = _cached_results2 == null ? void 0 : _cached_results2.value;
|
|
931
|
+
var shouleForceRefresh = yield _this11.shouldForceRefresh(_cached_results2);
|
|
932
|
+
console.log("Cache Results", _this11.type, (_cached_results2 == null ? void 0 : _cached_results2.value) == undefined, shouleForceRefresh, (_cached_results2 == null ? void 0 : _cached_results2.value) == undefined || shouleForceRefresh);
|
|
872
933
|
|
|
873
|
-
if ((_cached_results2 == null ? void 0 : _cached_results2.value) == undefined ||
|
|
934
|
+
if ((_cached_results2 == null ? void 0 : _cached_results2.value) == undefined || shouleForceRefresh) {
|
|
935
|
+
console.log("Getting From Database With Cache", _this11.type);
|
|
874
936
|
results = yield _this11.database.search(_this11.type, args.columns || "", query || "", args.limit, args.offset, args.sort, args.do_count, args.statement_consistent);
|
|
875
937
|
|
|
876
938
|
_this11.getCacheProviderObject(_this11.type).set(key, {
|
|
@@ -889,8 +951,13 @@ class SpiceModel {
|
|
|
889
951
|
}
|
|
890
952
|
|
|
891
953
|
try {
|
|
892
|
-
|
|
893
|
-
|
|
954
|
+
if (args.skip_read_serialize != true && args.skip_serialize != true) {
|
|
955
|
+
results.data = yield _this11.do_serialize(results.data, "read", {}, args, (yield _this11.propsToBeRemoved(results.data)));
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
if (args.skip_hooks != true) {
|
|
959
|
+
yield _this11.run_hook(results.data, "list", "after");
|
|
960
|
+
}
|
|
894
961
|
} catch (e) {
|
|
895
962
|
console.log(e);
|
|
896
963
|
}
|
|
@@ -997,7 +1064,9 @@ class SpiceModel {
|
|
|
997
1064
|
});
|
|
998
1065
|
|
|
999
1066
|
var returned_all = yield Promise.allSettled(_.map(classes, obj => {
|
|
1000
|
-
return new obj(_this13[_args]
|
|
1067
|
+
return new obj(_extends({}, _this13[_args], {
|
|
1068
|
+
skip_cache: _this13[_skip_cache]
|
|
1069
|
+
})).getMulti({
|
|
1001
1070
|
ids: ids
|
|
1002
1071
|
});
|
|
1003
1072
|
}));
|
|
@@ -1048,7 +1117,9 @@ class SpiceModel {
|
|
|
1048
1117
|
|
|
1049
1118
|
var classes = _.isArray(Class) ? Class : [Class];
|
|
1050
1119
|
var returned_all = yield Promise.allSettled(_.map(classes, obj => {
|
|
1051
|
-
return new obj(_this14[_args]
|
|
1120
|
+
return new obj(_extends({}, _this14[_args], {
|
|
1121
|
+
skip_cache: _this14[_skip_cache]
|
|
1122
|
+
})).getMulti({
|
|
1052
1123
|
ids: ids
|
|
1053
1124
|
});
|
|
1054
1125
|
}));
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.hasSQLInjection = hasSQLInjection;
|
|
5
|
+
|
|
6
|
+
function hasSQLInjection(input) {
|
|
7
|
+
// Convert input to lowercase for easier pattern matching
|
|
8
|
+
var lowerInput = input.toLowerCase(); // List of patterns that indicate potential SQL injection
|
|
9
|
+
|
|
10
|
+
var patterns = ["--", // T-SQL comment
|
|
11
|
+
"/*", // Start of block comment
|
|
12
|
+
"*/", // End of block comment
|
|
13
|
+
"or 1=1", // OR statement that's always true
|
|
14
|
+
"or true", // OR statement that's always true (non-T-SQL databases)
|
|
15
|
+
"union all", // UNION statement (often used to retrieve data from another table)
|
|
16
|
+
"union select", // Another form of UNION statement
|
|
17
|
+
";", // Commenting out the rest of a query
|
|
18
|
+
"drop table", // DDL statements are very suspicious
|
|
19
|
+
"update ", // Modification commands
|
|
20
|
+
"insert into", // Insertion commands
|
|
21
|
+
"delete from", // Deletion commands
|
|
22
|
+
"exec(", // Execution function in T-SQL
|
|
23
|
+
"execute(", // Another execution function in T-SQL
|
|
24
|
+
"xp_", // Extended stored procedures in T-SQL - often used in older SQL Server attacks
|
|
25
|
+
"0x", // Hex encoding
|
|
26
|
+
"benchmark(", // MySQL benchmark function - can be used for time-based blind attacks
|
|
27
|
+
"sleep(", // MySQL sleep function - can also be used for time-based blind attacks
|
|
28
|
+
"pg_sleep(", // PostgreSQL sleep function - time-based blind attack
|
|
29
|
+
"cast(", // Casting can be used for type-based attacks
|
|
30
|
+
"convert(", // Similar to the above
|
|
31
|
+
"waitfor delay" // SQL Server delay function - time-based blind attack
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
for (var pattern of patterns) {
|
|
35
|
+
if (lowerInput.includes(pattern)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
} // Additional patterns based on regular expressions for more complex detections
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
var regexPatterns = [/['"‘’“”][\s]*[+]+[\s]*['"‘’“”]/ // String concatenation, often used to obfuscate injections
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
for (var regex of regexPatterns) {
|
|
45
|
+
if (regex.test(lowerInput)) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return false;
|
|
51
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spice-js",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.8",
|
|
4
4
|
"description": "spice",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"repository": {
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
"license": "ISC",
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"agenda": "^3.1.0",
|
|
18
|
-
"caniuse-lite": "^1.0.30001283",
|
|
19
18
|
"co": "^4.6.0",
|
|
20
19
|
"dotenv": "^8.2.0",
|
|
21
20
|
"flat": "^5.0.0",
|
package/src/bootstrap/cache.js
CHANGED
|
@@ -8,7 +8,14 @@ async function connect() {
|
|
|
8
8
|
spice.cache_providers[key] = new spice.config.cache.providers[key](
|
|
9
9
|
spice.config.cache.drivers[key] || {}
|
|
10
10
|
);
|
|
11
|
-
spice.cache_providers[key]
|
|
11
|
+
spice.cache_providers[key]
|
|
12
|
+
.initialize()
|
|
13
|
+
.then(() => {
|
|
14
|
+
console.log("Redis initialized successfully");
|
|
15
|
+
})
|
|
16
|
+
.catch((err) => {
|
|
17
|
+
console.error("Error initializing Redis:", err);
|
|
18
|
+
});
|
|
12
19
|
}
|
|
13
20
|
} catch (e) {
|
|
14
21
|
console.log(e.stack);
|
package/src/models/SpiceModel.js
CHANGED
|
@@ -4,6 +4,7 @@ import { MapType, DataType } from "..";
|
|
|
4
4
|
let co = require("co");
|
|
5
5
|
var regeneratorRuntime = require("regenerator-runtime");
|
|
6
6
|
import ResourceLifecycleTriggered from "../events/events/ResourceLifecycleTriggered";
|
|
7
|
+
import { hasSQLInjection } from "../utility/Security";
|
|
7
8
|
|
|
8
9
|
var SDate = require("sonover-date"),
|
|
9
10
|
UUID = require("uuid"),
|
|
@@ -15,6 +16,7 @@ var SDate = require("sonover-date"),
|
|
|
15
16
|
_hooks = Symbol(),
|
|
16
17
|
_disable_lifecycle_events = Symbol(),
|
|
17
18
|
_external_modifier_loaded = Symbol(),
|
|
19
|
+
_skip_cache = Symbol(),
|
|
18
20
|
_serializers = Symbol();
|
|
19
21
|
|
|
20
22
|
//const _type = Symbol("type");
|
|
@@ -48,6 +50,7 @@ export default class SpiceModel {
|
|
|
48
50
|
this[_disable_lifecycle_events] =
|
|
49
51
|
args.args?.disable_lifecycle_events || false;
|
|
50
52
|
this[_ctx] = args?.args?.ctx;
|
|
53
|
+
this[_skip_cache] = args?.args?.skip_cache || false;
|
|
51
54
|
this[_hooks] = {
|
|
52
55
|
create: {
|
|
53
56
|
before: [],
|
|
@@ -365,11 +368,21 @@ export default class SpiceModel {
|
|
|
365
368
|
return return_string;
|
|
366
369
|
}
|
|
367
370
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
371
|
+
shouldUseCache(resource_type) {
|
|
372
|
+
// If '_skip_cache' property of this object is true, then we shouldn't cache.
|
|
373
|
+
if (this[_skip_cache] == true) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// If the system configuration for spice has a cache status set to "disable",
|
|
378
|
+
// then we shouldn't cache, so return false.
|
|
379
|
+
if (spice.config.cache?.status == "disabled") {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// If 'spice.cache[resource_type]' is not undefined,
|
|
384
|
+
// it implies that this resource type is already in the cache or is cacheable.
|
|
385
|
+
return spice.cache[resource_type] != undefined;
|
|
373
386
|
}
|
|
374
387
|
|
|
375
388
|
getCacheConfig(resource_type) {
|
|
@@ -405,8 +418,9 @@ export default class SpiceModel {
|
|
|
405
418
|
setMonitor() {
|
|
406
419
|
let current_time = new Date().getTime();
|
|
407
420
|
let obj = this.getCacheProviderObject();
|
|
408
|
-
|
|
409
|
-
|
|
421
|
+
let key = `monitor::${this.type}`;
|
|
422
|
+
let value = { time: current_time };
|
|
423
|
+
obj.set(key, value, { ttl: 0 });
|
|
410
424
|
}
|
|
411
425
|
|
|
412
426
|
async get(args) {
|
|
@@ -415,14 +429,17 @@ export default class SpiceModel {
|
|
|
415
429
|
args = {};
|
|
416
430
|
}
|
|
417
431
|
if (_.isString(args.id)) {
|
|
418
|
-
|
|
432
|
+
if (args.skip_hooks != true) {
|
|
433
|
+
await this.run_hook(args, "get", "before");
|
|
434
|
+
}
|
|
419
435
|
let key = `${this.type}::${args.id}`;
|
|
420
436
|
let results = {};
|
|
421
|
-
if (this.
|
|
437
|
+
if (this.shouldUseCache(this.type)) {
|
|
422
438
|
let cached_results = await this.getCacheProviderObject(this.type).get(
|
|
423
439
|
key
|
|
424
440
|
);
|
|
425
441
|
results = cached_results?.value;
|
|
442
|
+
|
|
426
443
|
if (
|
|
427
444
|
cached_results?.value == undefined ||
|
|
428
445
|
(await this.shouldForceRefresh(cached_results))
|
|
@@ -449,14 +466,18 @@ export default class SpiceModel {
|
|
|
449
466
|
}
|
|
450
467
|
|
|
451
468
|
if (results.deleted == undefined || results.deleted == false) {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
469
|
+
if (args.skip_read_serialize != true && args.skip_serialize != true) {
|
|
470
|
+
results = await this.do_serialize(
|
|
471
|
+
results,
|
|
472
|
+
"read",
|
|
473
|
+
{},
|
|
474
|
+
args,
|
|
475
|
+
await this.propsToBeRemoved(results)
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
if (args.skip_hooks != true) {
|
|
479
|
+
await this.run_hook(results, "get", "after");
|
|
480
|
+
}
|
|
460
481
|
return results;
|
|
461
482
|
} else {
|
|
462
483
|
throw new Error(`${this.type} does not exist`);
|
|
@@ -481,13 +502,14 @@ export default class SpiceModel {
|
|
|
481
502
|
if (!args) {
|
|
482
503
|
args = {};
|
|
483
504
|
}
|
|
484
|
-
|
|
485
|
-
|
|
505
|
+
if (args.skip_hooks != true) {
|
|
506
|
+
await this.run_hook(this, "list", "before");
|
|
507
|
+
}
|
|
486
508
|
_.remove(args.ids, (o) => o == undefined);
|
|
487
509
|
let key = `${this.type}::${_.join(args.ids, "|")}`;
|
|
488
510
|
let results = [];
|
|
489
511
|
if (args.ids.length > 0) {
|
|
490
|
-
if (this.
|
|
512
|
+
if (this.shouldUseCache(this.type)) {
|
|
491
513
|
let cached_results = await this.getCacheProviderObject(this.type).get(
|
|
492
514
|
key
|
|
493
515
|
);
|
|
@@ -508,15 +530,19 @@ export default class SpiceModel {
|
|
|
508
530
|
}
|
|
509
531
|
}
|
|
510
532
|
_.remove(results, (o) => o.type != this.type);
|
|
533
|
+
if (args.skip_read_serialize != true && args.skip_serialize != true) {
|
|
534
|
+
results = await this.do_serialize(
|
|
535
|
+
results,
|
|
536
|
+
"read",
|
|
537
|
+
{},
|
|
538
|
+
args,
|
|
539
|
+
await this.propsToBeRemoved(results)
|
|
540
|
+
);
|
|
541
|
+
}
|
|
511
542
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
"read",
|
|
516
|
-
{},
|
|
517
|
-
args,
|
|
518
|
-
await this.propsToBeRemoved(results)
|
|
519
|
-
);
|
|
543
|
+
if (args.skip_hooks != true) {
|
|
544
|
+
await this.run_hook(results, "list", "after", args.context);
|
|
545
|
+
}
|
|
520
546
|
|
|
521
547
|
return results;
|
|
522
548
|
} catch (e) {
|
|
@@ -569,19 +595,35 @@ export default class SpiceModel {
|
|
|
569
595
|
new: this,
|
|
570
596
|
id: args.id,
|
|
571
597
|
};
|
|
572
|
-
if (args.skip_hooks != true) {
|
|
573
|
-
await this.run_hook(cover_obj, "update", "before", results);
|
|
574
|
-
}
|
|
575
598
|
|
|
576
599
|
let form;
|
|
577
600
|
if (args.skip_write_serialize != true && args.skip_serialize != true) {
|
|
578
|
-
|
|
601
|
+
cover_obj.new = await this.do_serialize(
|
|
602
|
+
this,
|
|
603
|
+
"write",
|
|
604
|
+
cover_obj.new,
|
|
605
|
+
args
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (args.skip_hooks != true) {
|
|
610
|
+
await this.run_hook(cover_obj, "update", "before", results);
|
|
579
611
|
}
|
|
580
|
-
let db_data =
|
|
612
|
+
let db_data = cover_obj.new || this;
|
|
581
613
|
|
|
582
614
|
await this.database.update(args.id, db_data);
|
|
583
615
|
this.setMonitor();
|
|
584
616
|
|
|
617
|
+
if (args.skip_read_serialize != true && args.skip_serialize != true) {
|
|
618
|
+
cover_obj.new = await this.do_serialize(
|
|
619
|
+
cover_obj.new,
|
|
620
|
+
"read",
|
|
621
|
+
{},
|
|
622
|
+
args,
|
|
623
|
+
await this.propsToBeRemoved(cover_obj.new)
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
585
627
|
if (args.skip_hooks != true) {
|
|
586
628
|
await this.run_hook(
|
|
587
629
|
{
|
|
@@ -593,17 +635,8 @@ export default class SpiceModel {
|
|
|
593
635
|
results
|
|
594
636
|
);
|
|
595
637
|
}
|
|
596
|
-
if (args.skip_read_serialize != true && args.skip_serialize != true) {
|
|
597
|
-
form = await this.do_serialize(
|
|
598
|
-
form,
|
|
599
|
-
"read",
|
|
600
|
-
{},
|
|
601
|
-
args,
|
|
602
|
-
await this.propsToBeRemoved(form)
|
|
603
|
-
);
|
|
604
|
-
}
|
|
605
638
|
this.id = args.id;
|
|
606
|
-
return { ...
|
|
639
|
+
return { ...cover_obj.new, id: args.id };
|
|
607
640
|
} catch (e) {
|
|
608
641
|
console.log("Error on update", e, e.stack);
|
|
609
642
|
throw e;
|
|
@@ -627,26 +660,37 @@ export default class SpiceModel {
|
|
|
627
660
|
if (args && args.id) {
|
|
628
661
|
id = args.id;
|
|
629
662
|
}
|
|
630
|
-
|
|
631
|
-
|
|
663
|
+
|
|
664
|
+
if (args.skip_write_serialize != true && args.skip_serialize != true) {
|
|
665
|
+
workingForm = await this.do_serialize(workingForm, "write", {}, args);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (args.skip_hooks != true) {
|
|
669
|
+
await this.run_hook(workingForm, "create", "before");
|
|
670
|
+
}
|
|
632
671
|
|
|
633
672
|
let results = await this.database.insert(id, workingForm, args.expiry);
|
|
634
673
|
this.setMonitor();
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
674
|
+
|
|
675
|
+
if (args.skip_read_serialize != true && args.skip_serialize != true) {
|
|
676
|
+
results = await this.do_serialize(
|
|
677
|
+
results,
|
|
678
|
+
"read",
|
|
679
|
+
{},
|
|
680
|
+
args,
|
|
681
|
+
await this.propsToBeRemoved(results)
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
if (args.skip_hooks != true) {
|
|
685
|
+
await this.run_hook(
|
|
686
|
+
{
|
|
687
|
+
...results,
|
|
688
|
+
id,
|
|
689
|
+
},
|
|
690
|
+
"create",
|
|
691
|
+
"after"
|
|
692
|
+
);
|
|
693
|
+
}
|
|
650
694
|
return { ...results, id };
|
|
651
695
|
} catch (e) {
|
|
652
696
|
console.log(e.stack);
|
|
@@ -662,7 +706,9 @@ export default class SpiceModel {
|
|
|
662
706
|
}
|
|
663
707
|
let results = await this.database.get(args.id);
|
|
664
708
|
try {
|
|
665
|
-
|
|
709
|
+
if (args.skip_hooks != true) {
|
|
710
|
+
await this.run_hook(args, "delete", "before");
|
|
711
|
+
}
|
|
666
712
|
let delete_response = {};
|
|
667
713
|
if (args.hard) {
|
|
668
714
|
delete_response = await this.database.delete(args.id);
|
|
@@ -672,7 +718,9 @@ export default class SpiceModel {
|
|
|
672
718
|
results.deleted = true;
|
|
673
719
|
delete_response = await this.database.update(args.id, "");
|
|
674
720
|
}
|
|
675
|
-
|
|
721
|
+
if (args.skip_hooks != true) {
|
|
722
|
+
await this.run_hook(results, "delete", "after", results);
|
|
723
|
+
}
|
|
676
724
|
return {};
|
|
677
725
|
} catch (e) {
|
|
678
726
|
console.log(e.stack);
|
|
@@ -720,6 +768,9 @@ export default class SpiceModel {
|
|
|
720
768
|
}
|
|
721
769
|
}
|
|
722
770
|
|
|
771
|
+
if (hasSQLInjection(query)) {
|
|
772
|
+
return [];
|
|
773
|
+
}
|
|
723
774
|
if (args.limit) {
|
|
724
775
|
args.limit = Number(args.limit);
|
|
725
776
|
}
|
|
@@ -730,8 +781,9 @@ export default class SpiceModel {
|
|
|
730
781
|
if (!args.sort) {
|
|
731
782
|
args.sort = "created_at DESC";
|
|
732
783
|
}
|
|
733
|
-
|
|
734
|
-
|
|
784
|
+
if (args.skip_hooks != true) {
|
|
785
|
+
await this.run_hook(this, "list", "before");
|
|
786
|
+
}
|
|
735
787
|
function removeSpaceAndSpecialCharacters(str) {
|
|
736
788
|
return str.replace(/[^a-zA-Z0-9]/g, "");
|
|
737
789
|
}
|
|
@@ -742,7 +794,7 @@ export default class SpiceModel {
|
|
|
742
794
|
let results;
|
|
743
795
|
if (args.is_custom_query && args.is_custom_query === "true") {
|
|
744
796
|
if (args.ids.length > 0) {
|
|
745
|
-
if (this.
|
|
797
|
+
if (this.shouldUseCache(this.type)) {
|
|
746
798
|
let cached_results = await this.getCacheProviderObject(
|
|
747
799
|
this.type
|
|
748
800
|
).get(key);
|
|
@@ -764,7 +816,7 @@ export default class SpiceModel {
|
|
|
764
816
|
}
|
|
765
817
|
} else {
|
|
766
818
|
if (args.is_full_text && args.is_full_text === "true") {
|
|
767
|
-
if (this.
|
|
819
|
+
if (this.shouldUseCache(this.type)) {
|
|
768
820
|
let cached_results = await this.getCacheProviderObject(
|
|
769
821
|
this.type
|
|
770
822
|
).get(key);
|
|
@@ -794,15 +846,23 @@ export default class SpiceModel {
|
|
|
794
846
|
);
|
|
795
847
|
}
|
|
796
848
|
} else {
|
|
797
|
-
if (this.
|
|
849
|
+
if (this.shouldUseCache(this.type)) {
|
|
798
850
|
let cached_results = await this.getCacheProviderObject(
|
|
799
851
|
this.type
|
|
800
852
|
).get(key);
|
|
801
853
|
results = cached_results?.value;
|
|
802
|
-
|
|
803
|
-
cached_results
|
|
804
|
-
|
|
805
|
-
|
|
854
|
+
let shouleForceRefresh = await this.shouldForceRefresh(
|
|
855
|
+
cached_results
|
|
856
|
+
);
|
|
857
|
+
console.log(
|
|
858
|
+
"Cache Results",
|
|
859
|
+
this.type,
|
|
860
|
+
cached_results?.value == undefined,
|
|
861
|
+
shouleForceRefresh,
|
|
862
|
+
cached_results?.value == undefined || shouleForceRefresh
|
|
863
|
+
);
|
|
864
|
+
if (cached_results?.value == undefined || shouleForceRefresh) {
|
|
865
|
+
console.log("Getting From Database With Cache", this.type);
|
|
806
866
|
results = await this.database.search(
|
|
807
867
|
this.type,
|
|
808
868
|
args.columns || "",
|
|
@@ -837,14 +897,18 @@ export default class SpiceModel {
|
|
|
837
897
|
}
|
|
838
898
|
}
|
|
839
899
|
try {
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
900
|
+
if (args.skip_read_serialize != true && args.skip_serialize != true) {
|
|
901
|
+
results.data = await this.do_serialize(
|
|
902
|
+
results.data,
|
|
903
|
+
"read",
|
|
904
|
+
{},
|
|
905
|
+
args,
|
|
906
|
+
await this.propsToBeRemoved(results.data)
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
if (args.skip_hooks != true) {
|
|
910
|
+
await this.run_hook(results.data, "list", "after");
|
|
911
|
+
}
|
|
848
912
|
} catch (e) {
|
|
849
913
|
console.log(e);
|
|
850
914
|
}
|
|
@@ -931,7 +995,10 @@ export default class SpiceModel {
|
|
|
931
995
|
|
|
932
996
|
var returned_all = await Promise.allSettled(
|
|
933
997
|
_.map(classes, (obj) => {
|
|
934
|
-
return new obj(
|
|
998
|
+
return new obj({
|
|
999
|
+
...this[_args],
|
|
1000
|
+
skip_cache: this[_skip_cache],
|
|
1001
|
+
}).getMulti({
|
|
935
1002
|
ids: ids,
|
|
936
1003
|
});
|
|
937
1004
|
})
|
|
@@ -986,7 +1053,10 @@ export default class SpiceModel {
|
|
|
986
1053
|
let classes = _.isArray(Class) ? Class : [Class];
|
|
987
1054
|
var returned_all = await Promise.allSettled(
|
|
988
1055
|
_.map(classes, (obj) => {
|
|
989
|
-
return new obj(
|
|
1056
|
+
return new obj({
|
|
1057
|
+
...this[_args],
|
|
1058
|
+
skip_cache: this[_skip_cache],
|
|
1059
|
+
}).getMulti({
|
|
990
1060
|
ids: ids,
|
|
991
1061
|
});
|
|
992
1062
|
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function hasSQLInjection(input) {
|
|
2
|
+
// Convert input to lowercase for easier pattern matching
|
|
3
|
+
const lowerInput = input.toLowerCase();
|
|
4
|
+
|
|
5
|
+
// List of patterns that indicate potential SQL injection
|
|
6
|
+
const patterns = [
|
|
7
|
+
"--", // T-SQL comment
|
|
8
|
+
"/*", // Start of block comment
|
|
9
|
+
"*/", // End of block comment
|
|
10
|
+
"or 1=1", // OR statement that's always true
|
|
11
|
+
"or true", // OR statement that's always true (non-T-SQL databases)
|
|
12
|
+
"union all", // UNION statement (often used to retrieve data from another table)
|
|
13
|
+
"union select", // Another form of UNION statement
|
|
14
|
+
";", // Commenting out the rest of a query
|
|
15
|
+
"drop table", // DDL statements are very suspicious
|
|
16
|
+
"update ", // Modification commands
|
|
17
|
+
"insert into", // Insertion commands
|
|
18
|
+
"delete from", // Deletion commands
|
|
19
|
+
"exec(", // Execution function in T-SQL
|
|
20
|
+
"execute(", // Another execution function in T-SQL
|
|
21
|
+
"xp_", // Extended stored procedures in T-SQL - often used in older SQL Server attacks
|
|
22
|
+
"0x", // Hex encoding
|
|
23
|
+
"benchmark(", // MySQL benchmark function - can be used for time-based blind attacks
|
|
24
|
+
"sleep(", // MySQL sleep function - can also be used for time-based blind attacks
|
|
25
|
+
"pg_sleep(", // PostgreSQL sleep function - time-based blind attack
|
|
26
|
+
"cast(", // Casting can be used for type-based attacks
|
|
27
|
+
"convert(", // Similar to the above
|
|
28
|
+
"waitfor delay", // SQL Server delay function - time-based blind attack
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
for (let pattern of patterns) {
|
|
32
|
+
if (lowerInput.includes(pattern)) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Additional patterns based on regular expressions for more complex detections
|
|
38
|
+
const regexPatterns = [
|
|
39
|
+
/['"‘’“”][\s]*[+]+[\s]*['"‘’“”]/, // String concatenation, often used to obfuscate injections
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
for (let regex of regexPatterns) {
|
|
43
|
+
if (regex.test(lowerInput)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return false;
|
|
49
|
+
}
|