@flowerforce/flowerbase 1.2.0 → 1.2.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.
Files changed (92) hide show
  1. package/dist/auth/controller.d.ts.map +1 -1
  2. package/dist/auth/controller.js +3 -0
  3. package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
  4. package/dist/auth/providers/custom-function/controller.js +5 -2
  5. package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -1
  6. package/dist/auth/providers/local-userpass/controller.js +7 -10
  7. package/dist/auth/utils.d.ts +1 -1
  8. package/dist/auth/utils.d.ts.map +1 -1
  9. package/dist/auth/utils.js +4 -3
  10. package/dist/constants.d.ts +5 -0
  11. package/dist/constants.d.ts.map +1 -1
  12. package/dist/constants.js +5 -1
  13. package/dist/features/functions/controller.d.ts.map +1 -1
  14. package/dist/features/functions/controller.js +28 -2
  15. package/dist/features/rules/utils.d.ts.map +1 -1
  16. package/dist/features/rules/utils.js +11 -2
  17. package/dist/features/triggers/utils.d.ts.map +1 -1
  18. package/dist/features/triggers/utils.js +52 -2
  19. package/dist/index.d.ts +8 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +10 -9
  22. package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
  23. package/dist/services/mongodb-atlas/index.js +540 -483
  24. package/dist/services/mongodb-atlas/utils.d.ts +9 -2
  25. package/dist/services/mongodb-atlas/utils.d.ts.map +1 -1
  26. package/dist/services/mongodb-atlas/utils.js +113 -23
  27. package/dist/shared/handleUserRegistration.d.ts.map +1 -1
  28. package/dist/shared/handleUserRegistration.js +1 -0
  29. package/dist/shared/models/handleUserRegistration.model.d.ts +6 -2
  30. package/dist/shared/models/handleUserRegistration.model.d.ts.map +1 -1
  31. package/dist/utils/context/helpers.d.ts +6 -5
  32. package/dist/utils/context/helpers.d.ts.map +1 -1
  33. package/dist/utils/context/helpers.js +3 -0
  34. package/dist/utils/context/index.d.ts.map +1 -1
  35. package/dist/utils/context/index.js +2 -0
  36. package/dist/utils/initializer/exposeRoutes.d.ts.map +1 -1
  37. package/dist/utils/initializer/exposeRoutes.js +11 -4
  38. package/dist/utils/initializer/registerPlugins.d.ts +3 -1
  39. package/dist/utils/initializer/registerPlugins.d.ts.map +1 -1
  40. package/dist/utils/initializer/registerPlugins.js +9 -6
  41. package/dist/utils/roles/helpers.js +9 -2
  42. package/dist/utils/roles/machines/commonValidators.d.ts.map +1 -1
  43. package/dist/utils/roles/machines/commonValidators.js +10 -6
  44. package/dist/utils/roles/machines/read/B/validators.d.ts +4 -0
  45. package/dist/utils/roles/machines/read/B/validators.d.ts.map +1 -0
  46. package/dist/utils/roles/machines/read/B/validators.js +8 -0
  47. package/dist/utils/roles/machines/read/C/index.d.ts.map +1 -1
  48. package/dist/utils/roles/machines/read/C/index.js +10 -7
  49. package/dist/utils/roles/machines/read/C/validators.d.ts +5 -0
  50. package/dist/utils/roles/machines/read/C/validators.d.ts.map +1 -0
  51. package/dist/utils/roles/machines/read/C/validators.js +29 -0
  52. package/dist/utils/roles/machines/read/D/index.d.ts.map +1 -1
  53. package/dist/utils/roles/machines/read/D/index.js +13 -11
  54. package/dist/utils/rules.d.ts +1 -1
  55. package/dist/utils/rules.d.ts.map +1 -1
  56. package/dist/utils/rules.js +26 -17
  57. package/jest.config.ts +2 -12
  58. package/jest.setup.ts +28 -0
  59. package/package.json +1 -1
  60. package/src/auth/controller.ts +3 -0
  61. package/src/auth/providers/custom-function/controller.ts +5 -2
  62. package/src/auth/providers/local-userpass/controller.ts +13 -10
  63. package/src/auth/utils.ts +7 -4
  64. package/src/constants.ts +7 -2
  65. package/src/fastify.d.ts +32 -15
  66. package/src/features/functions/controller.ts +36 -2
  67. package/src/features/rules/utils.ts +11 -2
  68. package/src/features/triggers/utils.ts +59 -2
  69. package/src/index.ts +21 -8
  70. package/src/services/mongodb-atlas/__tests__/utils.test.ts +141 -0
  71. package/src/services/mongodb-atlas/index.ts +143 -90
  72. package/src/services/mongodb-atlas/utils.ts +158 -22
  73. package/src/shared/handleUserRegistration.ts +3 -3
  74. package/src/shared/models/handleUserRegistration.model.ts +8 -3
  75. package/src/types/fastify-raw-body.d.ts +22 -0
  76. package/src/utils/__tests__/STEP_B_STATES.test.ts +1 -1
  77. package/src/utils/__tests__/STEP_C_STATES.test.ts +1 -1
  78. package/src/utils/__tests__/STEP_D_STATES.test.ts +2 -2
  79. package/src/utils/__tests__/checkIsValidFieldNameFn.test.ts +9 -4
  80. package/src/utils/__tests__/registerPlugins.test.ts +16 -1
  81. package/src/utils/context/helpers.ts +3 -0
  82. package/src/utils/context/index.ts +1 -0
  83. package/src/utils/initializer/exposeRoutes.ts +15 -8
  84. package/src/utils/initializer/registerPlugins.ts +15 -7
  85. package/src/utils/roles/helpers.ts +20 -3
  86. package/src/utils/roles/machines/commonValidators.ts +10 -5
  87. package/src/utils/roles/machines/read/B/validators.ts +8 -0
  88. package/src/utils/roles/machines/read/C/index.ts +11 -7
  89. package/src/utils/roles/machines/read/C/validators.ts +21 -0
  90. package/src/utils/roles/machines/read/D/index.ts +22 -12
  91. package/src/utils/rules.ts +31 -22
  92. 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
