@flowerforce/flowerbase 1.2.0 → 1.2.1-beta.11

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 (125) hide show
  1. package/README.md +28 -3
  2. package/dist/auth/controller.d.ts.map +1 -1
  3. package/dist/auth/controller.js +57 -3
  4. package/dist/auth/plugins/jwt.d.ts.map +1 -1
  5. package/dist/auth/plugins/jwt.js +49 -3
  6. package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
  7. package/dist/auth/providers/custom-function/controller.js +19 -3
  8. package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -1
  9. package/dist/auth/providers/local-userpass/controller.js +125 -71
  10. package/dist/auth/providers/local-userpass/dtos.d.ts +11 -2
  11. package/dist/auth/providers/local-userpass/dtos.d.ts.map +1 -1
  12. package/dist/auth/utils.d.ts +53 -14
  13. package/dist/auth/utils.d.ts.map +1 -1
  14. package/dist/auth/utils.js +46 -63
  15. package/dist/constants.d.ts +14 -0
  16. package/dist/constants.d.ts.map +1 -1
  17. package/dist/constants.js +18 -5
  18. package/dist/features/functions/controller.d.ts.map +1 -1
  19. package/dist/features/functions/controller.js +32 -3
  20. package/dist/features/functions/dtos.d.ts +3 -0
  21. package/dist/features/functions/dtos.d.ts.map +1 -1
  22. package/dist/features/functions/interface.d.ts +3 -0
  23. package/dist/features/functions/interface.d.ts.map +1 -1
  24. package/dist/features/functions/utils.d.ts +2 -1
  25. package/dist/features/functions/utils.d.ts.map +1 -1
  26. package/dist/features/functions/utils.js +19 -7
  27. package/dist/features/rules/utils.d.ts.map +1 -1
  28. package/dist/features/rules/utils.js +11 -2
  29. package/dist/features/triggers/index.d.ts.map +1 -1
  30. package/dist/features/triggers/index.js +48 -7
  31. package/dist/features/triggers/utils.d.ts.map +1 -1
  32. package/dist/features/triggers/utils.js +118 -27
  33. package/dist/index.d.ts +8 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +57 -21
  36. package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
  37. package/dist/services/mongodb-atlas/index.js +605 -478
  38. package/dist/services/mongodb-atlas/model.d.ts +2 -1
  39. package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
  40. package/dist/services/mongodb-atlas/utils.d.ts +9 -2
  41. package/dist/services/mongodb-atlas/utils.d.ts.map +1 -1
  42. package/dist/services/mongodb-atlas/utils.js +113 -23
  43. package/dist/shared/handleUserRegistration.d.ts.map +1 -1
  44. package/dist/shared/handleUserRegistration.js +4 -1
  45. package/dist/shared/models/handleUserRegistration.model.d.ts +6 -2
  46. package/dist/shared/models/handleUserRegistration.model.d.ts.map +1 -1
  47. package/dist/utils/context/helpers.d.ts +7 -6
  48. package/dist/utils/context/helpers.d.ts.map +1 -1
  49. package/dist/utils/context/helpers.js +3 -0
  50. package/dist/utils/context/index.d.ts +1 -1
  51. package/dist/utils/context/index.d.ts.map +1 -1
  52. package/dist/utils/context/index.js +176 -5
  53. package/dist/utils/context/interface.d.ts +1 -1
  54. package/dist/utils/context/interface.d.ts.map +1 -1
  55. package/dist/utils/crypto/index.d.ts +1 -0
  56. package/dist/utils/crypto/index.d.ts.map +1 -1
  57. package/dist/utils/crypto/index.js +6 -2
  58. package/dist/utils/initializer/exposeRoutes.d.ts.map +1 -1
  59. package/dist/utils/initializer/exposeRoutes.js +11 -4
  60. package/dist/utils/initializer/registerPlugins.d.ts +3 -1
  61. package/dist/utils/initializer/registerPlugins.d.ts.map +1 -1
  62. package/dist/utils/initializer/registerPlugins.js +9 -6
  63. package/dist/utils/roles/helpers.js +11 -3
  64. package/dist/utils/roles/machines/commonValidators.d.ts.map +1 -1
  65. package/dist/utils/roles/machines/commonValidators.js +10 -6
  66. package/dist/utils/roles/machines/read/B/validators.d.ts +4 -0
  67. package/dist/utils/roles/machines/read/B/validators.d.ts.map +1 -0
  68. package/dist/utils/roles/machines/read/B/validators.js +8 -0
  69. package/dist/utils/roles/machines/read/C/index.d.ts.map +1 -1
  70. package/dist/utils/roles/machines/read/C/index.js +10 -7
  71. package/dist/utils/roles/machines/read/C/validators.d.ts +5 -0
  72. package/dist/utils/roles/machines/read/C/validators.d.ts.map +1 -0
  73. package/dist/utils/roles/machines/read/C/validators.js +29 -0
  74. package/dist/utils/roles/machines/read/D/index.d.ts.map +1 -1
  75. package/dist/utils/roles/machines/read/D/index.js +13 -11
  76. package/dist/utils/rules.d.ts +1 -1
  77. package/dist/utils/rules.d.ts.map +1 -1
  78. package/dist/utils/rules.js +26 -17
  79. package/jest.config.ts +2 -12
  80. package/jest.setup.ts +28 -0
  81. package/package.json +1 -2
  82. package/src/auth/controller.ts +70 -4
  83. package/src/auth/plugins/jwt.test.ts +93 -0
  84. package/src/auth/plugins/jwt.ts +62 -3
  85. package/src/auth/providers/custom-function/controller.ts +22 -5
  86. package/src/auth/providers/local-userpass/controller.ts +168 -96
  87. package/src/auth/providers/local-userpass/dtos.ts +13 -2
  88. package/src/auth/utils.ts +51 -86
  89. package/src/constants.ts +17 -3
  90. package/src/fastify.d.ts +32 -15
  91. package/src/features/functions/controller.ts +51 -3
  92. package/src/features/functions/dtos.ts +3 -0
  93. package/src/features/functions/interface.ts +3 -0
  94. package/src/features/functions/utils.ts +29 -8
  95. package/src/features/rules/utils.ts +11 -2
  96. package/src/features/triggers/index.ts +43 -1
  97. package/src/features/triggers/utils.ts +146 -38
  98. package/src/index.ts +69 -20
  99. package/src/services/mongodb-atlas/__tests__/findOneAndUpdate.test.ts +95 -0
  100. package/src/services/mongodb-atlas/__tests__/utils.test.ts +141 -0
  101. package/src/services/mongodb-atlas/index.ts +241 -90
  102. package/src/services/mongodb-atlas/model.ts +15 -2
  103. package/src/services/mongodb-atlas/utils.ts +158 -22
  104. package/src/shared/handleUserRegistration.ts +5 -4
  105. package/src/shared/models/handleUserRegistration.model.ts +8 -3
  106. package/src/types/fastify-raw-body.d.ts +22 -0
  107. package/src/utils/__tests__/STEP_B_STATES.test.ts +1 -1
  108. package/src/utils/__tests__/STEP_C_STATES.test.ts +1 -1
  109. package/src/utils/__tests__/STEP_D_STATES.test.ts +2 -2
  110. package/src/utils/__tests__/checkIsValidFieldNameFn.test.ts +9 -4
  111. package/src/utils/__tests__/registerPlugins.test.ts +16 -1
  112. package/src/utils/context/helpers.ts +3 -0
  113. package/src/utils/context/index.ts +238 -13
  114. package/src/utils/context/interface.ts +1 -1
  115. package/src/utils/crypto/index.ts +5 -1
  116. package/src/utils/initializer/exposeRoutes.ts +15 -8
  117. package/src/utils/initializer/registerPlugins.ts +15 -7
  118. package/src/utils/roles/helpers.ts +23 -5
  119. package/src/utils/roles/machines/commonValidators.ts +10 -5
  120. package/src/utils/roles/machines/read/B/validators.ts +8 -0
  121. package/src/utils/roles/machines/read/C/index.ts +11 -7
  122. package/src/utils/roles/machines/read/C/validators.ts +21 -0
  123. package/src/utils/roles/machines/read/D/index.ts +22 -12
  124. package/src/utils/rules.ts +31 -22
  125. package/tsconfig.spec.json +7 -0
