@flowerforce/flowerbase 1.8.3 → 1.8.4-beta.2
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/features/triggers/utils.d.ts.map +1 -1
- package/dist/features/triggers/utils.js +28 -18
- package/dist/monitoring/utils.d.ts +1 -1
- package/dist/services/mongodb-atlas/index.js +11 -11
- package/dist/utils/context/helpers.d.ts +3 -3
- package/dist/utils/roles/helpers.d.ts +1 -0
- package/dist/utils/roles/helpers.d.ts.map +1 -1
- package/dist/utils/roles/helpers.js +77 -8
- package/dist/utils/roles/machines/utils.d.ts +2 -0
- package/dist/utils/roles/machines/utils.d.ts.map +1 -1
- package/dist/utils/roles/machines/utils.js +33 -1
- package/package.json +1 -1
- package/src/features/triggers/__tests__/utils.test.ts +112 -0
- package/src/features/triggers/utils.ts +30 -18
- package/src/services/mongodb-atlas/index.ts +150 -150
- package/src/utils/__tests__/evaluateExpression.test.ts +89 -0
- package/src/utils/__tests__/getWinningRole.test.ts +17 -0
- package/src/utils/roles/helpers.ts +107 -14
- package/src/utils/roles/machines/utils.ts +34 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/features/triggers/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/features/triggers/utils.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,aAAa,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAA;AA6E9D;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAU,gBAAuB,KAAG,OAAO,CAAC,QAAQ,CAkB5E,CAAA;AAmrBD,eAAO,MAAM,gBAAgB;kHA5pB1B,aAAa;iHAmkBb,aAAa;uHAhdb,aAAa;CA6iBf,CAAA"}
|
|
@@ -27,11 +27,13 @@ exports.TRIGGER_HANDLERS = exports.loadTriggers = void 0;
|
|
|
27
27
|
const fs_1 = __importDefault(require("fs"));
|
|
28
28
|
const node_path_1 = __importDefault(require("node:path"));
|
|
29
29
|
const node_cron_1 = __importDefault(require("node-cron"));
|
|
30
|
+
const bson_1 = require("bson");
|
|
30
31
|
const constants_1 = require("../../constants");
|
|
31
32
|
const utils_1 = require("../../monitoring/utils");
|
|
32
33
|
const state_1 = require("../../state");
|
|
33
34
|
const utils_2 = require("../../utils");
|
|
34
35
|
const context_1 = require("../../utils/context");
|
|
36
|
+
const normalizeTriggerPayload = (value) => bson_1.EJSON.deserialize(bson_1.EJSON.serialize(value, { relaxed: false }));
|
|
35
37
|
const registerOnClose = (app, handler, label) => {
|
|
36
38
|
if (app.server) {
|
|
37
39
|
app.server.once('close', () => {
|
|
@@ -148,7 +150,8 @@ const handleCronTrigger = (_a) => __awaiter(void 0, [_a], void 0, function* ({ c
|
|
|
148
150
|
currentFunction: triggerHandler,
|
|
149
151
|
functionName,
|
|
150
152
|
functionsList,
|
|
151
|
-
services
|
|
153
|
+
services,
|
|
154
|
+
deserializeArgs: false
|
|
152
155
|
});
|
|
153
156
|
}
|
|
154
157
|
catch (error) {
|
|
@@ -332,7 +335,7 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
332
335
|
meta: Object.assign(Object.assign({}, baseMeta), { event: 'LOGOUT' })
|
|
333
336
|
});
|
|
334
337
|
yield (0, context_1.GenerateContext)({
|
|
335
|
-
args: [Object.assign({ user: userData }, op)],
|
|
338
|
+
args: [normalizeTriggerPayload(Object.assign({ user: userData }, op))],
|
|
336
339
|
app,
|
|
337
340
|
rules: state_1.StateManager.select("rules"),
|
|
338
341
|
user: {}, // TODO from currentUser ??
|
|
@@ -340,7 +343,8 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
340
343
|
functionName,
|
|
341
344
|
functionsList,
|
|
342
345
|
services,
|
|
343
|
-
runAsSystem: true
|
|
346
|
+
runAsSystem: true,
|
|
347
|
+
deserializeArgs: false
|
|
344
348
|
});
|
|
345
349
|
}
|
|
346
350
|
catch (error) {
|
|
@@ -352,7 +356,6 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
352
356
|
meta: Object.assign(Object.assign({}, baseMeta), { event: 'LOGOUT' }),
|
|
353
357
|
error
|
|
354
358
|
});
|
|
355
|
-
console.log("🚀 ~ handleAuthenticationTrigger ~ error:", error);
|
|
356
359
|
}
|
|
357
360
|
return;
|
|
358
361
|
}
|
|
@@ -389,7 +392,7 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
389
392
|
meta: Object.assign(Object.assign({}, baseMeta), { event: 'LOGIN' })
|
|
390
393
|
});
|
|
391
394
|
yield (0, context_1.GenerateContext)({
|
|
392
|
-
args: [Object.assign({ user: userData }, op)],
|
|
395
|
+
args: [normalizeTriggerPayload(Object.assign({ user: userData }, op))],
|
|
393
396
|
app,
|
|
394
397
|
rules: state_1.StateManager.select("rules"),
|
|
395
398
|
user: {}, // TODO from currentUser ??
|
|
@@ -397,7 +400,8 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
397
400
|
functionName,
|
|
398
401
|
functionsList,
|
|
399
402
|
services,
|
|
400
|
-
runAsSystem: true
|
|
403
|
+
runAsSystem: true,
|
|
404
|
+
deserializeArgs: false
|
|
401
405
|
});
|
|
402
406
|
}
|
|
403
407
|
catch (error) {
|
|
@@ -409,7 +413,6 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
409
413
|
meta: Object.assign(Object.assign({}, baseMeta), { event: 'LOGIN' }),
|
|
410
414
|
error
|
|
411
415
|
});
|
|
412
|
-
console.log("🚀 ~ handleAuthenticationTrigger ~ error:", error);
|
|
413
416
|
}
|
|
414
417
|
return;
|
|
415
418
|
}
|
|
@@ -441,7 +444,9 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
441
444
|
meta: Object.assign(Object.assign({}, baseMeta), { event: 'DELETE' })
|
|
442
445
|
});
|
|
443
446
|
yield (0, context_1.GenerateContext)({
|
|
444
|
-
args: isAutoTrigger
|
|
447
|
+
args: isAutoTrigger
|
|
448
|
+
? [normalizeTriggerPayload(userData)]
|
|
449
|
+
: [normalizeTriggerPayload(Object.assign({ user: userData }, op))],
|
|
445
450
|
app,
|
|
446
451
|
rules: state_1.StateManager.select("rules"),
|
|
447
452
|
user: {}, // TODO from currentUser ??
|
|
@@ -449,7 +454,8 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
449
454
|
functionName,
|
|
450
455
|
functionsList,
|
|
451
456
|
services,
|
|
452
|
-
runAsSystem: true
|
|
457
|
+
runAsSystem: true,
|
|
458
|
+
deserializeArgs: false
|
|
453
459
|
});
|
|
454
460
|
}
|
|
455
461
|
catch (error) {
|
|
@@ -461,7 +467,6 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
461
467
|
meta: Object.assign(Object.assign({}, baseMeta), { event: 'DELETE' }),
|
|
462
468
|
error
|
|
463
469
|
});
|
|
464
|
-
console.log("🚀 ~ handleAuthenticationTrigger ~ error:", error);
|
|
465
470
|
}
|
|
466
471
|
return;
|
|
467
472
|
}
|
|
@@ -495,7 +500,9 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
495
500
|
meta: Object.assign(Object.assign({}, baseMeta), { event: 'UPDATE' })
|
|
496
501
|
});
|
|
497
502
|
yield (0, context_1.GenerateContext)({
|
|
498
|
-
args: isAutoTrigger
|
|
503
|
+
args: isAutoTrigger
|
|
504
|
+
? [normalizeTriggerPayload(userData)]
|
|
505
|
+
: [normalizeTriggerPayload(Object.assign({ user: userData }, op))],
|
|
499
506
|
app,
|
|
500
507
|
rules: state_1.StateManager.select("rules"),
|
|
501
508
|
user: {}, // TODO from currentUser ??
|
|
@@ -503,7 +510,8 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
503
510
|
functionName,
|
|
504
511
|
functionsList,
|
|
505
512
|
services,
|
|
506
|
-
runAsSystem: true
|
|
513
|
+
runAsSystem: true,
|
|
514
|
+
deserializeArgs: false
|
|
507
515
|
});
|
|
508
516
|
}
|
|
509
517
|
catch (error) {
|
|
@@ -515,7 +523,6 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
515
523
|
meta: Object.assign(Object.assign({}, baseMeta), { event: 'UPDATE' }),
|
|
516
524
|
error
|
|
517
525
|
});
|
|
518
|
-
console.log("🚀 ~ handleAuthenticationTrigger ~ error:", error);
|
|
519
526
|
}
|
|
520
527
|
return;
|
|
521
528
|
}
|
|
@@ -586,7 +593,9 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
586
593
|
meta: Object.assign(Object.assign({}, baseMeta), { event: 'CREATE' })
|
|
587
594
|
});
|
|
588
595
|
yield (0, context_1.GenerateContext)({
|
|
589
|
-
args: isAutoTrigger
|
|
596
|
+
args: isAutoTrigger
|
|
597
|
+
? [normalizeTriggerPayload(userData)]
|
|
598
|
+
: [normalizeTriggerPayload(Object.assign({ user: userData }, op))],
|
|
590
599
|
app,
|
|
591
600
|
rules: state_1.StateManager.select("rules"),
|
|
592
601
|
user: {}, // TODO from currentUser ??
|
|
@@ -594,7 +603,8 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
594
603
|
functionName,
|
|
595
604
|
functionsList,
|
|
596
605
|
services,
|
|
597
|
-
runAsSystem: true
|
|
606
|
+
runAsSystem: true,
|
|
607
|
+
deserializeArgs: false
|
|
598
608
|
});
|
|
599
609
|
}
|
|
600
610
|
catch (error) {
|
|
@@ -606,7 +616,6 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
606
616
|
meta: Object.assign(Object.assign({}, baseMeta), { event: 'CREATE' }),
|
|
607
617
|
error
|
|
608
618
|
});
|
|
609
|
-
console.log("🚀 ~ handleAuthenticationTrigger ~ error:", error);
|
|
610
619
|
}
|
|
611
620
|
});
|
|
612
621
|
});
|
|
@@ -677,14 +686,15 @@ const handleDataBaseTrigger = (_a) => __awaiter(void 0, [_a], void 0, function*
|
|
|
677
686
|
});
|
|
678
687
|
try {
|
|
679
688
|
yield (0, context_1.GenerateContext)({
|
|
680
|
-
args: [change],
|
|
689
|
+
args: [normalizeTriggerPayload(change)],
|
|
681
690
|
app,
|
|
682
691
|
rules: state_1.StateManager.select("rules"),
|
|
683
692
|
user: {}, // TODO add from?
|
|
684
693
|
currentFunction: triggerHandler,
|
|
685
694
|
functionName,
|
|
686
695
|
functionsList,
|
|
687
|
-
services
|
|
696
|
+
services,
|
|
697
|
+
deserializeArgs: false
|
|
688
698
|
});
|
|
689
699
|
}
|
|
690
700
|
catch (error) {
|
|
@@ -77,7 +77,7 @@ export declare const getErrorDetails: (error: unknown) => {
|
|
|
77
77
|
};
|
|
78
78
|
export declare const pickHeaders: (headers: FastifyRequest["headers"]) => unknown;
|
|
79
79
|
export declare const createEventStore: (maxAgeMs: number, maxEvents: number) => EventStore;
|
|
80
|
-
export declare const classifyRequest: (url: string) => "function" | "
|
|
80
|
+
export declare const classifyRequest: (url: string) => "function" | "api" | "auth" | "http_endpoint" | "trigger" | "http";
|
|
81
81
|
export declare const createEventId: () => string;
|
|
82
82
|
export declare const buildRulesMeta: (meta?: MonitMeta) => {
|
|
83
83
|
collection: string;
|
|
@@ -490,7 +490,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
|
|
|
490
490
|
emitMongoEvent('findOne');
|
|
491
491
|
return null;
|
|
492
492
|
}
|
|
493
|
-
const winningRole = (0, utils_2.
|
|
493
|
+
const winningRole = yield (0, utils_2.getWinningRoleAsync)(result, user, roles);
|
|
494
494
|
logDebug('findOne winningRole', {
|
|
495
495
|
collection: collName,
|
|
496
496
|
winningRoleName: (_a = winningRole === null || winningRole === void 0 ? void 0 : winningRole.name) !== null && _a !== void 0 ? _a : null,
|
|
@@ -546,7 +546,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
|
|
|
546
546
|
const formattedQuery = (0, utils_3.getFormattedQuery)(filters, query, user);
|
|
547
547
|
// Retrieve the document to check permissions before deleting
|
|
548
548
|
const result = yield collection.findOne(buildAndQuery(formattedQuery));
|
|
549
|
-
const winningRole = (0, utils_2.
|
|
549
|
+
const winningRole = yield (0, utils_2.getWinningRoleAsync)(result, user, roles);
|
|
550
550
|
logDebug('delete winningRole', {
|
|
551
551
|
collection: collName,
|
|
552
552
|
userId: getUserId(user),
|
|
@@ -600,7 +600,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
|
|
|
600
600
|
try {
|
|
601
601
|
if (!run_as_system) {
|
|
602
602
|
(0, utils_3.checkDenyOperation)(normalizedRules, collection.collectionName, model_1.CRUD_OPERATIONS.CREATE);
|
|
603
|
-
const winningRole = (0, utils_2.
|
|
603
|
+
const winningRole = yield (0, utils_2.getWinningRoleAsync)(data, user, roles);
|
|
604
604
|
const { status, document } = winningRole
|
|
605
605
|
? yield (0, machines_1.checkValidation)(winningRole, {
|
|
606
606
|
type: 'insert',
|
|
@@ -673,7 +673,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
|
|
|
673
673
|
}
|
|
674
674
|
throw new Error('Update not permitted');
|
|
675
675
|
}
|
|
676
|
-
const winningRole = (0, utils_2.
|
|
676
|
+
const winningRole = yield (0, utils_2.getWinningRoleAsync)(result, user, roles);
|
|
677
677
|
// Check if the update data contains MongoDB update operators (e.g., $set, $inc)
|
|
678
678
|
const updatedPaths = getUpdatedPaths(normalizedData);
|
|
679
679
|
const docToCheck = applyDocumentUpdateOperators(result, normalizedData);
|
|
@@ -756,7 +756,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
|
|
|
756
756
|
: [applyDocumentUpdateOperators(currentDoc, normalizedData)];
|
|
757
757
|
docToCheck = computedDoc;
|
|
758
758
|
}
|
|
759
|
-
const winningRole = (0, utils_2.
|
|
759
|
+
const winningRole = yield (0, utils_2.getWinningRoleAsync)(docToCheck, user, roles);
|
|
760
760
|
const { status, document } = winningRole
|
|
761
761
|
? yield (0, machines_1.checkValidation)(winningRole, {
|
|
762
762
|
type: validationType,
|
|
@@ -776,7 +776,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
|
|
|
776
776
|
emitMongoEvent('findOneAndUpdate');
|
|
777
777
|
return updateResult;
|
|
778
778
|
}
|
|
779
|
-
const readRole = (0, utils_2.
|
|
779
|
+
const readRole = yield (0, utils_2.getWinningRoleAsync)(updateResult, user, roles);
|
|
780
780
|
const readResult = readRole
|
|
781
781
|
? yield (0, machines_1.checkValidation)(readRole, {
|
|
782
782
|
type: 'read',
|
|
@@ -844,7 +844,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
|
|
|
844
844
|
const response = yield originalToArray();
|
|
845
845
|
const filteredResponse = yield Promise.all(response.map((currentDoc) => __awaiter(void 0, void 0, void 0, function* () {
|
|
846
846
|
var _a;
|
|
847
|
-
const winningRole = (0, utils_2.
|
|
847
|
+
const winningRole = yield (0, utils_2.getWinningRoleAsync)(currentDoc, user, roles);
|
|
848
848
|
logDebug('find winningRole', {
|
|
849
849
|
collection: collName,
|
|
850
850
|
userId: getUserId(user),
|
|
@@ -976,7 +976,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
|
|
|
976
976
|
const isValidChange = (change) => __awaiter(void 0, void 0, void 0, function* () {
|
|
977
977
|
const { fullDocument, updateDescription } = change;
|
|
978
978
|
const hasFullDocument = !!fullDocument;
|
|
979
|
-
const winningRole = (0, utils_2.
|
|
979
|
+
const winningRole = yield (0, utils_2.getWinningRoleAsync)(fullDocument, user, roles);
|
|
980
980
|
const fullDocumentValidation = winningRole
|
|
981
981
|
? yield (0, machines_1.checkValidation)(winningRole, {
|
|
982
982
|
type: 'read',
|
|
@@ -1088,7 +1088,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
|
|
|
1088
1088
|
(0, utils_3.checkDenyOperation)(normalizedRules, collection.collectionName, model_1.CRUD_OPERATIONS.CREATE);
|
|
1089
1089
|
// Validate each document against user's roles
|
|
1090
1090
|
const filteredItems = yield Promise.all(documents.map((currentDoc) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1091
|
-
const winningRole = (0, utils_2.
|
|
1091
|
+
const winningRole = yield (0, utils_2.getWinningRoleAsync)(currentDoc, user, roles);
|
|
1092
1092
|
const { status, document } = winningRole
|
|
1093
1093
|
? yield (0, machines_1.checkValidation)(winningRole, {
|
|
1094
1094
|
type: 'insert',
|
|
@@ -1133,7 +1133,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
|
|
|
1133
1133
|
const updatedPaths = getUpdatedPaths(normalizedData);
|
|
1134
1134
|
const docsToCheck = result.map((currentDoc) => applyDocumentUpdateOperators(currentDoc, normalizedData));
|
|
1135
1135
|
const filteredItems = yield Promise.all(docsToCheck.map((currentDoc, index) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1136
|
-
const winningRole = (0, utils_2.
|
|
1136
|
+
const winningRole = yield (0, utils_2.getWinningRoleAsync)(currentDoc, user, roles);
|
|
1137
1137
|
const { status, document } = winningRole
|
|
1138
1138
|
? yield (0, machines_1.checkValidation)(winningRole, {
|
|
1139
1139
|
type: 'write',
|
|
@@ -1187,7 +1187,7 @@ const getOperators = (mongo, { rules, dbName, collName, user, run_as_system, mon
|
|
|
1187
1187
|
const data = yield collection.find({ $and: formattedQuery }).toArray();
|
|
1188
1188
|
// Filter and validate each document based on user's roles
|
|
1189
1189
|
const filteredItems = yield Promise.all(data.map((currentDoc) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1190
|
-
const winningRole = (0, utils_2.
|
|
1190
|
+
const winningRole = yield (0, utils_2.getWinningRoleAsync)(currentDoc, user, roles);
|
|
1191
1191
|
const { status, document } = winningRole
|
|
1192
1192
|
? yield (0, machines_1.checkValidation)(winningRole, {
|
|
1193
1193
|
type: 'delete',
|
|
@@ -35,11 +35,11 @@ export declare const generateContextData: ({ user, services, app, rules, current
|
|
|
35
35
|
request: {
|
|
36
36
|
remoteIPAddress: string | undefined;
|
|
37
37
|
id?: string | undefined;
|
|
38
|
-
host?: string | undefined;
|
|
39
|
-
method?: string | undefined;
|
|
40
|
-
url?: string | undefined;
|
|
41
38
|
ips?: string[];
|
|
39
|
+
host?: string | undefined;
|
|
42
40
|
hostname?: string | undefined;
|
|
41
|
+
url?: string | undefined;
|
|
42
|
+
method?: string | undefined;
|
|
43
43
|
ip?: string | undefined;
|
|
44
44
|
};
|
|
45
45
|
user: unknown;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PermissionExpression } from './interface';
|
|
2
2
|
import { MachineContext } from './machines/interface';
|
|
3
|
+
export declare const evaluateExpandedExpression: (expression: unknown, params: MachineContext["params"], user?: MachineContext["user"]) => Promise<boolean>;
|
|
3
4
|
export declare const evaluateExpression: (params: MachineContext["params"], expression?: PermissionExpression, user?: MachineContext["user"]) => Promise<boolean>;
|
|
4
5
|
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/utils/roles/helpers.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/utils/roles/helpers.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AA+DrD,eAAO,MAAM,0BAA0B,GACrC,YAAY,OAAO,EACnB,QAAQ,cAAc,CAAC,QAAQ,CAAC,EAChC,OAAO,cAAc,CAAC,MAAM,CAAC,KAC5B,OAAO,CAAC,OAAO,CAwDjB,CAAA;AAED,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,cAAc,CAAC,QAAQ,CAAC,EAChC,aAAa,oBAAoB,EACjC,OAAO,cAAc,CAAC,MAAM,CAAC,KAC5B,OAAO,CAAC,OAAO,CAMjB,CAAA"}
|
|
@@ -12,13 +12,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.evaluateExpression = void 0;
|
|
15
|
+
exports.evaluateExpression = exports.evaluateExpandedExpression = void 0;
|
|
16
16
|
const services_1 = require("../../services");
|
|
17
17
|
const state_1 = require("../../state");
|
|
18
18
|
const context_1 = require("../context");
|
|
19
19
|
const rules_1 = require("../rules");
|
|
20
20
|
const utils_1 = __importDefault(require("../rules-matcher/utils"));
|
|
21
21
|
const functionsConditions = ['%%true', '%%false'];
|
|
22
|
+
const andConditions = ['$and', '%and'];
|
|
23
|
+
const orConditions = ['$or', '%or'];
|
|
22
24
|
const normalizeUserRole = (user) => {
|
|
23
25
|
if (!user)
|
|
24
26
|
return user;
|
|
@@ -34,17 +36,84 @@ const normalizeUserRole = (user) => {
|
|
|
34
36
|
? Object.assign(Object.assign({}, candidate), { role: customRole })
|
|
35
37
|
: user;
|
|
36
38
|
};
|
|
39
|
+
const buildEvaluationContext = (params, user) => {
|
|
40
|
+
var _a, _b, _c;
|
|
41
|
+
const normalizedUser = normalizeUserRole(user);
|
|
42
|
+
return Object.assign(Object.assign(Object.assign({}, ((_a = params.expansions) !== null && _a !== void 0 ? _a : {})), ((_b = params.cursor) !== null && _b !== void 0 ? _b : {})), { '%%root': params.cursor, '%%prevRoot': (_c = params.expansions) === null || _c === void 0 ? void 0 : _c['%%prevRoot'], '%%user': normalizedUser, '%%true': true, '%%false': false });
|
|
43
|
+
};
|
|
44
|
+
const getFunctionCondition = (expression) => {
|
|
45
|
+
if (!expression || typeof expression !== 'object' || Array.isArray(expression)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const entries = Object.entries(expression);
|
|
49
|
+
if (entries.length !== 1) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const [key, value] = entries[0];
|
|
53
|
+
if (!functionsConditions.includes(key)) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return Object.prototype.hasOwnProperty.call(value, '%function')
|
|
60
|
+
? [key, value]
|
|
61
|
+
: null;
|
|
62
|
+
};
|
|
63
|
+
const evaluateExpandedExpression = (expression, params, user) => __awaiter(void 0, void 0, void 0, function* () {
|
|
64
|
+
if (typeof expression === 'boolean') {
|
|
65
|
+
return expression;
|
|
66
|
+
}
|
|
67
|
+
if (!expression || typeof expression !== 'object') {
|
|
68
|
+
return Boolean(expression);
|
|
69
|
+
}
|
|
70
|
+
const block = expression;
|
|
71
|
+
const functionCondition = getFunctionCondition(block);
|
|
72
|
+
if (functionCondition) {
|
|
73
|
+
return evaluateComplexExpression(functionCondition, params, user);
|
|
74
|
+
}
|
|
75
|
+
const andKey = andConditions.find((key) => Object.prototype.hasOwnProperty.call(block, key));
|
|
76
|
+
if (andKey) {
|
|
77
|
+
const conditions = Array.isArray(block[andKey]) ? block[andKey] : [];
|
|
78
|
+
if (!conditions.length)
|
|
79
|
+
return true;
|
|
80
|
+
for (const condition of conditions) {
|
|
81
|
+
if (!(yield (0, exports.evaluateExpandedExpression)(condition, params, user))) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
const orKey = orConditions.find((key) => Object.prototype.hasOwnProperty.call(block, key));
|
|
88
|
+
if (orKey) {
|
|
89
|
+
const conditions = Array.isArray(block[orKey]) ? block[orKey] : [];
|
|
90
|
+
if (!conditions.length)
|
|
91
|
+
return true;
|
|
92
|
+
for (const condition of conditions) {
|
|
93
|
+
if (yield (0, exports.evaluateExpandedExpression)(condition, params, user)) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
const keys = Object.keys(block);
|
|
100
|
+
if (keys.length > 1) {
|
|
101
|
+
for (const key of keys) {
|
|
102
|
+
if (!(yield (0, exports.evaluateExpandedExpression)({ [key]: block[key] }, params, user))) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
return utils_1.default.checkRule(block, buildEvaluationContext(params, user), {});
|
|
109
|
+
});
|
|
110
|
+
exports.evaluateExpandedExpression = evaluateExpandedExpression;
|
|
37
111
|
const evaluateExpression = (params, expression, user) => __awaiter(void 0, void 0, void 0, function* () {
|
|
38
|
-
var _a;
|
|
39
112
|
if (!expression || typeof expression === 'boolean')
|
|
40
113
|
return !!expression;
|
|
41
|
-
const
|
|
42
|
-
const value = Object.assign(Object.assign(Object.assign({}, params.expansions), params.cursor), { '%%root': params.cursor, '%%prevRoot': (_a = params.expansions) === null || _a === void 0 ? void 0 : _a['%%prevRoot'], '%%user': normalizedUser, '%%true': true, '%%false': false });
|
|
114
|
+
const value = buildEvaluationContext(params, user);
|
|
43
115
|
const conditions = (0, rules_1.expandQuery)(expression, value);
|
|
44
|
-
|
|
45
|
-
return complexCondition
|
|
46
|
-
? yield evaluateComplexExpression(complexCondition, params, normalizedUser)
|
|
47
|
-
: utils_1.default.checkRule(conditions, value, {});
|
|
116
|
+
return (0, exports.evaluateExpandedExpression)(conditions, params, user);
|
|
48
117
|
});
|
|
49
118
|
exports.evaluateExpression = evaluateExpression;
|
|
50
119
|
const evaluateComplexExpression = (condition, params, user) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -12,6 +12,7 @@ import { LogMachineInfoParams } from './interface';
|
|
|
12
12
|
* @returns {Role | null} - Returns the first role that matches the `apply_when` condition, or `null` if none match.
|
|
13
13
|
*/
|
|
14
14
|
export declare const getWinningRole: (document: OptionalId<Document> | null, user: User, roles?: Role[]) => Role | null;
|
|
15
|
+
export declare const getWinningRoleAsync: (document: OptionalId<Document> | null, user: User, roles?: Role[]) => Promise<Role | null>;
|
|
15
16
|
/**
|
|
16
17
|
* Checks if the `apply_when` condition is valid for the given user and document.
|
|
17
18
|
*
|
|
@@ -22,6 +23,7 @@ export declare const getWinningRole: (document: OptionalId<Document> | null, use
|
|
|
22
23
|
* @returns {boolean} - Returns `true` if at least one valid rule is found, otherwise `false`.
|
|
23
24
|
*/
|
|
24
25
|
export declare const checkApplyWhen: (apply_when: Role["apply_when"], user: User, document: OptionalId<Document> | null) => boolean;
|
|
26
|
+
export declare const checkApplyWhenAsync: (apply_when: Role["apply_when"], user: User, document: OptionalId<Document> | null) => Promise<boolean>;
|
|
25
27
|
/**
|
|
26
28
|
* Logs machine step information if logging is enabled.
|
|
27
29
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/utils/roles/machines/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/utils/roles/machines/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAIzC,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AACnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAElD;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,UAAU,CAAC,QAAQ,CAAC,GAAG,IAAI,EACrC,MAAM,IAAI,EACV,QAAO,IAAI,EAAO,KACjB,IAAI,GAAG,IAQT,CAAA;AAED,eAAO,MAAM,mBAAmB,GAC9B,UAAU,UAAU,CAAC,QAAQ,CAAC,GAAG,IAAI,EACrC,MAAM,IAAI,EACV,QAAO,IAAI,EAAO,KACjB,OAAO,CAAC,IAAI,GAAG,IAAI,CAQrB,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GACzB,YAAY,IAAI,CAAC,YAAY,CAAC,EAC9B,MAAM,IAAI,EACV,UAAU,UAAU,CAAC,QAAQ,CAAC,GAAG,IAAI,YAQtC,CAAA;AAED,eAAO,MAAM,mBAAmB,GAC9B,YAAY,IAAI,CAAC,YAAY,CAAC,EAC9B,MAAM,IAAI,EACV,UAAU,UAAU,CAAC,QAAQ,CAAC,GAAG,IAAI,qBActC,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,GAAI,sCAK5B,oBAAoB,SAEtB,CAAA"}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
2
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.logMachineInfo = exports.checkApplyWhen = exports.getWinningRole = void 0;
|
|
12
|
+
exports.logMachineInfo = exports.checkApplyWhenAsync = exports.checkApplyWhen = exports.getWinningRoleAsync = exports.getWinningRole = void 0;
|
|
4
13
|
const utils_1 = require("../../../services/mongodb-atlas/utils");
|
|
14
|
+
const helpers_1 = require("../helpers");
|
|
5
15
|
/**
|
|
6
16
|
* Determines the first applicable role for a given user and document.
|
|
7
17
|
*
|
|
@@ -22,6 +32,17 @@ const getWinningRole = (document, user, roles = []) => {
|
|
|
22
32
|
return null;
|
|
23
33
|
};
|
|
24
34
|
exports.getWinningRole = getWinningRole;
|
|
35
|
+
const getWinningRoleAsync = (document_1, user_1, ...args_1) => __awaiter(void 0, [document_1, user_1, ...args_1], void 0, function* (document, user, roles = []) {
|
|
36
|
+
if (!roles.length)
|
|
37
|
+
return null;
|
|
38
|
+
for (const role of roles) {
|
|
39
|
+
if (yield (0, exports.checkApplyWhenAsync)(role.apply_when, user, document)) {
|
|
40
|
+
return role;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
});
|
|
45
|
+
exports.getWinningRoleAsync = getWinningRoleAsync;
|
|
25
46
|
/**
|
|
26
47
|
* Checks if the `apply_when` condition is valid for the given user and document.
|
|
27
48
|
*
|
|
@@ -40,6 +61,17 @@ const checkApplyWhen = (apply_when, user, document) => {
|
|
|
40
61
|
return !!validRule.length;
|
|
41
62
|
};
|
|
42
63
|
exports.checkApplyWhen = checkApplyWhen;
|
|
64
|
+
const checkApplyWhenAsync = (apply_when, user, document) => __awaiter(void 0, void 0, void 0, function* () {
|
|
65
|
+
return (0, helpers_1.evaluateExpression)({
|
|
66
|
+
type: 'read',
|
|
67
|
+
roles: [],
|
|
68
|
+
cursor: document,
|
|
69
|
+
expansions: {
|
|
70
|
+
'%%prevRoot': undefined
|
|
71
|
+
}
|
|
72
|
+
}, apply_when, user);
|
|
73
|
+
});
|
|
74
|
+
exports.checkApplyWhenAsync = checkApplyWhenAsync;
|
|
43
75
|
/**
|
|
44
76
|
* Logs machine step information if logging is enabled.
|
|
45
77
|
*
|
package/package.json
CHANGED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { ObjectId } from 'bson'
|
|
2
|
+
import { GenerateContext } from '../../../utils/context'
|
|
3
|
+
import { StateManager } from '../../../state'
|
|
4
|
+
import { TRIGGER_HANDLERS } from '../utils'
|
|
5
|
+
|
|
6
|
+
jest.mock('../../../utils/context', () => ({
|
|
7
|
+
GenerateContext: jest.fn()
|
|
8
|
+
}))
|
|
9
|
+
|
|
10
|
+
jest.mock('../../../state', () => ({
|
|
11
|
+
StateManager: {
|
|
12
|
+
select: jest.fn()
|
|
13
|
+
}
|
|
14
|
+
}))
|
|
15
|
+
|
|
16
|
+
const mockedGenerateContext = jest.mocked(GenerateContext)
|
|
17
|
+
const mockedStateSelect = StateManager.select as jest.Mock
|
|
18
|
+
|
|
19
|
+
describe('TRIGGER_HANDLERS.DATABASE', () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
mockedGenerateContext.mockReset()
|
|
22
|
+
mockedGenerateContext.mockResolvedValue(undefined)
|
|
23
|
+
mockedStateSelect.mockReset()
|
|
24
|
+
mockedStateSelect.mockReturnValue(undefined)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('preserves BSON ObjectId values in fullDocument before invoking the trigger function', async () => {
|
|
28
|
+
const changeListeners: Record<string, (...args: any[]) => unknown> = {}
|
|
29
|
+
const close = jest.fn(async () => undefined)
|
|
30
|
+
const watch = jest.fn(() => ({
|
|
31
|
+
on: jest.fn((event: string, listener: (...args: any[]) => unknown) => {
|
|
32
|
+
changeListeners[event] = listener
|
|
33
|
+
}),
|
|
34
|
+
close
|
|
35
|
+
}))
|
|
36
|
+
|
|
37
|
+
const collection = { watch }
|
|
38
|
+
const db = jest.fn(() => ({ collection: jest.fn(() => collection) }))
|
|
39
|
+
const client = { db }
|
|
40
|
+
|
|
41
|
+
const app = {
|
|
42
|
+
mongo: {
|
|
43
|
+
changestream: { client }
|
|
44
|
+
},
|
|
45
|
+
server: {
|
|
46
|
+
once: jest.fn()
|
|
47
|
+
}
|
|
48
|
+
} as any
|
|
49
|
+
|
|
50
|
+
await TRIGGER_HANDLERS.DATABASE({
|
|
51
|
+
config: {
|
|
52
|
+
database: 'flowerbase-test',
|
|
53
|
+
collection: 'activityLogs',
|
|
54
|
+
operation_types: ['INSERT'],
|
|
55
|
+
full_document: true,
|
|
56
|
+
full_document_before_change: false,
|
|
57
|
+
match: {},
|
|
58
|
+
project: {}
|
|
59
|
+
} as any,
|
|
60
|
+
triggerHandler: { code: 'module.exports = async function () {}' } as any,
|
|
61
|
+
functionsList: {
|
|
62
|
+
logTriggerEvent: { code: 'module.exports = async function () {}' }
|
|
63
|
+
} as any,
|
|
64
|
+
services: {} as any,
|
|
65
|
+
app,
|
|
66
|
+
triggerName: 'log-trigger-event',
|
|
67
|
+
triggerType: 'DATABASE',
|
|
68
|
+
functionName: 'logTriggerEvent'
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const documentId = new ObjectId('507f1f77bcf86cd799439011')
|
|
72
|
+
const ownerId = new ObjectId('507f191e810c19729de860ea')
|
|
73
|
+
|
|
74
|
+
await changeListeners.change({
|
|
75
|
+
clusterTime: new Date(),
|
|
76
|
+
operationType: 'insert',
|
|
77
|
+
ns: { db: 'flowerbase-test', coll: 'activityLogs' },
|
|
78
|
+
documentKey: { _id: documentId },
|
|
79
|
+
fullDocument: {
|
|
80
|
+
_id: documentId,
|
|
81
|
+
ownerId
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
expect(mockedGenerateContext).toHaveBeenCalledTimes(1)
|
|
86
|
+
expect(mockedGenerateContext).toHaveBeenCalledWith(
|
|
87
|
+
expect.objectContaining({
|
|
88
|
+
deserializeArgs: false,
|
|
89
|
+
args: [
|
|
90
|
+
expect.objectContaining({
|
|
91
|
+
documentKey: expect.objectContaining({
|
|
92
|
+
_id: expect.any(ObjectId)
|
|
93
|
+
}),
|
|
94
|
+
fullDocument: expect.objectContaining({
|
|
95
|
+
_id: expect.any(ObjectId),
|
|
96
|
+
ownerId: expect.any(ObjectId)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
]
|
|
100
|
+
})
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
const payload = mockedGenerateContext.mock.calls[0][0].args[0] as {
|
|
104
|
+
documentKey: { _id: ObjectId }
|
|
105
|
+
fullDocument: { _id: ObjectId; ownerId: ObjectId }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
expect(payload.documentKey._id.toHexString()).toBe(documentId.toHexString())
|
|
109
|
+
expect(payload.fullDocument._id.toHexString()).toBe(documentId.toHexString())
|
|
110
|
+
expect(payload.fullDocument.ownerId.toHexString()).toBe(ownerId.toHexString())
|
|
111
|
+
})
|
|
112
|
+
})
|