- return [
61
- isValidPreFilter && expandQuery(preFilter[0].query, { '%%user': user }),
62
- query
63
- ].filter(Boolean).filter(r => Object.keys(r).length > 0)
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
- const nestedPipeline = isSimpleStage ? [] : unionWithStage.pipeline || []
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
- coll: currentCollection,
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
- return [facetKey, applyAccessControlToPipeline(facetPipeline, rules, user)]
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: FastifyMongoObject = app?.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<InsertOneResult<Document>>
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 failed validation, with an empty document', async () => {
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: false, document: {} })
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({ name: 'Alice', email: 'alice@example.com', _id: mockId })
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(5)
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
  })
@@ -27,6 +27,9 @@ export const generateContextData = ({
27
27
  console: {
28
28
  log: (...args: Arguments) => {
29
29
  console.log(...args)
30
+ },
31
+ error: (...args: Arguments) => {
32
+ console.error(...args)
30
33
  }
31
34
  },
32
35
  context: {
@@ -29,6 +29,7 @@ export async function GenerateContext({
29
29
  enqueue,
30
30
  request
31
31
  }: GenerateContextParams) {
32
+ if (!currentFunction) return
32
33
 
33
34
  const functionsQueue = StateManager.select("functionsQueue")
34
35
 
@@ -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
- deployment_model: 'LOCAL',
18
- location: 'IE',
19
- hostname: `${DEFAULT_CONFIG.HTTPS_SCHEMA}://${req.headers.host}`,
20
- ws_hostname: `${DEFAULT_CONFIG.HTTPS_SCHEMA === 'https' ? 'wss' : 'ws'}://${req.headers.host}`
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
- }: Pick<RegisterPluginsParams, 'jwtSecret' | 'mongodbUrl' | 'functionsList'>): Promise<
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<Record<string, any>>(conditions).find(([key]) =>
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 { name } = config['%function']
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: [params.cursor],
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
- return role[currentType]
27
- ? await evaluateExpression(params, role[currentType], user)
28
- : undefined
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
- return !!Object.keys(role.fields ?? {}).length
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')