@@ -1,10 +1,116 @@
1
1
  import { createRequire } from 'node:module'
2
+ import path from 'node:path'
3
+ import { pathToFileURL } from 'node:url'
2
4
  import vm from 'vm'
3
5
  import { EJSON } from 'bson'
4
6
  import { StateManager } from '../../state'
5
7
  import { generateContextData } from './helpers'
6
8
  import { GenerateContextParams } from './interface'
7
9
 
10
+ const dynamicImport = new Function('specifier', 'return import(specifier)') as (
11
+ specifier: string
12
+ ) => Promise<Record<string, unknown>>
13
+
14
+ const transformImportsToRequire = (code: string): string => {
15
+ let importIndex = 0
16
+ const lines = code.split(/\r?\n/)
17
+
18
+ return lines
19
+ .map((line) => {
20
+ const trimmed = line.trim()
21
+
22
+ if (/^import\s+type\s+/.test(trimmed)) {
23
+ return ''
24
+ }
25
+
26
+ const sideEffectMatch = trimmed.match(/^import\s+['"]([^'"]+)['"]\s*;?$/)
27
+ if (sideEffectMatch) {
28
+ return `require('${sideEffectMatch[1]}')`
29
+ }
30
+
31
+ const match = trimmed.match(/^import\s+(.+?)\s+from\s+['"]([^'"]+)['"]\s*;?$/)
32
+ if (!match) return line
33
+
34
+ const [, importClause, source] = match
35
+ const clause = importClause.trim()
36
+
37
+ if (clause.startsWith('{') && clause.endsWith('}')) {
38
+ const named = clause.slice(1, -1).trim()
39
+ return `const { ${named} } = require('${source}')`
40
+ }
41
+
42
+ const namespaceMatch = clause.match(/^\*\s+as\s+(\w+)$/)
43
+ if (namespaceMatch) {
44
+ return `const ${namespaceMatch[1]} = require('${source}')`
45
+ }
46
+
47
+ if (clause.includes(',')) {
48
+ const [defaultPart, restRaw] = clause.split(',', 2)
49
+ const defaultName = defaultPart.trim()
50
+ const rest = restRaw.trim()
51
+ const tmpName = `__fb_import_${importIndex++}`
52
+ const linesOut = [`const ${tmpName} = require('${source}')`]
53
+
54
+ if (defaultName) {
55
+ linesOut.push(`const ${defaultName} = ${tmpName}`)
56
+ }
57
+
58
+ if (rest.startsWith('{') && rest.endsWith('}')) {
59
+ const named = rest.slice(1, -1).trim()
60
+ linesOut.push(`const { ${named} } = ${tmpName}`)
61
+ } else {
62
+ const nsMatch = rest.match(/^\*\s+as\s+(\w+)$/)
63
+ if (nsMatch) {
64
+ linesOut.push(`const ${nsMatch[1]} = ${tmpName}`)
65
+ }
66
+ }
67
+
68
+ return linesOut.join('\n')
69
+ }
70
+
71
+ return `const ${clause} = require('${source}')`
72
+ })
73
+ .join('\n')
74
+ }
75
+
76
+ const wrapEsmModule = (code: string): string => {
77
+ const prelude = [
78
+ 'const __fb_module = { exports: {} };',
79
+ 'let exports = __fb_module.exports;',
80
+ 'let module = __fb_module;',
81
+ 'const __fb_require = globalThis.__fb_require;',
82
+ 'const require = __fb_require;',
83
+ 'const __filename = globalThis.__fb_filename;',
84
+ 'const __dirname = globalThis.__fb_dirname;'
85
+ ].join('\n')
86
+
87
+ const trailer = [
88
+ 'globalThis.__fb_module = __fb_module;',
89
+ 'globalThis.__fb_exports = exports;'
90
+ ].join('\n')
91
+
92
+ return `${prelude}\n${code}\n${trailer}`
93
+ }
94
+
95
+ const resolveImportTarget = (specifier: string, customRequire: NodeRequire): string => {
96
+ try {
97
+ const resolved = customRequire.resolve(specifier)
98
+ if (resolved.startsWith('node:')) return resolved
99
+ if (path.isAbsolute(resolved)) {
100
+ return pathToFileURL(resolved).href
101
+ }
102
+ return resolved
103
+ } catch {
104
+ return specifier
105
+ }
106
+ }
107
+
108
+ const shouldFallbackFromVmModules = (error: unknown): boolean => {
109
+ if (!error || typeof error !== 'object') return false
110
+ const code = (error as { code?: string }).code
111
+ return code === 'ERR_VM_MODULES_DISABLED' || code === 'ERR_VM_MODULES_NOT_SUPPORTED'
112
+ }
113
+
8
114
  /**
9
115
  * > Used to generate the current context
10
116
  * @testable
@@ -28,7 +134,8 @@ export async function GenerateContext({
28
134
  deserializeArgs = true,
29
135
  enqueue,
30
136
  request
31
- }: GenerateContextParams) {
137
+ }: GenerateContextParams): Promise<unknown> {
138
+ if (!currentFunction) return
32
139
 
33
140
  const functionsQueue = StateManager.select("functionsQueue")
34
141
 
@@ -47,27 +154,145 @@ export async function GenerateContext({
47
154
  request
48
155
  })
49
156
 
157
+ type ExportedFunction = (...args: unknown[]) => unknown
158
+ type SandboxModule = { exports: unknown }
159
+ type SandboxContext = vm.Context & {
160
+ exports?: unknown
161
+ module?: SandboxModule
162
+ __fb_module?: SandboxModule
163
+ __fb_exports?: unknown
164
+ __fb_require?: NodeRequire
165
+ __fb_filename?: string
166
+ __fb_dirname?: string
167
+ }
168
+
169
+ const isExportedFunction = (value: unknown): value is ExportedFunction =>
170
+ typeof value === 'function'
171
+
172
+ const getDefaultExport = (value: unknown): ExportedFunction | undefined => {
173
+ if (!value || typeof value !== 'object') return undefined
174
+ if (!('default' in value)) return undefined
175
+ const maybeDefault = (value as { default?: unknown }).default
176
+ return isExportedFunction(maybeDefault) ? maybeDefault : undefined
177
+ }
178
+
179
+ const resolveExport = (ctx: SandboxContext): ExportedFunction | undefined => {
180
+ const moduleExports = ctx.module?.exports ?? ctx.__fb_module?.exports
181
+ if (isExportedFunction(moduleExports)) return moduleExports
182
+ const contextExports = ctx.exports ?? ctx.__fb_exports
183
+ if (isExportedFunction(contextExports)) return contextExports
184
+ return getDefaultExport(moduleExports) ?? getDefaultExport(contextExports)
185
+ }
186
+
187
+ const sandboxModule: SandboxModule = { exports: {} }
188
+
50
189
  try {
51
- const entryFile = require.main?.filename ?? process.cwd();
52
- const customRequire = createRequire(entryFile);
53
-
54
- vm.runInContext(functionToRun.code, vm.createContext({
55
- ...contextData, require: customRequire,
56
- exports,
57
- module,
58
- __filename: __filename,
59
- __dirname: __dirname
60
- }));
190
+ const entryFile = require.main?.filename ?? process.cwd()
191
+ const customRequire = createRequire(entryFile)
192
+
193
+ const vmContext: SandboxContext = vm.createContext({
194
+ ...contextData,
195
+ require: customRequire,
196
+ exports: sandboxModule.exports,
197
+ module: sandboxModule,
198
+ __filename,
199
+ __dirname,
200
+ __fb_require: customRequire,
201
+ __fb_filename: __filename,
202
+ __fb_dirname: __dirname
203
+ }) as SandboxContext
204
+
205
+ const vmModules = vm as typeof vm & {
206
+ SourceTextModule?: typeof vm.SourceTextModule
207
+ SyntheticModule?: typeof vm.SyntheticModule
208
+ }
209
+ const hasStaticImport = /\bimport\s+/.test(functionToRun.code)
210
+ let usedVmModules = false
211
+
212
+ if (hasStaticImport && vmModules.SourceTextModule && vmModules.SyntheticModule) {
213
+ try {
214
+ const moduleCache = new Map<string, vm.Module>()
215
+
216
+ const loadModule = async (specifier: string): Promise<vm.Module> => {
217
+ const importTarget = resolveImportTarget(specifier, customRequire)
218
+ const cached = moduleCache.get(importTarget)
219
+ if (cached) return cached
220
+
221
+ const namespace = await dynamicImport(importTarget)
222
+ const exportNames = Object.keys(namespace)
223
+ if ('default' in namespace && !exportNames.includes('default')) {
224
+ exportNames.push('default')
225
+ }
226
+
227
+ const syntheticModule = new vmModules.SyntheticModule(
228
+ exportNames,
229
+ function () {
230
+ for (const name of exportNames) {
231
+ this.setExport(name, namespace[name])
232
+ }
233
+ },
234
+ { context: vmContext, identifier: importTarget }
235
+ )
236
+
237
+ moduleCache.set(importTarget, syntheticModule)
238
+ return syntheticModule
239
+ }
240
+
241
+ const importModuleDynamically = (async (
242
+ specifier: string
243
+ ): Promise<vm.Module> => {
244
+ const module = await loadModule(specifier)
245
+ if (module.status === 'unlinked') {
246
+ await module.link(loadModule)
247
+ }
248
+ if (module.status === 'linked') {
249
+ await module.evaluate()
250
+ }
251
+ return module
252
+ }) as unknown as vm.ScriptOptions['importModuleDynamically']
253
+
254
+ const sourceModule = new vmModules.SourceTextModule(
255
+ wrapEsmModule(functionToRun.code),
256
+ {
257
+ context: vmContext,
258
+ identifier: entryFile,
259
+ initializeImportMeta: (meta) => {
260
+ meta.url = pathToFileURL(entryFile).href
261
+ },
262
+ importModuleDynamically
263
+ }
264
+ )
265
+
266
+ await sourceModule.link(loadModule)
267
+ await sourceModule.evaluate()
268
+ usedVmModules = true
269
+ } catch (error) {
270
+ if (!shouldFallbackFromVmModules(error)) {
271
+ throw error
272
+ }
273
+ }
274
+ }
275
+
276
+ if (!usedVmModules) {
277
+ const codeToRun = functionToRun.code.includes('import ')
278
+ ? transformImportsToRequire(functionToRun.code)
279
+ : functionToRun.code
280
+ vm.runInContext(codeToRun, vmContext)
281
+ }
282
+
283
+ sandboxModule.exports = resolveExport(vmContext) ?? sandboxModule.exports
61
284
  }
62
285
  catch (e) {
63
286
  console.log(e)
64
287
  }
65
288
 
66
289
  if (deserializeArgs) {
67
- return await module.exports(...EJSON.deserialize(args))
290
+ return await (sandboxModule.exports as ExportedFunction)(
291
+ ...EJSON.deserialize(args)
292
+ )
68
293
  }
69
294
 
70
- return await module.exports(...args)
295
+ return await (sandboxModule.exports as ExportedFunction)(...args)
71
296
  }
72
297
  const res = await functionsQueue.add(run, enqueue)
73
298
  return res
@@ -20,5 +20,5 @@ export interface GenerateContextParams {
20
20
 
21
21
  type ContextRequest = Pick<FastifyRequest, "ips" | "host" | "hostname" | "url" | "method" | "ip" | "id">
22
22
  export interface GenerateContextDataParams extends Omit<GenerateContextParams, 'args'> {
23
- GenerateContext: (params: GenerateContextParams) => Promise<void>
23
+ GenerateContext: (params: GenerateContextParams) => Promise<unknown>
24
24
  }
@@ -36,6 +36,10 @@ export const comparePassword = async (plaintext: string, storedPassword: string)
36
36
  * > Generate a random token
37
37
  * @param length -> the token length
38
38
  */
