@flowerforce/flowerbase 1.2.0 → 1.2.1-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/auth/controller.d.ts.map +1 -1
- package/dist/auth/controller.js +3 -0
- package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
- package/dist/auth/providers/custom-function/controller.js +5 -2
- package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -1
- package/dist/auth/providers/local-userpass/controller.js +7 -10
- package/dist/auth/utils.d.ts.map +1 -1
- package/dist/auth/utils.js +3 -2
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +5 -1
- package/dist/features/functions/controller.d.ts.map +1 -1
- package/dist/features/functions/controller.js +28 -2
- package/dist/features/rules/utils.d.ts.map +1 -1
- package/dist/features/rules/utils.js +11 -2
- package/dist/features/triggers/utils.d.ts.map +1 -1
- package/dist/features/triggers/utils.js +52 -2
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -9
- package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.js +540 -483
- package/dist/services/mongodb-atlas/utils.d.ts +9 -2
- package/dist/services/mongodb-atlas/utils.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/utils.js +113 -23
- package/dist/shared/handleUserRegistration.d.ts.map +1 -1
- package/dist/shared/handleUserRegistration.js +1 -0
- package/dist/shared/models/handleUserRegistration.model.d.ts +6 -2
- package/dist/shared/models/handleUserRegistration.model.d.ts.map +1 -1
- package/dist/utils/context/helpers.d.ts +6 -5
- package/dist/utils/context/helpers.d.ts.map +1 -1
- package/dist/utils/context/helpers.js +3 -0
- package/dist/utils/context/index.d.ts.map +1 -1
- package/dist/utils/context/index.js +2 -0
- package/dist/utils/initializer/exposeRoutes.d.ts.map +1 -1
- package/dist/utils/initializer/exposeRoutes.js +11 -4
- package/dist/utils/initializer/registerPlugins.d.ts +3 -1
- package/dist/utils/initializer/registerPlugins.d.ts.map +1 -1
- package/dist/utils/initializer/registerPlugins.js +9 -6
- package/dist/utils/roles/helpers.js +9 -2
- package/dist/utils/roles/machines/commonValidators.d.ts.map +1 -1
- package/dist/utils/roles/machines/commonValidators.js +10 -6
- package/dist/utils/roles/machines/read/B/validators.d.ts +4 -0
- package/dist/utils/roles/machines/read/B/validators.d.ts.map +1 -0
- package/dist/utils/roles/machines/read/B/validators.js +8 -0
- package/dist/utils/roles/machines/read/C/index.d.ts.map +1 -1
- package/dist/utils/roles/machines/read/C/index.js +10 -7
- package/dist/utils/roles/machines/read/C/validators.d.ts +5 -0
- package/dist/utils/roles/machines/read/C/validators.d.ts.map +1 -0
- package/dist/utils/roles/machines/read/C/validators.js +29 -0
- package/dist/utils/roles/machines/read/D/index.d.ts.map +1 -1
- package/dist/utils/roles/machines/read/D/index.js +13 -11
- package/dist/utils/rules.d.ts +1 -1
- package/dist/utils/rules.d.ts.map +1 -1
- package/dist/utils/rules.js +26 -17
- package/jest.config.ts +2 -12
- package/jest.setup.ts +28 -0
- package/package.json +1 -1
- package/src/auth/controller.ts +3 -0
- package/src/auth/providers/custom-function/controller.ts +5 -2
- package/src/auth/providers/local-userpass/controller.ts +13 -10
- package/src/auth/utils.ts +6 -3
- package/src/constants.ts +7 -2
- package/src/fastify.d.ts +32 -15
- package/src/features/functions/controller.ts +36 -2
- package/src/features/rules/utils.ts +11 -2
- package/src/features/triggers/utils.ts +59 -2
- package/src/index.ts +21 -8
- package/src/services/mongodb-atlas/__tests__/utils.test.ts +141 -0
- package/src/services/mongodb-atlas/index.ts +143 -90
- package/src/services/mongodb-atlas/utils.ts +158 -22
- package/src/shared/handleUserRegistration.ts +3 -3
- package/src/shared/models/handleUserRegistration.model.ts +8 -3
- package/src/types/fastify-raw-body.d.ts +22 -0
- package/src/utils/__tests__/STEP_B_STATES.test.ts +1 -1
- package/src/utils/__tests__/STEP_C_STATES.test.ts +1 -1
- package/src/utils/__tests__/STEP_D_STATES.test.ts +2 -2
- package/src/utils/__tests__/checkIsValidFieldNameFn.test.ts +9 -4
- package/src/utils/__tests__/registerPlugins.test.ts +16 -1
- package/src/utils/context/helpers.ts +3 -0
- package/src/utils/context/index.ts +1 -0
- package/src/utils/initializer/exposeRoutes.ts +15 -8
- package/src/utils/initializer/registerPlugins.ts +15 -7
- package/src/utils/roles/helpers.ts +20 -3
- package/src/utils/roles/machines/commonValidators.ts +10 -5
- package/src/utils/roles/machines/read/B/validators.ts +8 -0
- package/src/utils/roles/machines/read/C/index.ts +11 -7
- package/src/utils/roles/machines/read/C/validators.ts +21 -0
- package/src/utils/roles/machines/read/D/index.ts +22 -12
- package/src/utils/rules.ts +31 -22
- package/tsconfig.spec.json +7 -0
|
@@ -37,7 +37,7 @@ export const getValidRule = <T extends Role | Filter>({
|
|
|
37
37
|
// checkRule valuta se i campi del record soddisfano quella condizione.
|
|
38
38
|
// Quindi le regole vengono effettivamente rispettate.
|
|
39
39
|
const valid = rulesMatcherUtils.checkRule(
|
|
40
|
-
conditions,
|
|
40
|
+
conditions as Parameters<typeof rulesMatcherUtils.checkRule>[0],
|
|
41
41
|
{
|
|
42
42
|
...(record ?? {}),
|
|
43
43
|
'%%user': user,
|
|
@@ -57,10 +57,16 @@ export const getFormattedQuery = (
|
|
|
57
57
|
) => {
|
|
58
58
|
const preFilter = getValidRule({ filters, user })
|
|
59
59
|
const isValidPreFilter = !!preFilter?.length
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
const formatted: FilterMongoDB<Document>[] = []
|
|
61
|
+
if (isValidPreFilter) {
|
|
62
|
+
formatted.push(
|
|
63
|
+
expandQuery(preFilter[0].query, { '%%user': user }) as FilterMongoDB<Document>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
if (query && Object.keys(query).length > 0) {
|
|
67
|
+
formatted.push(query as FilterMongoDB<Document>)
|
|
68
|
+
}
|
|
69
|
+
return formatted
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
export const getFormattedProjection = (
|
|
@@ -90,61 +96,111 @@ export const applyAccessControlToPipeline = (
|
|
|
90
96
|
roles?: Role[]
|
|
91
97
|
}
|
|
92
98
|
>,
|
|
93
|
-
user: User
|
|
99
|
+
user: User,
|
|
100
|
+
collectionName: string,
|
|
101
|
+
options?: {
|
|
102
|
+
isClientPipeline?: boolean
|
|
103
|
+
}
|
|
94
104
|
): AggregationPipeline => {
|
|
105
|
+
const { isClientPipeline = false } = options || {}
|
|
106
|
+
const hiddenFieldsForCollection = isClientPipeline
|
|
107
|
+
? getHiddenFieldsFromRulesConfig(rules[collectionName])
|
|
108
|
+
: []
|
|
109
|
+
|
|
95
110
|
return pipeline.map((stage) => {
|
|
96
111
|
const [stageName] = Object.keys(stage)
|
|
97
112
|
const value = stage[stageName as keyof typeof stage]
|
|
98
113
|
|
|
99
|
-
// CASE LOOKUP
|
|
100
114
|
if (stageName === STAGES_TO_SEARCH.LOOKUP) {
|
|
101
115
|
const lookUpStage = value as LookupStage
|
|
102
116
|
const currentCollection = lookUpStage.from
|
|
117
|
+
checkDenyOperation(rules as Rules, currentCollection, CRUD_OPERATIONS.READ)
|
|
103
118
|
const lookupRules = rules[currentCollection] || {}
|
|
104
119
|
const formattedQuery = getFormattedQuery(lookupRules.filters, {}, user)
|
|
105
120
|
const projection = getFormattedProjection(lookupRules.filters)
|
|
106
121
|
|
|
122
|
+
const nestedPipeline = applyAccessControlToPipeline(
|
|
123
|
+
lookUpStage.pipeline || [],
|
|
124
|
+
rules,
|
|
125
|
+
user,
|
|
126
|
+
currentCollection,
|
|
127
|
+
{ isClientPipeline }
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
const lookupPipeline = [
|
|
131
|
+
...(formattedQuery.length ? [{ $match: { $and: formattedQuery } }] : []),
|
|
132
|
+
...(projection ? [{ $project: projection }] : []),
|
|
133
|
+
...nestedPipeline
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
const pipelineWithHiddenFields = isClientPipeline
|
|
137
|
+
? prependUnsetStage(lookupPipeline, getHiddenFieldsFromRulesConfig(lookupRules))
|
|
138
|
+
: lookupPipeline
|
|
139
|
+
|
|
107
140
|
return {
|
|
108
141
|
$lookup: {
|
|
109
142
|
...lookUpStage,
|
|
110
|
-
pipeline:
|
|
111
|
-
...(formattedQuery.length ? [{ $match: { $and: formattedQuery } }] : []),
|
|
112
|
-
...(projection ? [{ $project: projection }] : []),
|
|
113
|
-
...applyAccessControlToPipeline(lookUpStage.pipeline || [], rules, user)
|
|
114
|
-
]
|
|
143
|
+
pipeline: pipelineWithHiddenFields
|
|
115
144
|
}
|
|
116
145
|
}
|
|
117
146
|
}
|
|
118
147
|
|
|
119
|
-
// CASE LOOKUP
|
|
120
148
|
if (stageName === STAGES_TO_SEARCH.UNION_WITH) {
|
|
121
149
|
const unionWithStage = value as UnionWithStage
|
|
122
150
|
const isSimpleStage = typeof unionWithStage === 'string'
|
|
123
151
|
const currentCollection = isSimpleStage ? unionWithStage : unionWithStage.coll
|
|
152
|
+
checkDenyOperation(rules as Rules, currentCollection, CRUD_OPERATIONS.READ)
|
|
124
153
|
const unionRules = rules[currentCollection] || {}
|
|
125
154
|
const formattedQuery = getFormattedQuery(unionRules.filters, {}, user)
|
|
126
155
|
const projection = getFormattedProjection(unionRules.filters)
|
|
127
156
|
|
|
128
|
-
|
|
157
|
+
if (isSimpleStage) {
|
|
158
|
+
return stage
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const nestedPipeline = unionWithStage.pipeline || []
|
|
162
|
+
|
|
163
|
+
const sanitizedNestedPipeline = applyAccessControlToPipeline(
|
|
164
|
+
nestedPipeline,
|
|
165
|
+
rules,
|
|
166
|
+
user,
|
|
167
|
+
currentCollection,
|
|
168
|
+
{ isClientPipeline }
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
const unionPipeline = [
|
|
172
|
+
...(formattedQuery.length ? [{ $match: { $and: formattedQuery } }] : []),
|
|
173
|
+
...(projection ? [{ $project: projection }] : []),
|
|
174
|
+
...sanitizedNestedPipeline
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
const pipelineWithHiddenFields = isClientPipeline
|
|
178
|
+
? prependUnsetStage(unionPipeline, getHiddenFieldsFromRulesConfig(unionRules))
|
|
179
|
+
: unionPipeline
|
|
129
180
|
|
|
130
181
|
return {
|
|
131
182
|
$unionWith: {
|
|
132
|
-
|
|
133
|
-
pipeline:
|
|
134
|
-
...(formattedQuery.length ? [{ $match: { $and: formattedQuery } }] : []),
|
|
135
|
-
...(projection ? [{ $project: projection }] : []),
|
|
136
|
-
...applyAccessControlToPipeline(nestedPipeline, rules, user)
|
|
137
|
-
]
|
|
183
|
+
...unionWithStage,
|
|
184
|
+
pipeline: pipelineWithHiddenFields
|
|
138
185
|
}
|
|
139
186
|
}
|
|
140
187
|
}
|
|
141
188
|
|
|
142
|
-
// CASE FACET
|
|
143
189
|
if (stageName === STAGES_TO_SEARCH.FACET) {
|
|
144
190
|
const modifiedFacets = Object.fromEntries(
|
|
145
191
|
(Object.entries(value) as [string, AggregationPipelineStage[]][]).map(
|
|
146
192
|
([facetKey, facetPipeline]) => {
|
|
147
|
-
|
|
193
|
+
const sanitizedFacetPipeline = applyAccessControlToPipeline(
|
|
194
|
+
facetPipeline,
|
|
195
|
+
rules,
|
|
196
|
+
user,
|
|
197
|
+
collectionName,
|
|
198
|
+
{ isClientPipeline }
|
|
199
|
+
)
|
|
200
|
+
const facetPipelineWithHiddenFields = isClientPipeline
|
|
201
|
+
? prependUnsetStage(sanitizedFacetPipeline, hiddenFieldsForCollection)
|
|
202
|
+
: sanitizedFacetPipeline
|
|
203
|
+
return [facetKey, facetPipelineWithHiddenFields]
|
|
148
204
|
}
|
|
149
205
|
)
|
|
150
206
|
)
|
|
@@ -210,3 +266,83 @@ export const getCollectionsFromPipeline = (pipeline: Document[]) => {
|
|
|
210
266
|
return acc
|
|
211
267
|
}, [])
|
|
212
268
|
}
|
|
269
|
+
|
|
270
|
+
const CLIENT_STAGE_BLACKLIST = new Set([
|
|
271
|
+
'$replaceRoot',
|
|
272
|
+
'$merge',
|
|
273
|
+
'$out',
|
|
274
|
+
'$function',
|
|
275
|
+
'$where',
|
|
276
|
+
'$accumulator',
|
|
277
|
+
'$graphLookup'
|
|
278
|
+
])
|
|
279
|
+
|
|
280
|
+
export function ensureClientPipelineStages(pipeline: AggregationPipeline) {
|
|
281
|
+
pipeline.forEach((stage) => {
|
|
282
|
+
const [stageName] = Object.keys(stage)
|
|
283
|
+
if (!stageName) return
|
|
284
|
+
|
|
285
|
+
if (CLIENT_STAGE_BLACKLIST.has(stageName)) {
|
|
286
|
+
throw new Error(`Stage ${stageName} is not allowed in client aggregate pipelines`)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const value = stage[stageName as keyof typeof stage]
|
|
290
|
+
|
|
291
|
+
if (stageName === STAGES_TO_SEARCH.LOOKUP) {
|
|
292
|
+
ensureClientPipelineStages((value as LookupStage).pipeline || [])
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (stageName === STAGES_TO_SEARCH.UNION_WITH) {
|
|
297
|
+
if (typeof value === 'string') {
|
|
298
|
+
throw new Error('$unionWith must provide a pipeline when called from the client')
|
|
299
|
+
}
|
|
300
|
+
const unionStage = value as { pipeline?: AggregationPipeline }
|
|
301
|
+
ensureClientPipelineStages(unionStage.pipeline || [])
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (stageName === STAGES_TO_SEARCH.FACET) {
|
|
306
|
+
Object.values(value as Record<string, AggregationPipeline>).forEach((facetPipeline) =>
|
|
307
|
+
ensureClientPipelineStages(facetPipeline)
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function getHiddenFieldsFromRulesConfig(rulesConfig?: { roles?: Role[] }) {
|
|
314
|
+
if (!rulesConfig) {
|
|
315
|
+
return []
|
|
316
|
+
}
|
|
317
|
+
return collectHiddenFieldsFromRoles(rulesConfig.roles)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function collectHiddenFieldsFromRoles(roles: Role[] = []) {
|
|
321
|
+
const hiddenFields = new Set<string>()
|
|
322
|
+
|
|
323
|
+
const collectFromFields = (
|
|
324
|
+
fields?: Role['fields'] | Role['additional_fields']
|
|
325
|
+
) => {
|
|
326
|
+
if (!fields) return
|
|
327
|
+
Object.entries(fields).forEach(([fieldName, permissions]) => {
|
|
328
|
+
const canRead = Boolean(permissions?.read || permissions?.write)
|
|
329
|
+
if (!canRead) {
|
|
330
|
+
hiddenFields.add(fieldName)
|
|
331
|
+
}
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
roles.forEach((role) => {
|
|
336
|
+
collectFromFields(role.fields)
|
|
337
|
+
collectFromFields(role.additional_fields)
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
return Array.from(hiddenFields)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function prependUnsetStage(pipeline: AggregationPipeline, hiddenFields: string[]) {
|
|
344
|
+
if (!hiddenFields.length) {
|
|
345
|
+
return pipeline
|
|
346
|
+
}
|
|
347
|
+
return [{ $unset: hiddenFields }, ...pipeline]
|
|
348
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { FastifyMongoObject } from "@fastify/mongodb/types"
|
|
2
1
|
import { AUTH_CONFIG, DB_NAME } from "../constants"
|
|
3
2
|
import { hashPassword } from "../utils/crypto"
|
|
4
3
|
import { HandleUserRegistration } from "./models/handleUserRegistration.model"
|
|
@@ -18,7 +17,7 @@ const handleUserRegistration: HandleUserRegistration = (app, opt) => async ({ em
|
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
const { authCollection } = AUTH_CONFIG
|
|
21
|
-
const mongo
|
|
20
|
+
const mongo = app?.mongo
|
|
22
21
|
const db = mongo.client.db(DB_NAME)
|
|
23
22
|
const hashedPassword = await hashPassword(password)
|
|
24
23
|
|
|
@@ -31,6 +30,7 @@ const handleUserRegistration: HandleUserRegistration = (app, opt) => async ({ em
|
|
|
31
30
|
email,
|
|
32
31
|
password: hashedPassword,
|
|
33
32
|
status: skipUserCheck ? 'confirmed' : 'pending',
|
|
33
|
+
createdAt: new Date(),
|
|
34
34
|
custom_data: {
|
|
35
35
|
// TODO: aggiungere dati personalizzati alla registrazione
|
|
36
36
|
},
|
|
@@ -62,4 +62,4 @@ const handleUserRegistration: HandleUserRegistration = (app, opt) => async ({ em
|
|
|
62
62
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
export default handleUserRegistration
|
|
65
|
+
export default handleUserRegistration
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { FastifyInstance } from "fastify/types/instance"
|
|
2
|
-
import { InsertOneResult } from "mongodb/mongodb"
|
|
3
2
|
import { User } from "../../auth/dtos"
|
|
4
3
|
import { Rules } from "../../features/rules/interface"
|
|
5
4
|
|
|
@@ -16,12 +15,18 @@ export type Options = {
|
|
|
16
15
|
run_as_system?: boolean
|
|
17
16
|
}
|
|
18
17
|
|
|
18
|
+
type RegistrationResult = {
|
|
19
|
+
insertedId?: {
|
|
20
|
+
toString: () => string
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
export type HandleUserRegistration = (
|
|
20
25
|
app: FastifyInstance,
|
|
21
26
|
opt: Options
|
|
22
|
-
) => (params: RegistrationParams) => Promise<
|
|
27
|
+
) => (params: RegistrationParams) => Promise<RegistrationResult>
|
|
23
28
|
|
|
24
29
|
export enum PROVIDER {
|
|
25
30
|
LOCAL_USERPASS = "local-userpass",
|
|
26
31
|
CUSTOM_FUNCTION = "custom-function"
|
|
27
|
-
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import 'fastify'
|
|
2
|
+
import type { FastifyJWT } from '@fastify/jwt'
|
|
3
|
+
import { Db, MongoClient } from 'mongodb'
|
|
4
|
+
|
|
5
|
+
declare module 'fastify' {
|
|
6
|
+
interface FastifyRequest {
|
|
7
|
+
rawBody?: string
|
|
8
|
+
user?: FastifyJWT['user']
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface FastifyContextConfig {
|
|
12
|
+
rawBody?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface FastifyInstance {
|
|
16
|
+
mongo?: {
|
|
17
|
+
client: MongoClient
|
|
18
|
+
db?: Db
|
|
19
|
+
ObjectId: typeof import('mongodb').ObjectId
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -11,7 +11,7 @@ const {
|
|
|
11
11
|
evaluateDocumentsFiltersWrite
|
|
12
12
|
} = STEP_B_STATES
|
|
13
13
|
|
|
14
|
-
jest.mock('../roles/machines/B/validators', () => ({
|
|
14
|
+
jest.mock('../roles/machines/read/B/validators', () => ({
|
|
15
15
|
evaluateDocumentFiltersReadFn: jest.fn(),
|
|
16
16
|
evaluateDocumentFiltersWriteFn: jest.fn()
|
|
17
17
|
}))
|
|
@@ -12,7 +12,7 @@ const endValidation = jest.fn()
|
|
|
12
12
|
const goToNextValidationStage = jest.fn()
|
|
13
13
|
const next = jest.fn()
|
|
14
14
|
|
|
15
|
-
jest.mock('../roles/machines/C/validators', () => ({
|
|
15
|
+
jest.mock('../roles/machines/read/C/validators', () => ({
|
|
16
16
|
evaluateTopLevelReadFn: jest.fn(),
|
|
17
17
|
checkFieldsPropertyExists: jest.fn(),
|
|
18
18
|
evaluateTopLevelWriteFn: jest.fn()
|
|
@@ -76,7 +76,7 @@ describe('STEP_D_STATES', () => {
|
|
|
76
76
|
})
|
|
77
77
|
expect(next).toHaveBeenCalledWith('evaluateRead')
|
|
78
78
|
})
|
|
79
|
-
it('checkIsValidFieldName should end a
|
|
79
|
+
it('checkIsValidFieldName should end a successful validation, with a document', async () => {
|
|
80
80
|
const mockedLogInfo = jest
|
|
81
81
|
.spyOn(Utils, 'logMachineInfo')
|
|
82
82
|
.mockImplementation(() => 'Mocked Value')
|
|
@@ -95,7 +95,7 @@ describe('STEP_D_STATES', () => {
|
|
|
95
95
|
next,
|
|
96
96
|
initialStep: null
|
|
97
97
|
})
|
|
98
|
-
expect(endValidation).toHaveBeenCalledWith({ success:
|
|
98
|
+
expect(endValidation).toHaveBeenCalledWith({ success: true, document: { name: 'test' } })
|
|
99
99
|
expect(mockedLogInfo).toHaveBeenCalledWith({
|
|
100
100
|
enabled: mockContext.enableLog,
|
|
101
101
|
machine: 'D',
|
|
@@ -32,7 +32,12 @@ describe('checkIsValidFieldNameFn', () => {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const result = checkIsValidFieldNameFn(context as MachineContext)
|
|
35
|
-
expect(result).toEqual({
|
|
35
|
+
expect(result).toEqual({
|
|
36
|
+
_id: mockId,
|
|
37
|
+
name: 'Alice',
|
|
38
|
+
email: 'alice@example.com',
|
|
39
|
+
age: 25
|
|
40
|
+
})
|
|
36
41
|
})
|
|
37
42
|
it("should exclude _id if role doesn't allows it", () => {
|
|
38
43
|
const mockedRole = {
|
|
@@ -127,7 +132,7 @@ describe('checkIsValidFieldNameFn', () => {
|
|
|
127
132
|
|
|
128
133
|
const result = checkIsValidFieldNameFn(context as MachineContext)
|
|
129
134
|
|
|
130
|
-
expect(result).toEqual({})
|
|
135
|
+
expect(result).toEqual({ email: 'charlie@example.com' })
|
|
131
136
|
})
|
|
132
137
|
|
|
133
138
|
it('should handle additional_fields correctly for read permission', () => {
|
|
@@ -147,7 +152,7 @@ describe('checkIsValidFieldNameFn', () => {
|
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
const result = checkIsValidFieldNameFn(context as MachineContext)
|
|
150
|
-
expect(result).toEqual({ _id: mockId, phone: '123456789' })
|
|
155
|
+
expect(result).toEqual({ _id: mockId, phone: '123456789', address: 'Unknown' })
|
|
151
156
|
})
|
|
152
157
|
it('should handle additional_fields correctly for write permission', () => {
|
|
153
158
|
const mockedRole = {
|
|
@@ -186,6 +191,6 @@ describe('checkIsValidFieldNameFn', () => {
|
|
|
186
191
|
}
|
|
187
192
|
|
|
188
193
|
const result = checkIsValidFieldNameFn(context as MachineContext)
|
|
189
|
-
expect(result).toEqual({ _id: mockId })
|
|
194
|
+
expect(result).toEqual({ _id: mockId, phone: '123456789', address: 'Unknown' })
|
|
190
195
|
})
|
|
191
196
|
})
|
|
@@ -2,6 +2,8 @@ import cors from '@fastify/cors'
|
|
|
2
2
|
import fastifyMongodb from '@fastify/mongodb'
|
|
3
3
|
import { authController } from '../../auth/controller'
|
|
4
4
|
import jwtAuthPlugin from '../../auth/plugins/jwt'
|
|
5
|
+
import fastifyRawBody from 'fastify-raw-body'
|
|
6
|
+
import { customFunctionController } from '../../auth/providers/custom-function/controller'
|
|
5
7
|
import { localUserPassController } from '../../auth/providers/local-userpass/controller'
|
|
6
8
|
import { Functions } from '../../features/functions/interface'
|
|
7
9
|
import { registerPlugins } from '../initializer/registerPlugins'
|
|
@@ -34,7 +36,7 @@ describe('registerPlugins', () => {
|
|
|
34
36
|
})
|
|
35
37
|
|
|
36
38
|
// Check Plugins Registration
|
|
37
|
-
expect(registerMock).toHaveBeenCalledTimes(
|
|
39
|
+
expect(registerMock).toHaveBeenCalledTimes(7)
|
|
38
40
|
expect(registerMock).toHaveBeenCalledWith(cors, {
|
|
39
41
|
origin: '*',
|
|
40
42
|
methods: ['POST', 'GET']
|
|
@@ -50,10 +52,22 @@ describe('registerPlugins', () => {
|
|
|
50
52
|
expect(registerMock).toHaveBeenCalledWith(localUserPassController, {
|
|
51
53
|
prefix: `${MOCKED_API_VERSION}/app/:appId/auth/providers/local-userpass`
|
|
52
54
|
})
|
|
55
|
+
expect(registerMock).toHaveBeenCalledWith(fastifyRawBody, {
|
|
56
|
+
field: 'rawBody',
|
|
57
|
+
global: false,
|
|
58
|
+
encoding: 'utf8',
|
|
59
|
+
runFirst: true,
|
|
60
|
+
routes: [],
|
|
61
|
+
jsonContentTypes: []
|
|
62
|
+
})
|
|
63
|
+
expect(registerMock).toHaveBeenCalledWith(customFunctionController, {
|
|
64
|
+
prefix: `${MOCKED_API_VERSION}/app/:appId/auth/providers/custom-function`
|
|
65
|
+
})
|
|
53
66
|
})
|
|
54
67
|
|
|
55
68
|
it('should handle errors in the catch block', async () => {
|
|
56
69
|
const errorLog = jest.spyOn(console, 'error').mockImplementation(() => {})
|
|
70
|
+
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {})
|
|
57
71
|
|
|
58
72
|
await registerPlugins({
|
|
59
73
|
register: errorMock,
|
|
@@ -67,5 +81,6 @@ describe('registerPlugins', () => {
|
|
|
67
81
|
'Plugin registration failed'
|
|
68
82
|
)
|
|
69
83
|
errorLog.mockRestore()
|
|
84
|
+
logSpy.mockRestore()
|
|
70
85
|
})
|
|
71
86
|
})
|
|
@@ -13,12 +13,21 @@ import { hashPassword } from '../crypto'
|
|
|
13
13
|
*/
|
|
14
14
|
export const exposeRoutes = async (fastify: FastifyInstance) => {
|
|
15
15
|
try {
|
|
16
|
-
fastify.get(`${API_VERSION}/app/:appId/location`, async (req) =>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
hostname
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
fastify.get(`${API_VERSION}/app/:appId/location`, async (req) => {
|
|
17
|
+
const schema = DEFAULT_CONFIG?.HTTPS_SCHEMA ?? 'http'
|
|
18
|
+
const headerHost = req.headers.host ?? 'localhost:3000'
|
|
19
|
+
const hostname = headerHost.split(':')[0]
|
|
20
|
+
const port = DEFAULT_CONFIG?.PORT ?? 3000
|
|
21
|
+
const host = `${hostname}:${port}`
|
|
22
|
+
const wsSchema = 'wss'
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
deployment_model: 'LOCAL',
|
|
26
|
+
location: 'IE',
|
|
27
|
+
hostname: `${schema}://${host}`,
|
|
28
|
+
ws_hostname: `${wsSchema}://${host}`
|
|
29
|
+
}
|
|
30
|
+
})
|
|
22
31
|
|
|
23
32
|
fastify.get('/health', async () => ({
|
|
24
33
|
status: 'ok',
|
|
@@ -77,5 +86,3 @@ export const exposeRoutes = async (fastify: FastifyInstance) => {
|
|
|
77
86
|
console.error('Error while exposing routes', (e as Error).message)
|
|
78
87
|
}
|
|
79
88
|
}
|
|
80
|
-
|
|
81
|
-
|
|
@@ -2,6 +2,7 @@ import cors from '@fastify/cors'
|
|
|
2
2
|
import fastifyMongodb from '@fastify/mongodb'
|
|
3
3
|
import { FastifyInstance } from 'fastify'
|
|
4
4
|
import fastifyRawBody from 'fastify-raw-body'
|
|
5
|
+
import { CorsConfig } from '../../'
|
|
5
6
|
import { authController } from '../../auth/controller'
|
|
6
7
|
import jwtAuthPlugin from '../../auth/plugins/jwt'
|
|
7
8
|
import { customFunctionController } from '../../auth/providers/custom-function/controller'
|
|
@@ -17,6 +18,7 @@ type RegisterPluginsParams = {
|
|
|
17
18
|
mongodbUrl: string
|
|
18
19
|
jwtSecret: string
|
|
19
20
|
functionsList: Functions
|
|
21
|
+
corsConfig?: CorsConfig
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
type RegisterConfig = {
|
|
@@ -36,12 +38,14 @@ export const registerPlugins = async ({
|
|
|
36
38
|
register,
|
|
37
39
|
mongodbUrl,
|
|
38
40
|
jwtSecret,
|
|
39
|
-
functionsList
|
|
41
|
+
functionsList,
|
|
42
|
+
corsConfig
|
|
40
43
|
}: RegisterPluginsParams) => {
|
|
41
44
|
try {
|
|
42
45
|
const registersConfig = await getRegisterConfig({
|
|
43
46
|
mongodbUrl,
|
|
44
47
|
jwtSecret,
|
|
48
|
+
corsConfig,
|
|
45
49
|
functionsList
|
|
46
50
|
})
|
|
47
51
|
|
|
@@ -52,6 +56,7 @@ export const registerPlugins = async ({
|
|
|
52
56
|
} catch (e) {
|
|
53
57
|
console.log('Registration FAILED --->', pluginName)
|
|
54
58
|
console.log('Error --->', e)
|
|
59
|
+
throw e
|
|
55
60
|
}
|
|
56
61
|
})
|
|
57
62
|
} catch (e) {
|
|
@@ -67,18 +72,21 @@ export const registerPlugins = async ({
|
|
|
67
72
|
*/
|
|
68
73
|
const getRegisterConfig = async ({
|
|
69
74
|
mongodbUrl,
|
|
70
|
-
jwtSecret
|
|
71
|
-
|
|
75
|
+
jwtSecret,
|
|
76
|
+
corsConfig
|
|
77
|
+
}: Pick<RegisterPluginsParams, 'jwtSecret' | 'mongodbUrl' | 'functionsList' | 'corsConfig'>): Promise<
|
|
72
78
|
RegisterConfig[]
|
|
73
79
|
> => {
|
|
80
|
+
const corsOptions = corsConfig ?? {
|
|
81
|
+
origin: '*',
|
|
82
|
+
methods: ['POST', 'GET']
|
|
83
|
+
}
|
|
84
|
+
|
|
74
85
|
return [
|
|
75
86
|
{
|
|
76
87
|
pluginName: 'cors',
|
|
77
88
|
plugin: cors,
|
|
78
|
-
options:
|
|
79
|
-
origin: '*',
|
|
80
|
-
methods: ['POST', 'GET', 'DELETE']
|
|
81
|
-
}
|
|
89
|
+
options: corsOptions
|
|
82
90
|
},
|
|
83
91
|
{
|
|
84
92
|
pluginName: 'fastifyMongodb',
|
|
@@ -22,7 +22,7 @@ export const evaluateExpression = async (
|
|
|
22
22
|
'%%true': true
|
|
23
23
|
}
|
|
24
24
|
const conditions = expandQuery(expression, value)
|
|
25
|
-
const complexCondition = Object.entries
|
|
25
|
+
const complexCondition = Object.entries(conditions as Record<string, any>).find(([key]) =>
|
|
26
26
|
functionsConditions.includes(key)
|
|
27
27
|
)
|
|
28
28
|
return complexCondition
|
|
@@ -37,12 +37,29 @@ const evaluateComplexExpression = async (
|
|
|
37
37
|
) => {
|
|
38
38
|
const [key, config] = condition
|
|
39
39
|
|
|
40
|
-
const
|
|
40
|
+
const functionConfig = config['%function']
|
|
41
|
+
const { name, arguments: fnArguments } = functionConfig
|
|
41
42
|
const functionsList = StateManager.select('functions')
|
|
42
43
|
const app = StateManager.select('app')
|
|
43
44
|
const currentFunction = functionsList[name]
|
|
45
|
+
|
|
46
|
+
const expansionContext = {
|
|
47
|
+
...params.expansions,
|
|
48
|
+
...params.cursor,
|
|
49
|
+
'%%root': params.cursor,
|
|
50
|
+
'%%user': user,
|
|
51
|
+
'%%true': true,
|
|
52
|
+
'%%false': false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const expandedArguments =
|
|
56
|
+
fnArguments && fnArguments.length
|
|
57
|
+
? ((expandQuery({ args: fnArguments }, expansionContext) as { args: unknown[] })
|
|
58
|
+
.args ?? [])
|
|
59
|
+
: [params.cursor]
|
|
60
|
+
|
|
44
61
|
const response = await GenerateContext({
|
|
45
|
-
args:
|
|
62
|
+
args: expandedArguments,
|
|
46
63
|
app,
|
|
47
64
|
rules: StateManager.select("rules"),
|
|
48
65
|
user,
|
|
@@ -3,7 +3,7 @@ import { evaluateExpression } from '../helpers'
|
|
|
3
3
|
import { DocumentFiltersPermissions } from '../interface'
|
|
4
4
|
import { MachineContext } from './interface'
|
|
5
5
|
|
|
6
|
-
const readOnlyPermissions = ['read']
|
|
6
|
+
const readOnlyPermissions = ['read', 'search']
|
|
7
7
|
const readWritePermissions = ['write', 'delete', 'insert', ...readOnlyPermissions]
|
|
8
8
|
|
|
9
9
|
export const evaluateDocumentFiltersFn = async (
|
|
@@ -23,11 +23,16 @@ export const evaluateTopLevelPermissionsFn = async (
|
|
|
23
23
|
{ params, role, user }: MachineContext,
|
|
24
24
|
currentType: MachineContext['params']['type']
|
|
25
25
|
) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const permission = role?.[currentType]
|
|
27
|
+
if (typeof permission === 'undefined') {
|
|
28
|
+
return undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return await evaluateExpression(params, permission, user)
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
export const checkFieldsPropertyExists = ({ role }: MachineContext) => {
|
|
32
|
-
|
|
35
|
+
const hasFields = !!Object.keys(role?.fields ?? {}).length
|
|
36
|
+
const hasAdditional = !!Object.keys(role?.additional_fields ?? {}).length
|
|
37
|
+
return hasFields || hasAdditional
|
|
33
38
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MachineContext } from '../../interface'
|
|
2
|
+
import { evaluateDocumentFiltersFn } from '../../commonValidators'
|
|
3
|
+
|
|
4
|
+
export const evaluateDocumentFiltersReadFn = (context: MachineContext) =>
|
|
5
|
+
evaluateDocumentFiltersFn(context, 'read')
|
|
6
|
+
|
|
7
|
+
export const evaluateDocumentFiltersWriteFn = (context: MachineContext) =>
|
|
8
|
+
evaluateDocumentFiltersFn(context, 'write')
|