@flowerforce/flowerbase 1.0.1-beta.3
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/CHANGELOG.md +0 -0
- package/LICENSE +3 -0
- package/README.md +18 -0
- package/dist/auth/controller.d.ts +8 -0
- package/dist/auth/controller.d.ts.map +1 -0
- package/dist/auth/controller.js +76 -0
- package/dist/auth/dtos.d.ts +6 -0
- package/dist/auth/dtos.d.ts.map +1 -0
- package/dist/auth/dtos.js +2 -0
- package/dist/auth/plugins/jwt.d.ts +14 -0
- package/dist/auth/plugins/jwt.d.ts.map +1 -0
- package/dist/auth/plugins/jwt.js +68 -0
- package/dist/auth/providers/local-userpass/controller.d.ts +8 -0
- package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -0
- package/dist/auth/providers/local-userpass/controller.js +184 -0
- package/dist/auth/providers/local-userpass/dtos.d.ts +35 -0
- package/dist/auth/providers/local-userpass/dtos.d.ts.map +1 -0
- package/dist/auth/providers/local-userpass/dtos.js +2 -0
- package/dist/auth/utils.d.ts +126 -0
- package/dist/auth/utils.d.ts.map +1 -0
- package/dist/auth/utils.js +122 -0
- package/dist/constants.d.ts +18 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +34 -0
- package/dist/features/endpoints/index.d.ts +10 -0
- package/dist/features/endpoints/index.d.ts.map +1 -0
- package/dist/features/endpoints/index.js +31 -0
- package/dist/features/endpoints/interface.d.ts +27 -0
- package/dist/features/endpoints/interface.d.ts.map +1 -0
- package/dist/features/endpoints/interface.js +2 -0
- package/dist/features/endpoints/utils.d.ts +31 -0
- package/dist/features/endpoints/utils.d.ts.map +1 -0
- package/dist/features/endpoints/utils.js +85 -0
- package/dist/features/functions/controller.d.ts +9 -0
- package/dist/features/functions/controller.d.ts.map +1 -0
- package/dist/features/functions/controller.js +88 -0
- package/dist/features/functions/dtos.d.ts +34 -0
- package/dist/features/functions/dtos.d.ts.map +1 -0
- package/dist/features/functions/dtos.js +2 -0
- package/dist/features/functions/index.d.ts +9 -0
- package/dist/features/functions/index.d.ts.map +1 -0
- package/dist/features/functions/index.js +28 -0
- package/dist/features/functions/interface.d.ts +32 -0
- package/dist/features/functions/interface.d.ts.map +1 -0
- package/dist/features/functions/interface.js +2 -0
- package/dist/features/functions/utils.d.ts +23 -0
- package/dist/features/functions/utils.d.ts.map +1 -0
- package/dist/features/functions/utils.js +75 -0
- package/dist/features/rules/index.d.ts +1 -0
- package/dist/features/rules/index.d.ts.map +1 -0
- package/dist/features/rules/index.js +1 -0
- package/dist/features/rules/interface.d.ts +22 -0
- package/dist/features/rules/interface.d.ts.map +1 -0
- package/dist/features/rules/interface.js +2 -0
- package/dist/features/rules/utils.d.ts +3 -0
- package/dist/features/rules/utils.d.ts.map +1 -0
- package/dist/features/rules/utils.js +31 -0
- package/dist/features/triggers/dtos.d.ts +9 -0
- package/dist/features/triggers/dtos.d.ts.map +1 -0
- package/dist/features/triggers/dtos.js +2 -0
- package/dist/features/triggers/index.d.ts +10 -0
- package/dist/features/triggers/index.d.ts.map +1 -0
- package/dist/features/triggers/index.js +57 -0
- package/dist/features/triggers/interface.d.ts +44 -0
- package/dist/features/triggers/interface.d.ts.map +1 -0
- package/dist/features/triggers/interface.js +2 -0
- package/dist/features/triggers/utils.d.ts +16 -0
- package/dist/features/triggers/utils.d.ts.map +1 -0
- package/dist/features/triggers/utils.js +153 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +84 -0
- package/dist/model.d.ts +2 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +2 -0
- package/dist/services/api/index.d.ts +36 -0
- package/dist/services/api/index.d.ts.map +1 -0
- package/dist/services/api/index.js +36 -0
- package/dist/services/api/model.d.ts +33 -0
- package/dist/services/api/model.d.ts.map +1 -0
- package/dist/services/api/model.js +2 -0
- package/dist/services/api/utils.d.ts +16 -0
- package/dist/services/api/utils.d.ts.map +1 -0
- package/dist/services/api/utils.js +45 -0
- package/dist/services/aws/index.d.ts +13 -0
- package/dist/services/aws/index.d.ts.map +1 -0
- package/dist/services/aws/index.js +50 -0
- package/dist/services/index.d.ts +41 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +14 -0
- package/dist/services/interface.d.ts +3 -0
- package/dist/services/interface.d.ts.map +1 -0
- package/dist/services/interface.js +2 -0
- package/dist/services/mongodb-atlas/index.d.ts +4 -0
- package/dist/services/mongodb-atlas/index.d.ts.map +1 -0
- package/dist/services/mongodb-atlas/index.js +483 -0
- package/dist/services/mongodb-atlas/model.d.ts +39 -0
- package/dist/services/mongodb-atlas/model.d.ts.map +1 -0
- package/dist/services/mongodb-atlas/model.js +2 -0
- package/dist/services/mongodb-atlas/utils.d.ts +8 -0
- package/dist/services/mongodb-atlas/utils.d.ts.map +1 -0
- package/dist/services/mongodb-atlas/utils.js +33 -0
- package/dist/state.d.ts +6 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +18 -0
- package/dist/utils/context/helpers.d.ts +74 -0
- package/dist/utils/context/helpers.d.ts.map +1 -0
- package/dist/utils/context/helpers.js +60 -0
- package/dist/utils/context/index.d.ts +14 -0
- package/dist/utils/context/index.d.ts.map +1 -0
- package/dist/utils/context/index.js +50 -0
- package/dist/utils/context/interface.d.ts +18 -0
- package/dist/utils/context/interface.d.ts.map +1 -0
- package/dist/utils/context/interface.js +2 -0
- package/dist/utils/crypto/index.d.ts +19 -0
- package/dist/utils/crypto/index.d.ts.map +1 -0
- package/dist/utils/crypto/index.js +50 -0
- package/dist/utils/helpers/someAsync.d.ts +12 -0
- package/dist/utils/helpers/someAsync.d.ts.map +1 -0
- package/dist/utils/helpers/someAsync.js +56 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +11 -0
- package/dist/utils/initializer/exposeRoutes.d.ts +8 -0
- package/dist/utils/initializer/exposeRoutes.d.ts.map +1 -0
- package/dist/utils/initializer/exposeRoutes.js +41 -0
- package/dist/utils/initializer/registerPlugins.d.ts +19 -0
- package/dist/utils/initializer/registerPlugins.d.ts.map +1 -0
- package/dist/utils/initializer/registerPlugins.js +84 -0
- package/dist/utils/roles/helpers.d.ts +4 -0
- package/dist/utils/roles/helpers.d.ts.map +1 -0
- package/dist/utils/roles/helpers.js +47 -0
- package/dist/utils/roles/interface.d.ts +33 -0
- package/dist/utils/roles/interface.d.ts.map +1 -0
- package/dist/utils/roles/interface.js +2 -0
- package/dist/utils/roles/machines/commonValidators.d.ts +6 -0
- package/dist/utils/roles/machines/commonValidators.d.ts.map +1 -0
- package/dist/utils/roles/machines/commonValidators.js +34 -0
- package/dist/utils/roles/machines/index.d.ts +14 -0
- package/dist/utils/roles/machines/index.d.ts.map +1 -0
- package/dist/utils/roles/machines/index.js +27 -0
- package/dist/utils/roles/machines/interface.d.ts +46 -0
- package/dist/utils/roles/machines/interface.d.ts.map +1 -0
- package/dist/utils/roles/machines/interface.js +2 -0
- package/dist/utils/roles/machines/machine.d.ts +15 -0
- package/dist/utils/roles/machines/machine.d.ts.map +1 -0
- package/dist/utils/roles/machines/machine.js +97 -0
- package/dist/utils/roles/machines/read/A/index.d.ts +3 -0
- package/dist/utils/roles/machines/read/A/index.d.ts.map +1 -0
- package/dist/utils/roles/machines/read/A/index.js +27 -0
- package/dist/utils/roles/machines/read/B/index.d.ts +3 -0
- package/dist/utils/roles/machines/read/B/index.d.ts.map +1 -0
- package/dist/utils/roles/machines/read/B/index.js +36 -0
- package/dist/utils/roles/machines/read/C/index.d.ts +3 -0
- package/dist/utils/roles/machines/read/C/index.d.ts.map +1 -0
- package/dist/utils/roles/machines/read/C/index.js +38 -0
- package/dist/utils/roles/machines/read/D/index.d.ts +3 -0
- package/dist/utils/roles/machines/read/D/index.d.ts.map +1 -0
- package/dist/utils/roles/machines/read/D/index.js +26 -0
- package/dist/utils/roles/machines/read/D/validators.d.ts +4 -0
- package/dist/utils/roles/machines/read/D/validators.d.ts.map +1 -0
- package/dist/utils/roles/machines/read/D/validators.js +24 -0
- package/dist/utils/roles/machines/read/index.d.ts +2 -0
- package/dist/utils/roles/machines/read/index.d.ts.map +1 -0
- package/dist/utils/roles/machines/read/index.js +8 -0
- package/dist/utils/roles/machines/utils.d.ts +37 -0
- package/dist/utils/roles/machines/utils.d.ts.map +1 -0
- package/dist/utils/roles/machines/utils.js +54 -0
- package/dist/utils/roles/machines/write/A/index.d.ts +3 -0
- package/dist/utils/roles/machines/write/A/index.d.ts.map +1 -0
- package/dist/utils/roles/machines/write/A/index.js +29 -0
- package/dist/utils/roles/machines/write/B/index.d.ts +3 -0
- package/dist/utils/roles/machines/write/B/index.d.ts.map +1 -0
- package/dist/utils/roles/machines/write/B/index.js +47 -0
- package/dist/utils/roles/machines/write/C/index.d.ts +3 -0
- package/dist/utils/roles/machines/write/C/index.d.ts.map +1 -0
- package/dist/utils/roles/machines/write/C/index.js +26 -0
- package/dist/utils/roles/machines/write/C/validators.d.ts +4 -0
- package/dist/utils/roles/machines/write/C/validators.d.ts.map +1 -0
- package/dist/utils/roles/machines/write/C/validators.js +24 -0
- package/dist/utils/roles/machines/write/index.d.ts +2 -0
- package/dist/utils/roles/machines/write/index.d.ts.map +1 -0
- package/dist/utils/roles/machines/write/index.js +7 -0
- package/dist/utils/rules-matcher/interface.d.ts +338 -0
- package/dist/utils/rules-matcher/interface.d.ts.map +1 -0
- package/dist/utils/rules-matcher/interface.js +26 -0
- package/dist/utils/rules-matcher/utils.d.ts +11 -0
- package/dist/utils/rules-matcher/utils.d.ts.map +1 -0
- package/dist/utils/rules-matcher/utils.js +214 -0
- package/dist/utils/rules.d.ts +2 -0
- package/dist/utils/rules.d.ts.map +1 -0
- package/dist/utils/rules.js +22 -0
- package/jest.config.ts +24 -0
- package/package.json +63 -0
- package/project.json +10 -0
- package/rollup.config.js +17 -0
- package/src/auth/controller.ts +78 -0
- package/src/auth/dtos.ts +6 -0
- package/src/auth/plugins/jwt.ts +68 -0
- package/src/auth/providers/local-userpass/controller.ts +226 -0
- package/src/auth/providers/local-userpass/dtos.ts +40 -0
- package/src/auth/utils.ts +165 -0
- package/src/babel.config.json +3 -0
- package/src/constants.ts +22 -0
- package/src/fastify.d.ts +28 -0
- package/src/features/endpoints/index.ts +27 -0
- package/src/features/endpoints/interface.ts +29 -0
- package/src/features/endpoints/utils.ts +72 -0
- package/src/features/functions/controller.ts +102 -0
- package/src/features/functions/dtos.ts +41 -0
- package/src/features/functions/index.ts +21 -0
- package/src/features/functions/interface.ts +38 -0
- package/src/features/functions/utils.ts +82 -0
- package/src/features/rules/index.tsx +0 -0
- package/src/features/rules/interface.ts +24 -0
- package/src/features/rules/utils.ts +20 -0
- package/src/features/triggers/dtos.ts +9 -0
- package/src/features/triggers/index.ts +34 -0
- package/src/features/triggers/interface.ts +44 -0
- package/src/features/triggers/utils.ts +157 -0
- package/src/global.d.ts +0 -0
- package/src/index.ts +75 -0
- package/src/model.ts +1 -0
- package/src/services/api/index.ts +50 -0
- package/src/services/api/model.ts +38 -0
- package/src/services/api/utils.ts +39 -0
- package/src/services/aws/index.ts +48 -0
- package/src/services/index.ts +9 -0
- package/src/services/interface.ts +3 -0
- package/src/services/mongodb-atlas/index.ts +569 -0
- package/src/services/mongodb-atlas/model.ts +67 -0
- package/src/services/mongodb-atlas/utils.ts +44 -0
- package/src/state.ts +24 -0
- package/src/utils/__tests__/STEP_A_STATES.test.ts +54 -0
- package/src/utils/__tests__/STEP_B_STATES.test.ts +113 -0
- package/src/utils/__tests__/STEP_C_STATES.test.ts +87 -0
- package/src/utils/__tests__/STEP_D_STATES.test.ts +93 -0
- package/src/utils/__tests__/checkAdditionalFieldsFn.test.ts +45 -0
- package/src/utils/__tests__/checkApplyWhen.test.ts +49 -0
- package/src/utils/__tests__/checkFieldsPropertyExists.test.ts +47 -0
- package/src/utils/__tests__/checkIsValidFieldNameFn.test.ts +190 -0
- package/src/utils/__tests__/comparePassword.test.ts +38 -0
- package/src/utils/__tests__/evaluateDocumentsFiltersReadFn.test.ts +57 -0
- package/src/utils/__tests__/evaluateDocumentsFiltersWriteFn.test.ts +57 -0
- package/src/utils/__tests__/evaluateTopLevelReadFn.test.ts +58 -0
- package/src/utils/__tests__/evaluateTopLevelWriteFn.test.ts +66 -0
- package/src/utils/__tests__/exposeRoutes.test.ts +65 -0
- package/src/utils/__tests__/generateContextData.test.ts +75 -0
- package/src/utils/__tests__/getDefaultRule.test.ts +29 -0
- package/src/utils/__tests__/getKey.test.ts +12 -0
- package/src/utils/__tests__/getKeys.test.ts +11 -0
- package/src/utils/__tests__/getWinningRole.test.ts +66 -0
- package/src/utils/__tests__/hashPassword.test.ts +28 -0
- package/src/utils/__tests__/isEmpty.test.ts +17 -0
- package/src/utils/__tests__/logMachineInfo.test.ts +15 -0
- package/src/utils/__tests__/operators.test.ts +99 -0
- package/src/utils/__tests__/readFileContent.test.ts +35 -0
- package/src/utils/__tests__/registerPlugins.test.ts +59 -0
- package/src/utils/__tests__/rule.test.ts +51 -0
- package/src/utils/__tests__/rulesMatcherInterfaces.test.ts +57 -0
- package/src/utils/__tests__/rulesMatcherUtils.test.ts +56 -0
- package/src/utils/__tests__/someAsync.test.ts +55 -0
- package/src/utils/context/helpers.ts +71 -0
- package/src/utils/context/index.ts +52 -0
- package/src/utils/context/interface.ts +19 -0
- package/src/utils/crypto/index.ts +36 -0
- package/src/utils/helpers/someAsync.ts +24 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/initializer/exposeRoutes.ts +26 -0
- package/src/utils/initializer/registerPlugins.ts +97 -0
- package/src/utils/roles/helpers.ts +47 -0
- package/src/utils/roles/interface.ts +42 -0
- package/src/utils/roles/machines/commonValidators.ts +24 -0
- package/src/utils/roles/machines/index.ts +20 -0
- package/src/utils/roles/machines/interface.ts +46 -0
- package/src/utils/roles/machines/machine.ts +85 -0
- package/src/utils/roles/machines/read/A/index.ts +19 -0
- package/src/utils/roles/machines/read/B/index.ts +31 -0
- package/src/utils/roles/machines/read/C/index.ts +30 -0
- package/src/utils/roles/machines/read/D/index.ts +20 -0
- package/src/utils/roles/machines/read/D/validators.ts +24 -0
- package/src/utils/roles/machines/read/index.ts +6 -0
- package/src/utils/roles/machines/utils.ts +54 -0
- package/src/utils/roles/machines/write/A/index.ts +25 -0
- package/src/utils/roles/machines/write/B/index.ts +43 -0
- package/src/utils/roles/machines/write/C/index.ts +20 -0
- package/src/utils/roles/machines/write/C/validators.ts +24 -0
- package/src/utils/roles/machines/write/index.ts +5 -0
- package/src/utils/rules-matcher/interface.ts +365 -0
- package/src/utils/rules-matcher/utils.ts +281 -0
- package/src/utils/rules.ts +19 -0
- package/tsconfig.json +28 -0
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
import { EventEmitterAsyncResourceOptions } from 'events'
|
|
2
|
+
import isEqual from 'lodash/isEqual'
|
|
3
|
+
import { Collection, Document, EventsDescription, FindCursor, WithId } from 'mongodb'
|
|
4
|
+
import { checkValidation } from '../../utils/roles/machines'
|
|
5
|
+
import { getWinningRole } from '../../utils/roles/machines/utils'
|
|
6
|
+
import { GetOperatorsFunction, MongodbAtlasFunction } from './model'
|
|
7
|
+
import { getFormattedQuery } from './utils'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
//TODO aggiungere no-sql inject security
|
|
12
|
+
const getOperators: GetOperatorsFunction = (
|
|
13
|
+
collection,
|
|
14
|
+
{ rules = {}, collName, user, run_as_system }
|
|
15
|
+
) => ({
|
|
16
|
+
/**
|
|
17
|
+
* Finds a single document in a MongoDB collection with optional role-based filtering and validation.
|
|
18
|
+
*
|
|
19
|
+
* @param {Filter<Document>} query - The MongoDB query used to match the document.
|
|
20
|
+
* @returns {Promise<Document | {} | null>} A promise resolving to the document if found and permitted, an empty object if access is denied, or `null` if not found.
|
|
21
|
+
*
|
|
22
|
+
* @description
|
|
23
|
+
* If `run_as_system` is enabled, the function behaves like a standard `collection.findOne(query)` with no access checks.
|
|
24
|
+
* Otherwise:
|
|
25
|
+
* - Merges the provided query with any access control filters using `getFormattedQuery`.
|
|
26
|
+
* - Attempts to find the document using the formatted query.
|
|
27
|
+
* - Determines the user's role via `getWinningRole`.
|
|
28
|
+
* - Validates the result using `checkValidation` to ensure read permission.
|
|
29
|
+
* - If validation fails, returns an empty object; otherwise returns the validated document.
|
|
30
|
+
*/
|
|
31
|
+
findOne: async (query) => {
|
|
32
|
+
if (!run_as_system) {
|
|
33
|
+
const { filters, roles } = rules[collName] || {}
|
|
34
|
+
|
|
35
|
+
// Apply access control filters to the query
|
|
36
|
+
const formattedQuery = getFormattedQuery(filters, query, user)
|
|
37
|
+
|
|
38
|
+
const result = await collection.findOne({ $and: formattedQuery })
|
|
39
|
+
|
|
40
|
+
const winningRole = getWinningRole(result, user, roles)
|
|
41
|
+
|
|
42
|
+
const { status, document } = winningRole
|
|
43
|
+
? await checkValidation(winningRole, {
|
|
44
|
+
type: "read",
|
|
45
|
+
roles,
|
|
46
|
+
cursor: result,
|
|
47
|
+
expansions: {},
|
|
48
|
+
}, user)
|
|
49
|
+
: { status: true, document: result }
|
|
50
|
+
|
|
51
|
+
// Return validated document or empty object if not permitted
|
|
52
|
+
return Promise.resolve(status ? document : {})
|
|
53
|
+
}
|
|
54
|
+
// System mode: no validation applied
|
|
55
|
+
return collection.findOne(query)
|
|
56
|
+
},
|
|
57
|
+
/**
|
|
58
|
+
* Deletes a single document from a MongoDB collection with optional role-based validation.
|
|
59
|
+
*
|
|
60
|
+
* @param {Filter<Document>} [query={}] - The MongoDB query used to match the document to delete.
|
|
61
|
+
* @returns {Promise<DeleteResult>} A promise resolving to the result of the delete operation.
|
|
62
|
+
*
|
|
63
|
+
* @throws {Error} If the user is not authorized to delete the document.
|
|
64
|
+
*
|
|
65
|
+
* @description
|
|
66
|
+
* If `run_as_system` is enabled, the function deletes the document directly using `collection.deleteOne(query)`.
|
|
67
|
+
* Otherwise:
|
|
68
|
+
* - Applies role-based and custom filters to the query using `getFormattedQuery`.
|
|
69
|
+
* - Retrieves the document using `findOne` to validate user permissions.
|
|
70
|
+
* - Checks if the user has the appropriate role to perform a delete via `checkValidation`.
|
|
71
|
+
* - If validation fails, throws an error.
|
|
72
|
+
* - If validation passes, deletes the document using the filtered query.
|
|
73
|
+
*/
|
|
74
|
+
deleteOne: async (query = {}) => {
|
|
75
|
+
if (!run_as_system) {
|
|
76
|
+
const { filters, roles } = rules[collName] || {}
|
|
77
|
+
|
|
78
|
+
// Apply access control filters
|
|
79
|
+
const formattedQuery = getFormattedQuery(filters, query, user)
|
|
80
|
+
|
|
81
|
+
// Retrieve the document to check permissions before deleting
|
|
82
|
+
const result = await collection.findOne(formattedQuery)
|
|
83
|
+
const winningRole = getWinningRole(result, user, roles)
|
|
84
|
+
|
|
85
|
+
const { status } = winningRole
|
|
86
|
+
? await checkValidation(winningRole, {
|
|
87
|
+
type: "delete",
|
|
88
|
+
roles,
|
|
89
|
+
cursor: result,
|
|
90
|
+
expansions: {},
|
|
91
|
+
}, user)
|
|
92
|
+
: { status: true }
|
|
93
|
+
|
|
94
|
+
if (!status) {
|
|
95
|
+
throw new Error('Delete not permitted')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return collection.deleteOne(formattedQuery)
|
|
99
|
+
}
|
|
100
|
+
// System mode: bypass access control
|
|
101
|
+
return collection.deleteOne(query)
|
|
102
|
+
},
|
|
103
|
+
/**
|
|
104
|
+
* Inserts a single document into a MongoDB collection with optional role-based validation.
|
|
105
|
+
*
|
|
106
|
+
* @param {OptionalId<Document>} data - The document to insert.
|
|
107
|
+
* @param {InsertOneOptions} [options] - Optional settings for the insert operation, such as `writeConcern`.
|
|
108
|
+
* @returns {Promise<InsertOneResult<Document>>} A promise resolving to the result of the insert operation.
|
|
109
|
+
*
|
|
110
|
+
* @throws {Error} If the user is not authorized to insert the document.
|
|
111
|
+
*
|
|
112
|
+
* @description
|
|
113
|
+
* If `run_as_system` is enabled, the document is inserted directly without any validation.
|
|
114
|
+
* Otherwise:
|
|
115
|
+
* - Determines the appropriate user role using `getWinningRole`.
|
|
116
|
+
* - Validates the insert operation using `checkValidation`.
|
|
117
|
+
* - If validation fails, an error is thrown.
|
|
118
|
+
* - If validation passes, the document is inserted.
|
|
119
|
+
*
|
|
120
|
+
* This ensures that only users with the correct permissions can insert data into the collection.
|
|
121
|
+
*/
|
|
122
|
+
insertOne: async (data, options) => {
|
|
123
|
+
const { roles } = rules[collName] || {}
|
|
124
|
+
|
|
125
|
+
if (!run_as_system) {
|
|
126
|
+
const winningRole = getWinningRole(data, user, roles)
|
|
127
|
+
|
|
128
|
+
const { status, document } = winningRole
|
|
129
|
+
? await checkValidation(winningRole, {
|
|
130
|
+
type: "insert",
|
|
131
|
+
roles,
|
|
132
|
+
cursor: data,
|
|
133
|
+
expansions: {},
|
|
134
|
+
}, user)
|
|
135
|
+
: { status: true, document: data }
|
|
136
|
+
|
|
137
|
+
if (!status || !isEqual(data, document)) {
|
|
138
|
+
throw new Error('Insert not permitted')
|
|
139
|
+
}
|
|
140
|
+
return collection.insertOne(data, options)
|
|
141
|
+
}
|
|
142
|
+
// System mode: insert without validation
|
|
143
|
+
return collection.insertOne(data, options)
|
|
144
|
+
},
|
|
145
|
+
/**
|
|
146
|
+
* Updates a single document in a MongoDB collection with optional role-based validation.
|
|
147
|
+
*
|
|
148
|
+
* @param {Filter<Document>} query - The MongoDB query used to match the document to update.
|
|
149
|
+
* @param {UpdateFilter<Document> | Partial<Document>} data - The update operations or replacement document.
|
|
150
|
+
* @param {UpdateOptions} [options] - Optional settings for the update operation.
|
|
151
|
+
* @returns {Promise<UpdateResult>} A promise resolving to the result of the update operation.
|
|
152
|
+
*
|
|
153
|
+
* @throws {Error} If the user is not authorized to update the document.
|
|
154
|
+
*
|
|
155
|
+
* @description
|
|
156
|
+
* If `run_as_system` is enabled, the function directly updates the document using `collection.updateOne(query, data, options)`.
|
|
157
|
+
* Otherwise, it follows these steps:
|
|
158
|
+
* - Applies access control filters to the query using `getFormattedQuery`.
|
|
159
|
+
* - Retrieves the document using `findOne` to check if it exists and whether the user has permission to modify it.
|
|
160
|
+
* - Determines the user's role via `getWinningRole`.
|
|
161
|
+
* - Flattens update operators (`$set`, `$inc`, etc.) if present to extract the final modified fields.
|
|
162
|
+
* - Validates the update data using `checkValidation` to ensure compliance with role-based rules.
|
|
163
|
+
* - Ensures that no unauthorized modifications occur by comparing the validated document with the intended changes.
|
|
164
|
+
* - If validation fails, throws an error; otherwise, updates the document.
|
|
165
|
+
*/
|
|
166
|
+
updateOne: async (query, data, options) => {
|
|
167
|
+
if (!run_as_system) {
|
|
168
|
+
const { filters, roles } = rules[collName] || {}
|
|
169
|
+
// Apply access control filters
|
|
170
|
+
const formattedQuery = getFormattedQuery(filters, query, user)
|
|
171
|
+
|
|
172
|
+
// Retrieve the document to check permissions before updating
|
|
173
|
+
const result = await collection.findOne({ $and: formattedQuery })
|
|
174
|
+
if (!result) {
|
|
175
|
+
throw new Error('Update not permitted')
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const winningRole = getWinningRole(result, user, roles)
|
|
179
|
+
|
|
180
|
+
// Check if the update data contains MongoDB update operators (e.g., $set, $inc)
|
|
181
|
+
const hasOperators = Object.keys(data).some(key => key.startsWith("$"))
|
|
182
|
+
|
|
183
|
+
// Flatten the update object to extract the actual fields being modified
|
|
184
|
+
// const docToCheck = hasOperators
|
|
185
|
+
// ? Object.values(data).reduce((acc, operation) => ({ ...acc, ...operation }), {})
|
|
186
|
+
// : data
|
|
187
|
+
|
|
188
|
+
const pipeline = [
|
|
189
|
+
{
|
|
190
|
+
$match: formattedQuery,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
$limit: 1
|
|
194
|
+
},
|
|
195
|
+
...Object.entries(data).map(([key, value]) => ({ [key]: value })),
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
const [docToCheck] = hasOperators ? await collection.aggregate(pipeline).toArray() : [data] as [Document]
|
|
199
|
+
|
|
200
|
+
// Validate update permissions
|
|
201
|
+
const { status, document } = winningRole
|
|
202
|
+
? await checkValidation(winningRole, {
|
|
203
|
+
type: "write",
|
|
204
|
+
roles,
|
|
205
|
+
cursor: docToCheck,
|
|
206
|
+
expansions: {},
|
|
207
|
+
}, user)
|
|
208
|
+
: { status: true, document: docToCheck }
|
|
209
|
+
|
|
210
|
+
// Ensure no unauthorized changes are made
|
|
211
|
+
const areDocumentsEqual = isEqual(document, docToCheck)
|
|
212
|
+
|
|
213
|
+
if (!status || !areDocumentsEqual) {
|
|
214
|
+
throw new Error('Update not permitted')
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return collection.updateOne(formattedQuery, data, options)
|
|
218
|
+
}
|
|
219
|
+
return collection.updateOne(query, data, options)
|
|
220
|
+
},
|
|
221
|
+
/**
|
|
222
|
+
* Finds documents in a MongoDB collection with optional role-based access control and post-query validation.
|
|
223
|
+
*
|
|
224
|
+
* @param {Filter<Document>} query - The MongoDB query to filter documents.
|
|
225
|
+
* @returns {FindCursor} A customized `FindCursor` that includes additional access control logic in its `toArray()` method.
|
|
226
|
+
*
|
|
227
|
+
* @description
|
|
228
|
+
* If `run_as_system` is enabled, the function simply returns a regular MongoDB cursor (`collection.find(query)`).
|
|
229
|
+
* Otherwise:
|
|
230
|
+
* - Combines the user query with role-based filters via `getFormattedQuery`.
|
|
231
|
+
* - Executes the query using `collection.find` with a `$and` of all filters.
|
|
232
|
+
* - Returns a cloned `FindCursor` where `toArray()`:
|
|
233
|
+
* - Applies additional post-query validation using `checkValidation` for each document.
|
|
234
|
+
* - Filters out documents the current user is not authorized to read.
|
|
235
|
+
*
|
|
236
|
+
* This ensures that both pre-query filtering and post-query validation are applied consistently.
|
|
237
|
+
*/
|
|
238
|
+
find: (query) => {
|
|
239
|
+
if (!run_as_system) {
|
|
240
|
+
const { filters, roles } = rules[collName] || {}
|
|
241
|
+
|
|
242
|
+
// Pre-query filtering based on access control rules
|
|
243
|
+
const formattedQuery = getFormattedQuery(filters, query, user)
|
|
244
|
+
const originalCursor = collection.find({ $and: formattedQuery })
|
|
245
|
+
|
|
246
|
+
// Clone the cursor to override `toArray` with post-query validation
|
|
247
|
+
const client = originalCursor[
|
|
248
|
+
'client' as keyof typeof originalCursor
|
|
249
|
+
] as EventEmitterAsyncResourceOptions
|
|
250
|
+
const newCursor = new FindCursor(client)
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Overridden `toArray` method that validates each document for read access.
|
|
254
|
+
*
|
|
255
|
+
* @returns {Promise<Document[]>} An array of documents the user is authorized to read.
|
|
256
|
+
*/
|
|
257
|
+
newCursor.toArray = async () => {
|
|
258
|
+
const response = await originalCursor.toArray()
|
|
259
|
+
|
|
260
|
+
const filteredResponse = await Promise.all(response.map(async (currentDoc) => {
|
|
261
|
+
const winningRole = getWinningRole(currentDoc, user, roles)
|
|
262
|
+
|
|
263
|
+
const { status, document } = winningRole
|
|
264
|
+
? await checkValidation(winningRole, {
|
|
265
|
+
type: "read",
|
|
266
|
+
roles,
|
|
267
|
+
cursor: currentDoc,
|
|
268
|
+
expansions: {},
|
|
269
|
+
}, user)
|
|
270
|
+
: { status: !roles.length, document: currentDoc }
|
|
271
|
+
|
|
272
|
+
return status ? document : undefined
|
|
273
|
+
}))
|
|
274
|
+
|
|
275
|
+
return filteredResponse.filter(Boolean)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return newCursor
|
|
279
|
+
}
|
|
280
|
+
// System mode: return original unfiltered cursor
|
|
281
|
+
return collection.find(query)
|
|
282
|
+
},
|
|
283
|
+
/**
|
|
284
|
+
* Watches changes on a MongoDB collection with optional role-based filtering of change events.
|
|
285
|
+
*
|
|
286
|
+
* @param {Document[]} [pipeline=[]] - Optional aggregation pipeline stages to apply to the change stream.
|
|
287
|
+
* @param {ChangeStreamOptions} [options] - Optional settings for the change stream, such as `fullDocument`, `resumeAfter`, etc.
|
|
288
|
+
* @returns {ChangeStream} A MongoDB `ChangeStream` instance, optionally enhanced with access control.
|
|
289
|
+
*
|
|
290
|
+
* @description
|
|
291
|
+
* If `run_as_system` is enabled, this function simply returns `collection.watch(pipeline, options)`.
|
|
292
|
+
* Otherwise:
|
|
293
|
+
* - Applies access control filters via `getFormattedQuery`.
|
|
294
|
+
* - Prepends a `$match` stage to the pipeline to limit watched changes to authorized documents.
|
|
295
|
+
* - Overrides the `.on()` method of the returned `ChangeStream` to:
|
|
296
|
+
* - Validate the `fullDocument` and any `updatedFields` using `checkValidation`.
|
|
297
|
+
* - Filter out change events the user is not authorized to see.
|
|
298
|
+
* - Pass only validated and filtered events to the original listener.
|
|
299
|
+
*
|
|
300
|
+
* This allows fine-grained control over what change events a user can observe, based on roles and filters.
|
|
301
|
+
*/
|
|
302
|
+
watch: (
|
|
303
|
+
pipeline = [],
|
|
304
|
+
options
|
|
305
|
+
) => {
|
|
306
|
+
if (!run_as_system) {
|
|
307
|
+
const { filters, roles } = rules[collName] || {}
|
|
308
|
+
|
|
309
|
+
// Apply access filters to initial change stream pipeline
|
|
310
|
+
const formattedQuery = getFormattedQuery(filters, {}, user)
|
|
311
|
+
const formattedPipeline = [{
|
|
312
|
+
$match: {
|
|
313
|
+
$and: formattedQuery
|
|
314
|
+
}
|
|
315
|
+
}, ...pipeline]
|
|
316
|
+
|
|
317
|
+
const result = collection.watch(formattedPipeline, options)
|
|
318
|
+
const originalOn = result.on.bind(result)
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Validates a change event against the user's roles.
|
|
322
|
+
*
|
|
323
|
+
* @param {Document} change - A change event from the ChangeStream.
|
|
324
|
+
* @returns {Promise<{ status: boolean, document: Document, updatedFieldsStatus: boolean, updatedFields: Document }>}
|
|
325
|
+
*/
|
|
326
|
+
const isValidChange = async ({ fullDocument, updateDescription }: Document) => {
|
|
327
|
+
const winningRole = getWinningRole(fullDocument, user, roles)
|
|
328
|
+
|
|
329
|
+
const { status, document } = winningRole
|
|
330
|
+
? await checkValidation(winningRole, {
|
|
331
|
+
type: "read",
|
|
332
|
+
roles,
|
|
333
|
+
cursor: fullDocument,
|
|
334
|
+
expansions: {},
|
|
335
|
+
}, user)
|
|
336
|
+
: { status: true, document: fullDocument }
|
|
337
|
+
|
|
338
|
+
const { status: updatedFieldsStatus, document: updatedFields } = winningRole
|
|
339
|
+
? await checkValidation(winningRole, {
|
|
340
|
+
type: "read",
|
|
341
|
+
roles,
|
|
342
|
+
cursor: updateDescription?.updatedFields,
|
|
343
|
+
expansions: {},
|
|
344
|
+
}, user)
|
|
345
|
+
: { status: true, document: updateDescription?.updatedFields }
|
|
346
|
+
|
|
347
|
+
return { status, document, updatedFieldsStatus, updatedFields }
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Override the .on() method to apply validation before emitting events
|
|
351
|
+
result.on = <EventKey extends keyof EventsDescription>(
|
|
352
|
+
eventType: EventKey,
|
|
353
|
+
listener: EventsDescription[EventKey]
|
|
354
|
+
) => {
|
|
355
|
+
return originalOn(eventType, async (change: Document) => {
|
|
356
|
+
const { status, document, updatedFieldsStatus, updatedFields } = await isValidChange(change)
|
|
357
|
+
if (!status) return
|
|
358
|
+
|
|
359
|
+
const filteredChange = {
|
|
360
|
+
...change,
|
|
361
|
+
fullDocument: document,
|
|
362
|
+
updateDescription: {
|
|
363
|
+
...change.updateDescription,
|
|
364
|
+
updatedFields: updatedFieldsStatus ? updatedFields : {}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
listener(filteredChange)
|
|
369
|
+
})
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return result
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// System mode: no filtering applied
|
|
376
|
+
return collection.watch(pipeline, options)
|
|
377
|
+
},
|
|
378
|
+
//TODO -> add filter & rules in aggregate
|
|
379
|
+
aggregate: (
|
|
380
|
+
pipeline,
|
|
381
|
+
options,
|
|
382
|
+
) => collection.aggregate(pipeline, options),
|
|
383
|
+
/**
|
|
384
|
+
* Inserts multiple documents into a MongoDB collection with optional role-based access control and validation.
|
|
385
|
+
*
|
|
386
|
+
* @param {OptionalId<Document>[]} documents - The array of documents to insert.
|
|
387
|
+
* @param {BulkWriteOptions} [options] - Optional settings passed to `insertMany`, such as `ordered`, `writeConcern`, etc.
|
|
388
|
+
* @returns {Promise<InsertManyResult<Document>>} A promise resolving to the result of the insert operation.
|
|
389
|
+
*
|
|
390
|
+
* @throws {Error} If no documents pass validation or user is not permitted to insert.
|
|
391
|
+
*
|
|
392
|
+
* @description
|
|
393
|
+
* If `run_as_system` is enabled, this function directly inserts the documents without validation.
|
|
394
|
+
* Otherwise, for each document:
|
|
395
|
+
* - Finds the user's applicable role using `getWinningRole`.
|
|
396
|
+
* - Validates the insert operation through `checkValidation`.
|
|
397
|
+
* - Filters out any documents the user is not authorized to insert.
|
|
398
|
+
* Only documents passing validation will be inserted.
|
|
399
|
+
*/
|
|
400
|
+
insertMany: async (documents, options) => {
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
if (!run_as_system) {
|
|
404
|
+
const { roles } = rules[collName] || {}
|
|
405
|
+
// Validate each document against user's roles
|
|
406
|
+
const filteredItems = await Promise.all(documents.map(async (currentDoc) => {
|
|
407
|
+
const winningRole = getWinningRole(currentDoc, user, roles)
|
|
408
|
+
|
|
409
|
+
const { status, document } = winningRole
|
|
410
|
+
? await checkValidation(winningRole, {
|
|
411
|
+
type: "insert",
|
|
412
|
+
roles,
|
|
413
|
+
cursor: currentDoc,
|
|
414
|
+
expansions: {},
|
|
415
|
+
}, user)
|
|
416
|
+
: { status: !roles.length, document: currentDoc }
|
|
417
|
+
|
|
418
|
+
return status ? document : undefined
|
|
419
|
+
}))
|
|
420
|
+
|
|
421
|
+
const canInsert = isEqual(filteredItems, documents)
|
|
422
|
+
|
|
423
|
+
if (!canInsert) {
|
|
424
|
+
throw new Error('Insert not permitted')
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return collection.insertMany(documents, options)
|
|
428
|
+
}
|
|
429
|
+
// If system mode is active, insert all documents without validation
|
|
430
|
+
return collection.insertMany(documents, options)
|
|
431
|
+
},
|
|
432
|
+
updateMany: async (query, data, options) => {
|
|
433
|
+
if (!run_as_system) {
|
|
434
|
+
const { filters, roles } = rules[collName] || {}
|
|
435
|
+
// Apply access control filters
|
|
436
|
+
const formattedQuery = getFormattedQuery(filters, query, user)
|
|
437
|
+
|
|
438
|
+
// Retrieve the document to check permissions before updating
|
|
439
|
+
const result = await collection.find({ $and: formattedQuery }).toArray()
|
|
440
|
+
if (!result) {
|
|
441
|
+
throw new Error('Update not permitted')
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
// Check if the update data contains MongoDB update operators (e.g., $set, $inc)
|
|
446
|
+
const hasOperators = Object.keys(data).some(key => key.startsWith("$"))
|
|
447
|
+
|
|
448
|
+
// Flatten the update object to extract the actual fields being modified
|
|
449
|
+
// const docToCheck = hasOperators
|
|
450
|
+
// ? Object.values(data).reduce((acc, operation) => ({ ...acc, ...operation }), {})
|
|
451
|
+
// : data
|
|
452
|
+
|
|
453
|
+
const pipeline = [
|
|
454
|
+
{
|
|
455
|
+
$match: formattedQuery,
|
|
456
|
+
},
|
|
457
|
+
...Object.entries(data).map(([key, value]) => ({ [key]: value })),
|
|
458
|
+
];
|
|
459
|
+
|
|
460
|
+
const docsToCheck = hasOperators ? await collection.aggregate(pipeline).toArray() : result
|
|
461
|
+
|
|
462
|
+
const filteredItems = await Promise.all(docsToCheck.map(async (currentDoc) => {
|
|
463
|
+
const winningRole = getWinningRole(currentDoc, user, roles)
|
|
464
|
+
|
|
465
|
+
const { status, document } = winningRole
|
|
466
|
+
? await checkValidation(winningRole, {
|
|
467
|
+
type: "write",
|
|
468
|
+
roles,
|
|
469
|
+
cursor: currentDoc,
|
|
470
|
+
expansions: {},
|
|
471
|
+
}, user)
|
|
472
|
+
: { status: !roles.length, document: currentDoc }
|
|
473
|
+
|
|
474
|
+
return status ? document : undefined
|
|
475
|
+
}))
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
// Ensure no unauthorized changes are made
|
|
479
|
+
const areDocumentsEqual = isEqual(docsToCheck, filteredItems)
|
|
480
|
+
|
|
481
|
+
if (!areDocumentsEqual) {
|
|
482
|
+
throw new Error('Update not permitted')
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return collection.updateMany(formattedQuery, data, options)
|
|
486
|
+
}
|
|
487
|
+
return collection.updateMany(query, data, options)
|
|
488
|
+
},
|
|
489
|
+
/**
|
|
490
|
+
* Deletes multiple documents from a MongoDB collection with role-based access control and validation.
|
|
491
|
+
*
|
|
492
|
+
* @param query - The initial MongoDB query to filter documents to be deleted.
|
|
493
|
+
* @returns {Promise<{ acknowledged: boolean, deletedCount: number }>} A promise resolving to the deletion result.
|
|
494
|
+
*
|
|
495
|
+
* @description
|
|
496
|
+
* If `run_as_system` is enabled, this function directly deletes documents matching the given query.
|
|
497
|
+
* Otherwise, it:
|
|
498
|
+
* - Applies additional filters from access control rules.
|
|
499
|
+
* - Fetches matching documents.
|
|
500
|
+
* - Validates each document against user roles.
|
|
501
|
+
* - Deletes only the documents that the current user has permission to delete.
|
|
502
|
+
*/
|
|
503
|
+
deleteMany: async (query = {}) => {
|
|
504
|
+
if (!run_as_system) {
|
|
505
|
+
const { filters, roles } = rules[collName] || {}
|
|
506
|
+
|
|
507
|
+
// Apply access control filters
|
|
508
|
+
const formattedQuery = getFormattedQuery(filters, query, user)
|
|
509
|
+
|
|
510
|
+
// Fetch documents matching the combined filters
|
|
511
|
+
const data = await collection.find({ $and: formattedQuery }).toArray()
|
|
512
|
+
|
|
513
|
+
// Filter and validate each document based on user's roles
|
|
514
|
+
const filteredItems = await Promise.all(data.map(async (currentDoc) => {
|
|
515
|
+
const winningRole = getWinningRole(currentDoc, user, roles)
|
|
516
|
+
|
|
517
|
+
const { status, document } = winningRole
|
|
518
|
+
? await checkValidation(winningRole, {
|
|
519
|
+
type: "delete",
|
|
520
|
+
roles,
|
|
521
|
+
cursor: currentDoc,
|
|
522
|
+
expansions: {},
|
|
523
|
+
}, user)
|
|
524
|
+
: { status: !roles.length, document: currentDoc }
|
|
525
|
+
|
|
526
|
+
return status ? document : undefined
|
|
527
|
+
}))
|
|
528
|
+
|
|
529
|
+
// Extract IDs of documents that passed validation
|
|
530
|
+
const elementsToDelete = (filteredItems.filter(Boolean) as WithId<Document>[]).map(({ _id }) => _id)
|
|
531
|
+
|
|
532
|
+
if (!elementsToDelete.length) {
|
|
533
|
+
return Promise.resolve({
|
|
534
|
+
acknowledged: true,
|
|
535
|
+
deletedCount: 0
|
|
536
|
+
})
|
|
537
|
+
}
|
|
538
|
+
// Build final delete query with access control and ID filter
|
|
539
|
+
const deleteQuery = {
|
|
540
|
+
$and: [
|
|
541
|
+
...formattedQuery,
|
|
542
|
+
{ _id: { $in: elementsToDelete } }
|
|
543
|
+
]
|
|
544
|
+
};
|
|
545
|
+
return collection.deleteMany(deleteQuery)
|
|
546
|
+
}
|
|
547
|
+
// If running as system, bypass access control and delete directly
|
|
548
|
+
return collection.deleteMany(query)
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
const MongodbAtlas: MongodbAtlasFunction = (
|
|
554
|
+
app,
|
|
555
|
+
{ rules, user, run_as_system } = {}
|
|
556
|
+
) => ({
|
|
557
|
+
db: (dbName: string) => {
|
|
558
|
+
return {
|
|
559
|
+
collection: (collName: string) => {
|
|
560
|
+
const collection: Collection<Document> = app.mongo.client
|
|
561
|
+
.db(dbName)
|
|
562
|
+
.collection(collName)
|
|
563
|
+
return getOperators(collection, { rules, collName, user, run_as_system })
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
export default MongodbAtlas
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { FastifyInstance } from 'fastify'
|
|
2
|
+
import { Collection, Document, FindCursor, WithId } from 'mongodb'
|
|
3
|
+
import { User } from '../../auth/dtos'
|
|
4
|
+
import { Filter, Rules } from '../../features/rules/interface'
|
|
5
|
+
import { Role } from '../../utils/roles/interface'
|
|
6
|
+
|
|
7
|
+
export type MongodbAtlasFunction = (
|
|
8
|
+
app: FastifyInstance,
|
|
9
|
+
{
|
|
10
|
+
rules,
|
|
11
|
+
user,
|
|
12
|
+
run_as_system
|
|
13
|
+
}: {
|
|
14
|
+
user?: User
|
|
15
|
+
rules?: Rules
|
|
16
|
+
run_as_system?: boolean
|
|
17
|
+
}
|
|
18
|
+
) => {
|
|
19
|
+
db: (dbName: string) => {
|
|
20
|
+
collection: (collName: string) => ReturnType<GetOperatorsFunction>
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type GetValidRuleParams<T extends Role | Filter> = {
|
|
25
|
+
filters: T[]
|
|
26
|
+
user: User
|
|
27
|
+
record?: WithId<Document> | Document | null
|
|
28
|
+
}
|
|
29
|
+
type Method<T extends keyof Collection<Document>> = Collection<Document>[T]
|
|
30
|
+
|
|
31
|
+
export type GetOperatorsFunction = (
|
|
32
|
+
collection: Collection<Document>,
|
|
33
|
+
{
|
|
34
|
+
rules,
|
|
35
|
+
collName,
|
|
36
|
+
user,
|
|
37
|
+
run_as_system,
|
|
38
|
+
}: {
|
|
39
|
+
user?: User
|
|
40
|
+
rules?: Rules
|
|
41
|
+
run_as_system?: boolean
|
|
42
|
+
collName: string
|
|
43
|
+
}
|
|
44
|
+
) => {
|
|
45
|
+
findOne: (
|
|
46
|
+
...params: Parameters<Method<"findOne">>
|
|
47
|
+
) => ReturnType<Method<"findOne">>
|
|
48
|
+
deleteOne: (
|
|
49
|
+
...params: Parameters<Method<"findOne">>
|
|
50
|
+
) => ReturnType<Method<"findOne">>
|
|
51
|
+
insertOne: (
|
|
52
|
+
...params: Parameters<Method<'insertOne'>>
|
|
53
|
+
) => ReturnType<Method<'insertOne'>>
|
|
54
|
+
updateOne: (
|
|
55
|
+
...params: Parameters<Method<'updateOne'>>
|
|
56
|
+
) => ReturnType<Method<'updateOne'>>
|
|
57
|
+
find: (...params: Parameters<Method<'find'>>) => FindCursor
|
|
58
|
+
watch: (
|
|
59
|
+
...params: Parameters<Method<'watch'>>
|
|
60
|
+
) => ReturnType<Method<'watch'>>
|
|
61
|
+
aggregate: (
|
|
62
|
+
...params: Parameters<Method<'aggregate'>>
|
|
63
|
+
) => ReturnType<Method<'aggregate'>>
|
|
64
|
+
insertMany: (...params: Parameters<Method<'insertMany'>>) => ReturnType<Method<'insertMany'>>
|
|
65
|
+
updateMany: (...params: Parameters<Method<'updateMany'>>) => ReturnType<Method<'updateMany'>>
|
|
66
|
+
deleteMany: (...params: Parameters<Method<'deleteMany'>>) => ReturnType<Method<'deleteMany'>>
|
|
67
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
|
|
2
|
+
import { Collection, Document } from 'mongodb'
|
|
3
|
+
import { User } from '../../auth/dtos'
|
|
4
|
+
import { Filter } from '../../features/rules/interface'
|
|
5
|
+
import { Role } from '../../utils/roles/interface'
|
|
6
|
+
import { expandQuery } from '../../utils/rules'
|
|
7
|
+
import rulesMatcherUtils from '../../utils/rules-matcher/utils'
|
|
8
|
+
import { GetValidRuleParams } from './model'
|
|
9
|
+
|
|
10
|
+
export const getValidRule = <T extends Role | Filter>({
|
|
11
|
+
filters = [],
|
|
12
|
+
user,
|
|
13
|
+
record = null
|
|
14
|
+
}: GetValidRuleParams<T>) => {
|
|
15
|
+
if (!filters.length) return []
|
|
16
|
+
return filters.filter((f) => {
|
|
17
|
+
if (Object.keys(f.apply_when).length === 0) return true
|
|
18
|
+
const conditions = expandQuery(f.apply_when, {
|
|
19
|
+
'%%user': user,
|
|
20
|
+
'%%true': true
|
|
21
|
+
/** values */
|
|
22
|
+
})
|
|
23
|
+
const valid = rulesMatcherUtils.checkRule(
|
|
24
|
+
conditions,
|
|
25
|
+
{
|
|
26
|
+
...(record ?? {}),
|
|
27
|
+
'%%user': user
|
|
28
|
+
},
|
|
29
|
+
{}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return valid
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
export const getFormattedQuery = (filters: Filter[] = [], query: Parameters<Collection<Document>['findOne']>[0], user?: User) => {
|
|
38
|
+
const preFilter = getValidRule({ filters, user })
|
|
39
|
+
const isValidPreFilter = !!preFilter?.length
|
|
40
|
+
return [
|
|
41
|
+
isValidPreFilter && expandQuery(preFilter[0].query, { '%%user': user }),
|
|
42
|
+
query
|
|
43
|
+
].filter(Boolean)
|
|
44
|
+
}
|