39
- export const generateToken = (length = 32) => {
39
+ export const generateToken = (length = 64) => {
40
40
  return crypto.randomBytes(length).toString('hex')
41
41
  }
42
+
43
+ export const hashToken = (token: string) => {
44
+ return crypto.createHash('sha256').update(token).digest('hex')
45
+ }
@@ -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
@@ -34,15 +34,32 @@ const evaluateComplexExpression = async (
34
34
  condition: [string, Record<string, any>],
35
35
  params: MachineContext['params'],
36
36
  user: MachineContext['user']
37
- ) => {
37
+ ): Promise<boolean> => {
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,
@@ -50,5 +67,6 @@ const evaluateComplexExpression = async (
50
67
  functionsList,
51
68
  services
52
69
  })
53
- return key === '%%true' ? response : !response
70
+ const isTruthy = Boolean(response)
71
+ return key === '%%true' ? isTruthy : !isTruthy
54
72
  }
@@ -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')
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  checkFieldsPropertyExists,
3
- evaluateTopLevelPermissionsFn
4
- } from '../../commonValidators'
3
+ evaluateTopLevelReadFn,
4
+ evaluateTopLevelWriteFn
5
+ } from './validators'
5
6
  import { States } from '../../interface'
6
7
  import { logMachineInfo } from '../../utils'
