@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.
- package/README.md +28 -3
- package/dist/auth/controller.d.ts.map +1 -1
- package/dist/auth/controller.js +57 -3
- package/dist/auth/plugins/jwt.d.ts.map +1 -1
- package/dist/auth/plugins/jwt.js +49 -3
- package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
- package/dist/auth/providers/custom-function/controller.js +19 -3
- package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -1
- package/dist/auth/providers/local-userpass/controller.js +125 -71
- package/dist/auth/providers/local-userpass/dtos.d.ts +11 -2
- package/dist/auth/providers/local-userpass/dtos.d.ts.map +1 -1
- package/dist/auth/utils.d.ts +53 -14
- package/dist/auth/utils.d.ts.map +1 -1
- package/dist/auth/utils.js +46 -63
- package/dist/constants.d.ts +14 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +18 -5
- package/dist/features/functions/controller.d.ts.map +1 -1
- package/dist/features/functions/controller.js +32 -3
- package/dist/features/functions/dtos.d.ts +3 -0
- package/dist/features/functions/dtos.d.ts.map +1 -1
- package/dist/features/functions/interface.d.ts +3 -0
- package/dist/features/functions/interface.d.ts.map +1 -1
- package/dist/features/functions/utils.d.ts +2 -1
- package/dist/features/functions/utils.d.ts.map +1 -1
- package/dist/features/functions/utils.js +19 -7
- package/dist/features/rules/utils.d.ts.map +1 -1
- package/dist/features/rules/utils.js +11 -2
- package/dist/features/triggers/index.d.ts.map +1 -1
- package/dist/features/triggers/index.js +48 -7
- package/dist/features/triggers/utils.d.ts.map +1 -1
- package/dist/features/triggers/utils.js +118 -27
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +57 -21
- package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.js +605 -478
- package/dist/services/mongodb-atlas/model.d.ts +2 -1
- package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
- 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 +4 -1
- 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 +7 -6
- 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 +1 -1
- package/dist/utils/context/index.d.ts.map +1 -1
- package/dist/utils/context/index.js +176 -5
- package/dist/utils/context/interface.d.ts +1 -1
- package/dist/utils/context/interface.d.ts.map +1 -1
- package/dist/utils/crypto/index.d.ts +1 -0
- package/dist/utils/crypto/index.d.ts.map +1 -1
- package/dist/utils/crypto/index.js +6 -2
- 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 +11 -3
- 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 -2
- package/src/auth/controller.ts +70 -4
- package/src/auth/plugins/jwt.test.ts +93 -0
- package/src/auth/plugins/jwt.ts +62 -3
- package/src/auth/providers/custom-function/controller.ts +22 -5
- package/src/auth/providers/local-userpass/controller.ts +168 -96
- package/src/auth/providers/local-userpass/dtos.ts +13 -2
- package/src/auth/utils.ts +51 -86
- package/src/constants.ts +17 -3
- package/src/fastify.d.ts +32 -15
- package/src/features/functions/controller.ts +51 -3
- package/src/features/functions/dtos.ts +3 -0
- package/src/features/functions/interface.ts +3 -0
- package/src/features/functions/utils.ts +29 -8
- package/src/features/rules/utils.ts +11 -2
- package/src/features/triggers/index.ts +43 -1
- package/src/features/triggers/utils.ts +146 -38
- package/src/index.ts +69 -20
- package/src/services/mongodb-atlas/__tests__/findOneAndUpdate.test.ts +95 -0
- package/src/services/mongodb-atlas/__tests__/utils.test.ts +141 -0
- package/src/services/mongodb-atlas/index.ts +241 -90
- package/src/services/mongodb-atlas/model.ts +15 -2
- package/src/services/mongodb-atlas/utils.ts +158 -22
- package/src/shared/handleUserRegistration.ts +5 -4
- 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 +238 -13
- package/src/utils/context/interface.ts +1 -1
- package/src/utils/crypto/index.ts +5 -1
- package/src/utils/initializer/exposeRoutes.ts +15 -8
- package/src/utils/initializer/registerPlugins.ts +15 -7
- package/src/utils/roles/helpers.ts +23 -5
- 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
|
@@ -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
|
-
|
|
55
|
-
...contextData,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
290
|
+
return await (sandboxModule.exports as ExportedFunction)(
|
|
291
|
+
...EJSON.deserialize(args)
|
|
292
|
+
)
|
|
68
293
|
}
|
|
69
294
|
|
|
70
|
-
return await
|
|
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<
|
|
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 =
|
|
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
|
-
|
|
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
|
|
@@ -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
|
|
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,
|
|
@@ -50,5 +67,6 @@ const evaluateComplexExpression = async (
|
|
|
50
67
|
functionsList,
|
|
51
68
|
services
|
|
52
69
|
})
|
|
53
|
-
|
|
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
|
-
|
|
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')
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
checkFieldsPropertyExists,
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
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 {
|
|
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('
|
|
32
|
+
return check ? next('evaluateRead') : endValidation({ success: false })
|
|
15
33
|
},
|
|
16
|
-
|
|
17
|
-
|
|
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
|
}
|
package/src/utils/rules.ts
CHANGED
|
@@ -1,31 +1,40 @@
|
|
|
1
1
|
import get from 'lodash/get'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|