7
8
 
@@ -13,10 +14,13 @@ export const STEP_C_STATES: States = {
13
14
  step: 1,
14
15
  stepName: 'evaluateTopLevelRead'
15
16
  })
16
- const check = await evaluateTopLevelPermissionsFn(context, 'read')
17
- return check
18
- ? endValidation({ success: true })
19
- : next('evaluateTopLevelWrite', { check })
17
+ const check = await evaluateTopLevelReadFn(context)
18
+ if (check) {
19
+ return checkFieldsPropertyExists(context)
20
+ ? next('checkFieldsProperty')
21
+ : endValidation({ success: true })
22
+ }
23
+ return next('evaluateTopLevelWrite', { check })
20
24
  },
21
25
  evaluateTopLevelWrite: async ({ context, next, endValidation }) => {
22
26
  logMachineInfo({
@@ -25,7 +29,7 @@ export const STEP_C_STATES: States = {
25
29
  step: 2,
26
30
  stepName: 'evaluateTopLevelWrite'
27
31
  })
28
- const check = await evaluateTopLevelPermissionsFn(context, 'write')
32
+ const check = await evaluateTopLevelWriteFn(context)
29
33
  if (check) return endValidation({ success: true })
30
34
  return context?.prevParams?.check === false
31
35
  ? endValidation({ success: false })
@@ -0,0 +1,21 @@
1
+ import { MachineContext } from '../../interface'
2
+ import {
3
+ checkFieldsPropertyExists,
4
+ evaluateTopLevelPermissionsFn
5
+ } from '../../commonValidators'
6
+
7
+ export const evaluateTopLevelReadFn = async (context: MachineContext) => {
8
+ if (context.params.type !== 'read') {
9
+ return false
10
+ }
11
+ return evaluateTopLevelPermissionsFn(context, 'read')
12
+ }
13
+
14
+ export const evaluateTopLevelWriteFn = async (context: MachineContext) => {
15
+ if (!['read', 'write'].includes(context.params.type)) {
16
+ return undefined
17
+ }
18
+ return evaluateTopLevelPermissionsFn(context, 'write')
19
+ }
20
+
21
+ export { checkFieldsPropertyExists } from '../../commonValidators'
@@ -1,7 +1,25 @@
1
- import { States } from '../../interface'
1
+ import { Document } from 'mongodb'
2
+ import { MachineContext, States } from '../../interface'
2
3
  import { logMachineInfo } from '../../utils'
3
4
  import { checkAdditionalFieldsFn, checkIsValidFieldNameFn } from './validators'
4
5
 
6
+ const runCheckIsValidFieldName = async ({
7
+ context,
8
+ endValidation
9
+ }: {
10
+ context: MachineContext
11
+ endValidation: ({ success, document }: { success: boolean; document?: Document }) => void
12
+ }) => {
13
+ logMachineInfo({
14
+ enabled: context.enableLog,
15
+ machine: 'D',
16
+ step: 2,
17
+ stepName: 'checkIsValidFieldName'
18
+ })
19
+ const document = checkIsValidFieldNameFn(context)
20
+ return endValidation({ success: !!Object.keys(document).length, document })
21
+ }
22
+
5
23
  export const STEP_D_STATES: States = {
6
24
  checkAdditionalFields: async ({ context, next, endValidation }) => {
7
25
  logMachineInfo({
@@ -11,16 +29,8 @@ export const STEP_D_STATES: States = {
11
29
  stepName: 'checkAdditionalFields'
12
30
  })
13
31
  const check = checkAdditionalFieldsFn(context)
14
- return check ? next('checkIsValidFieldName') : endValidation({ success: false })
32
+ return check ? next('evaluateRead') : endValidation({ success: false })
15
33
  },
16
- checkIsValidFieldName: async ({ context, endValidation }) => {
17
- logMachineInfo({
18
- enabled: context.enableLog,
19
- machine: 'D',
20
- step: 2,
21
- stepName: 'checkIsValidFieldName'
22
- })
23
- const document = checkIsValidFieldNameFn(context)
24
- return endValidation({ success: !!Object.keys(document).length, document })
25
- }
34
+ evaluateRead: runCheckIsValidFieldName,
35
+ checkIsValidFieldName: runCheckIsValidFieldName
26
36
  }
@@ -1,31 +1,40 @@
1
1
  import get from 'lodash/get'
2
2
 
3
- const removeExtraColons = (val: unknown) => {
4
- return val?.toString().replace(/:+/g, ":")
3
+ const resolvePlaceholder = (value: string, objs: Record<string, unknown>) => {
4
+ if (!value.startsWith('%%')) return value
5
+
6
+ const path = value.slice(2)
7
+ const [rootKey, ...rest] = path.split('.')
8
+ const rootToken = `%%${rootKey}`
9
+ const rootValue = objs[rootToken]
10
+
11
+ if (!rest.length) {
12
+ return rootValue === undefined ? value : rootValue
13
+ }
14
+
15
+ const resolved = get(rootValue as object, rest.join('.'))
16
+ return resolved === undefined ? value : resolved
5
17
  }
6
18
 
7
- // Funzione che espande dinamicamente i placeholder con supporto per percorsi annidati
19
+ const expandValue = (input: unknown, objs: Record<string, unknown>): unknown => {
20
+ if (Array.isArray(input)) {
21
+ return input.map((item) => expandValue(item, objs))
22
+ }
23
+ if (input && typeof input === 'object') {
24
+ return Object.fromEntries(
25
+ Object.entries(input).map(([key, val]) => [key, expandValue(val, objs)])
26
+ )
27
+ }
28
+ if (typeof input === 'string') {
29
+ return resolvePlaceholder(input, objs)
30
+ }
31
+ return input
32
+ }
33
+
34
+ // Espande dinamicamente i placeholder con supporto per array e percorsi annidati
8
35
  export function expandQuery(
9
36
  template: Record<string, unknown>,
10
37
  objs: Record<string, unknown>
11
38
  ) {
12
- let expandedQuery = JSON.stringify(template) // Converti l'oggetto in una stringa per sostituire i placeholder
13
- const regex = /:\s*"%%([a-zA-Z0-9_.]+)"/g
14
- Object.keys(objs).forEach(() => {
15
- // Espandi tutti i placeholder %%values.<nested.property>
16
-
17
- const callback = (match: string, path: string) => {
18
- const value = get(objs, `%%${path}`) // Recupera il valore annidato da values
19
- const finalValue = typeof value === 'string' ? `"${value}"` : value && JSON.stringify(value)
20
- // TODO tolto i primi : creava questo tipo di oggetto {"userId"::"%%user.id"}
21
- const val = `:${value !== undefined ? finalValue : match}`; // Sostituisci se esiste, altrimenti lascia il placeholder
22
- return removeExtraColons(val)
23
- }
24
-
25
- expandedQuery = expandedQuery.replace(
26
- regex,
27
- callback as Parameters<typeof expandedQuery.replaceAll>[1]
28
- )
29
- })
30
- return JSON.parse(expandedQuery) // Converti la stringa JSON di nuovo in un oggetto
39
+ return expandValue(template, objs) as Record<string, unknown>
31
40
  }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "types": ["node", "jest"]
5
+ },
6
+ "include": ["src/**/*.test.ts", "src/**/*.spec.ts"]
7
+ }