@flowerforce/flowerbase 1.2.1-beta.2 → 1.2.1-beta.21
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 +37 -6
- package/dist/auth/controller.d.ts.map +1 -1
- package/dist/auth/controller.js +55 -4
- package/dist/auth/plugins/jwt.d.ts.map +1 -1
- package/dist/auth/plugins/jwt.js +52 -6
- package/dist/auth/providers/anon-user/controller.d.ts +8 -0
- package/dist/auth/providers/anon-user/controller.d.ts.map +1 -0
- package/dist/auth/providers/anon-user/controller.js +90 -0
- package/dist/auth/providers/anon-user/dtos.d.ts +10 -0
- package/dist/auth/providers/anon-user/dtos.d.ts.map +1 -0
- package/dist/auth/providers/anon-user/dtos.js +2 -0
- package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
- package/dist/auth/providers/custom-function/controller.js +35 -25
- package/dist/auth/providers/custom-function/dtos.d.ts +4 -1
- package/dist/auth/providers/custom-function/dtos.d.ts.map +1 -1
- package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -1
- package/dist/auth/providers/local-userpass/controller.js +159 -73
- package/dist/auth/providers/local-userpass/dtos.d.ts +17 -2
- package/dist/auth/providers/local-userpass/dtos.d.ts.map +1 -1
- package/dist/auth/utils.d.ts +76 -14
- package/dist/auth/utils.d.ts.map +1 -1
- package/dist/auth/utils.js +55 -61
- package/dist/constants.d.ts +12 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +16 -4
- package/dist/features/functions/controller.d.ts.map +1 -1
- package/dist/features/functions/controller.js +31 -12
- 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 +3 -2
- package/dist/features/functions/utils.d.ts.map +1 -1
- package/dist/features/functions/utils.js +19 -7
- package/dist/features/triggers/index.d.ts.map +1 -1
- package/dist/features/triggers/index.js +49 -7
- package/dist/features/triggers/interface.d.ts +1 -0
- package/dist/features/triggers/interface.d.ts.map +1 -1
- package/dist/features/triggers/utils.d.ts.map +1 -1
- package/dist/features/triggers/utils.js +67 -26
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -13
- package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.js +72 -2
- package/dist/services/mongodb-atlas/model.d.ts +3 -2
- package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/utils.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/utils.js +3 -1
- package/dist/shared/handleUserRegistration.d.ts.map +1 -1
- package/dist/shared/handleUserRegistration.js +66 -1
- package/dist/shared/models/handleUserRegistration.model.d.ts +2 -1
- package/dist/shared/models/handleUserRegistration.model.d.ts.map +1 -1
- package/dist/shared/models/handleUserRegistration.model.js +1 -0
- package/dist/utils/context/helpers.d.ts +6 -6
- package/dist/utils/context/helpers.d.ts.map +1 -1
- 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 -9
- 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.js +1 -1
- package/dist/utils/initializer/registerPlugins.d.ts.map +1 -1
- package/dist/utils/initializer/registerPlugins.js +12 -4
- package/dist/utils/roles/helpers.js +2 -1
- package/dist/utils/rules-matcher/utils.d.ts.map +1 -1
- package/dist/utils/rules-matcher/utils.js +3 -0
- package/package.json +1 -2
- package/src/auth/controller.ts +71 -5
- package/src/auth/plugins/jwt.test.ts +93 -0
- package/src/auth/plugins/jwt.ts +67 -8
- package/src/auth/providers/anon-user/controller.ts +91 -0
- package/src/auth/providers/anon-user/dtos.ts +10 -0
- package/src/auth/providers/custom-function/controller.ts +40 -31
- package/src/auth/providers/custom-function/dtos.ts +5 -1
- package/src/auth/providers/local-userpass/controller.ts +211 -101
- package/src/auth/providers/local-userpass/dtos.ts +20 -2
- package/src/auth/utils.ts +66 -83
- package/src/constants.ts +14 -2
- package/src/features/functions/controller.ts +42 -12
- 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/triggers/index.ts +44 -1
- package/src/features/triggers/interface.ts +1 -0
- package/src/features/triggers/utils.ts +89 -37
- package/src/index.ts +49 -13
- package/src/services/mongodb-atlas/__tests__/findOneAndUpdate.test.ts +95 -0
- package/src/services/mongodb-atlas/index.ts +665 -567
- package/src/services/mongodb-atlas/model.ts +16 -3
- package/src/services/mongodb-atlas/utils.ts +3 -0
- package/src/shared/handleUserRegistration.ts +83 -2
- package/src/shared/models/handleUserRegistration.model.ts +2 -1
- package/src/utils/__tests__/registerPlugins.test.ts +5 -1
- package/src/utils/context/index.ts +238 -18
- package/src/utils/context/interface.ts +1 -1
- package/src/utils/crypto/index.ts +5 -1
- package/src/utils/initializer/exposeRoutes.ts +1 -1
- package/src/utils/initializer/registerPlugins.ts +8 -0
- package/src/utils/roles/helpers.ts +3 -2
- package/src/utils/rules-matcher/utils.ts +3 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { FastifyInstance } from 'fastify'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Collection,
|
|
4
|
+
Document,
|
|
5
|
+
FindCursor,
|
|
6
|
+
FindOneAndUpdateOptions,
|
|
7
|
+
Filter as MongoFilter,
|
|
8
|
+
UpdateFilter,
|
|
9
|
+
WithId
|
|
10
|
+
} from 'mongodb'
|
|
3
11
|
import { User } from '../../auth/dtos'
|
|
4
12
|
import { Filter, Rules } from '../../features/rules/interface'
|
|
5
13
|
import { Role } from '../../utils/roles/interface'
|
|
@@ -50,11 +58,16 @@ export type GetOperatorsFunction = (
|
|
|
50
58
|
updateOne: (
|
|
51
59
|
...params: Parameters<Method<'updateOne'>>
|
|
52
60
|
) => ReturnType<Method<'updateOne'>>
|
|
61
|
+
findOneAndUpdate: (
|
|
62
|
+
filter: MongoFilter<Document>,
|
|
63
|
+
update: UpdateFilter<Document> | Document[],
|
|
64
|
+
options?: FindOneAndUpdateOptions
|
|
65
|
+
) => Promise<Document | null>
|
|
53
66
|
find: (...params: Parameters<Method<'find'>>) => FindCursor
|
|
54
67
|
watch: (...params: Parameters<Method<'watch'>>) => ReturnType<Method<'watch'>>
|
|
55
68
|
aggregate: (
|
|
56
69
|
...params: [...Parameters<Method<'aggregate'>>, isClient: boolean]
|
|
57
|
-
) =>
|
|
70
|
+
) => ReturnType<Method<'aggregate'>>
|
|
58
71
|
insertMany: (
|
|
59
72
|
...params: Parameters<Method<'insertMany'>>
|
|
60
73
|
) => ReturnType<Method<'insertMany'>>
|
|
@@ -73,4 +86,4 @@ export enum CRUD_OPERATIONS {
|
|
|
73
86
|
UPDATE = "UPDATE",
|
|
74
87
|
DELETE = "DELETE"
|
|
75
88
|
|
|
76
|
-
}
|
|
89
|
+
}
|
|
@@ -23,12 +23,14 @@ export const getValidRule = <T extends Role | Filter>({
|
|
|
23
23
|
record = null
|
|
24
24
|
}: GetValidRuleParams<T>) => {
|
|
25
25
|
if (!filters.length) return []
|
|
26
|
+
const rootRecord = record ?? null
|
|
26
27
|
|
|
27
28
|
return filters.filter((f) => {
|
|
28
29
|
if (Object.keys(f.apply_when).length === 0) return true
|
|
29
30
|
|
|
30
31
|
// expandQuery traduce i placeholder (%%user, %%true)
|
|
31
32
|
const conditions = expandQuery(f.apply_when, {
|
|
33
|
+
'%%root': rootRecord,
|
|
32
34
|
'%%user': user,
|
|
33
35
|
'%%true': true
|
|
34
36
|
/** values */
|
|
@@ -40,6 +42,7 @@ export const getValidRule = <T extends Role | Filter>({
|
|
|
40
42
|
conditions as Parameters<typeof rulesMatcherUtils.checkRule>[0],
|
|
41
43
|
{
|
|
42
44
|
...(record ?? {}),
|
|
45
|
+
'%%root': rootRecord,
|
|
43
46
|
'%%user': user,
|
|
44
47
|
'%%true': true
|
|
45
48
|
},
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { AUTH_CONFIG, DB_NAME } from "../constants"
|
|
2
|
-
import {
|
|
2
|
+
import { StateManager } from "../state"
|
|
3
|
+
import { GenerateContext } from "../utils/context"
|
|
4
|
+
import { generateToken, hashPassword } from "../utils/crypto"
|
|
3
5
|
import { HandleUserRegistration } from "./models/handleUserRegistration.model"
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -17,6 +19,10 @@ const handleUserRegistration: HandleUserRegistration = (app, opt) => async ({ em
|
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
const { authCollection } = AUTH_CONFIG
|
|
22
|
+
const localUserpassConfig = AUTH_CONFIG.localUserpassConfig
|
|
23
|
+
const autoConfirm = localUserpassConfig?.autoConfirm === true
|
|
24
|
+
const runConfirmationFunction = localUserpassConfig?.runConfirmationFunction === true
|
|
25
|
+
const confirmationFunctionName = localUserpassConfig?.confirmationFunctionName
|
|
20
26
|
const mongo = app?.mongo
|
|
21
27
|
const db = mongo.client.db(DB_NAME)
|
|
22
28
|
const hashedPassword = await hashPassword(password)
|
|
@@ -29,7 +35,7 @@ const handleUserRegistration: HandleUserRegistration = (app, opt) => async ({ em
|
|
|
29
35
|
const result = await db?.collection(authCollection!).insertOne({
|
|
30
36
|
email,
|
|
31
37
|
password: hashedPassword,
|
|
32
|
-
status: skipUserCheck ? 'confirmed' : 'pending',
|
|
38
|
+
status: skipUserCheck || autoConfirm ? 'confirmed' : 'pending',
|
|
33
39
|
createdAt: new Date(),
|
|
34
40
|
custom_data: {
|
|
35
41
|
// TODO: aggiungere dati personalizzati alla registrazione
|
|
@@ -58,6 +64,81 @@ const handleUserRegistration: HandleUserRegistration = (app, opt) => async ({ em
|
|
|
58
64
|
}
|
|
59
65
|
)
|
|
60
66
|
|
|
67
|
+
if (!result?.insertedId || skipUserCheck || autoConfirm) {
|
|
68
|
+
return result
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!runConfirmationFunction) {
|
|
72
|
+
throw new Error('Missing confirmation function')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!confirmationFunctionName) {
|
|
76
|
+
throw new Error('Missing confirmation function name')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const functionsList = StateManager.select('functions')
|
|
80
|
+
const services = StateManager.select('services')
|
|
81
|
+
const confirmationFunction = functionsList[confirmationFunctionName]
|
|
82
|
+
if (!confirmationFunction) {
|
|
83
|
+
throw new Error(`Confirmation function not found: ${confirmationFunctionName}`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const token = generateToken()
|
|
87
|
+
const tokenId = generateToken()
|
|
88
|
+
await db?.collection(authCollection!).updateOne(
|
|
89
|
+
{ _id: result.insertedId },
|
|
90
|
+
{
|
|
91
|
+
$set: {
|
|
92
|
+
confirmationToken: token,
|
|
93
|
+
confirmationTokenId: tokenId
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
type ConfirmationResult = { status?: 'success' | 'pending' | 'fail' }
|
|
99
|
+
let confirmationStatus: ConfirmationResult['status'] = 'fail'
|
|
100
|
+
try {
|
|
101
|
+
const response = await GenerateContext({
|
|
102
|
+
args: [{
|
|
103
|
+
token,
|
|
104
|
+
tokenId,
|
|
105
|
+
username: email
|
|
106
|
+
}],
|
|
107
|
+
app,
|
|
108
|
+
rules: {},
|
|
109
|
+
user: {},
|
|
110
|
+
currentFunction: confirmationFunction,
|
|
111
|
+
functionsList,
|
|
112
|
+
services,
|
|
113
|
+
runAsSystem: true
|
|
114
|
+
}) as ConfirmationResult
|
|
115
|
+
confirmationStatus = response?.status ?? 'fail'
|
|
116
|
+
} catch {
|
|
117
|
+
confirmationStatus = 'fail'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (confirmationStatus === 'success') {
|
|
121
|
+
await db?.collection(authCollection!).updateOne(
|
|
122
|
+
{ _id: result.insertedId },
|
|
123
|
+
{
|
|
124
|
+
$set: { status: 'confirmed' },
|
|
125
|
+
$unset: { confirmationToken: '', confirmationTokenId: '' }
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
return result
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (confirmationStatus === 'pending') {
|
|
132
|
+
return result
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
await db?.collection(authCollection!).updateOne(
|
|
136
|
+
{ _id: result.insertedId },
|
|
137
|
+
{
|
|
138
|
+
$set: { status: 'failed' },
|
|
139
|
+
$unset: { confirmationToken: '', confirmationTokenId: '' }
|
|
140
|
+
}
|
|
141
|
+
)
|
|
61
142
|
return result
|
|
62
143
|
|
|
63
144
|
}
|
|
@@ -3,6 +3,7 @@ import fastifyMongodb from '@fastify/mongodb'
|
|
|
3
3
|
import { authController } from '../../auth/controller'
|
|
4
4
|
import jwtAuthPlugin from '../../auth/plugins/jwt'
|
|
5
5
|
import fastifyRawBody from 'fastify-raw-body'
|
|
6
|
+
import { anonUserController } from '../../auth/providers/anon-user/controller'
|
|
6
7
|
import { customFunctionController } from '../../auth/providers/custom-function/controller'
|
|
7
8
|
import { localUserPassController } from '../../auth/providers/local-userpass/controller'
|
|
8
9
|
import { Functions } from '../../features/functions/interface'
|
|
@@ -36,7 +37,7 @@ describe('registerPlugins', () => {
|
|
|
36
37
|
})
|
|
37
38
|
|
|
38
39
|
// Check Plugins Registration
|
|
39
|
-
expect(registerMock).toHaveBeenCalledTimes(
|
|
40
|
+
expect(registerMock).toHaveBeenCalledTimes(8)
|
|
40
41
|
expect(registerMock).toHaveBeenCalledWith(cors, {
|
|
41
42
|
origin: '*',
|
|
42
43
|
methods: ['POST', 'GET']
|
|
@@ -63,6 +64,9 @@ describe('registerPlugins', () => {
|
|
|
63
64
|
expect(registerMock).toHaveBeenCalledWith(customFunctionController, {
|
|
64
65
|
prefix: `${MOCKED_API_VERSION}/app/:appId/auth/providers/custom-function`
|
|
65
66
|
})
|
|
67
|
+
expect(registerMock).toHaveBeenCalledWith(anonUserController, {
|
|
68
|
+
prefix: `${MOCKED_API_VERSION}/app/:appId/auth/providers/anon-user`
|
|
69
|
+
})
|
|
66
70
|
})
|
|
67
71
|
|
|
68
72
|
it('should handle errors in the catch block', async () => {
|
|
@@ -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,7 @@ export async function GenerateContext({
|
|
|
28
134
|
deserializeArgs = true,
|
|
29
135
|
enqueue,
|
|
30
136
|
request
|
|
31
|
-
}: GenerateContextParams) {
|
|
137
|
+
}: GenerateContextParams): Promise<unknown> {
|
|
32
138
|
if (!currentFunction) return
|
|
33
139
|
|
|
34
140
|
const functionsQueue = StateManager.select("functionsQueue")
|
|
@@ -48,28 +154,142 @@ export async function GenerateContext({
|
|
|
48
154
|
request
|
|
49
155
|
})
|
|
50
156
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
62
177
|
}
|
|
63
|
-
|
|
64
|
-
|
|
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
|
+
|
|
189
|
+
try {
|
|
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 =
|
|
242
|
+
((specifier: string) => loadModule(specifier) as unknown as vm.Module) as unknown as
|
|
243
|
+
vm.SourceTextModuleOptions['importModuleDynamically']
|
|
244
|
+
|
|
245
|
+
const sourceModule = new vmModules.SourceTextModule(
|
|
246
|
+
wrapEsmModule(functionToRun.code),
|
|
247
|
+
{
|
|
248
|
+
context: vmContext,
|
|
249
|
+
identifier: entryFile,
|
|
250
|
+
initializeImportMeta: (meta) => {
|
|
251
|
+
meta.url = pathToFileURL(entryFile).href
|
|
252
|
+
},
|
|
253
|
+
importModuleDynamically
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
await sourceModule.link(loadModule)
|
|
258
|
+
await sourceModule.evaluate()
|
|
259
|
+
usedVmModules = true
|
|
260
|
+
} catch (error) {
|
|
261
|
+
if (!shouldFallbackFromVmModules(error)) {
|
|
262
|
+
throw error
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!usedVmModules) {
|
|
268
|
+
const codeToRun = functionToRun.code.includes('import ')
|
|
269
|
+
? transformImportsToRequire(functionToRun.code)
|
|
270
|
+
: functionToRun.code
|
|
271
|
+
vm.runInContext(codeToRun, vmContext)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
sandboxModule.exports = resolveExport(vmContext) ?? sandboxModule.exports
|
|
275
|
+
} catch (error) {
|
|
276
|
+
console.error(error)
|
|
277
|
+
throw error
|
|
65
278
|
}
|
|
66
279
|
|
|
67
280
|
if (deserializeArgs) {
|
|
68
|
-
return await
|
|
281
|
+
return await (sandboxModule.exports as ExportedFunction)(
|
|
282
|
+
...EJSON.deserialize(args)
|
|
283
|
+
)
|
|
69
284
|
}
|
|
70
285
|
|
|
71
|
-
return await
|
|
286
|
+
return await (sandboxModule.exports as ExportedFunction)(...args)
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
const res = await functionsQueue.add(run, enqueue)
|
|
290
|
+
return res
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.error(error)
|
|
293
|
+
throw error
|
|
72
294
|
}
|
|
73
|
-
const res = await functionsQueue.add(run, enqueue)
|
|
74
|
-
return res
|
|
75
295
|
}
|
|
@@ -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
|
+
}
|
|
@@ -18,7 +18,7 @@ export const exposeRoutes = async (fastify: FastifyInstance) => {
|
|
|
18
18
|
const headerHost = req.headers.host ?? 'localhost:3000'
|
|
19
19
|
const hostname = headerHost.split(':')[0]
|
|
20
20
|
const port = DEFAULT_CONFIG?.PORT ?? 3000
|
|
21
|
-
const host = `${hostname}:${port}`
|
|
21
|
+
const host = port === 8080 ? hostname : `${hostname}:${port}`
|
|
22
22
|
const wsSchema = 'wss'
|
|
23
23
|
|
|
24
24
|
return {
|
|
@@ -5,6 +5,7 @@ import fastifyRawBody from 'fastify-raw-body'
|
|
|
5
5
|
import { CorsConfig } from '../../'
|
|
6
6
|
import { authController } from '../../auth/controller'
|
|
7
7
|
import jwtAuthPlugin from '../../auth/plugins/jwt'
|
|
8
|
+
import { anonUserController } from '../../auth/providers/anon-user/controller'
|
|
8
9
|
import { customFunctionController } from '../../auth/providers/custom-function/controller'
|
|
9
10
|
import { localUserPassController } from '../../auth/providers/local-userpass/controller'
|
|
10
11
|
import { API_VERSION } from '../../constants'
|
|
@@ -133,6 +134,13 @@ const getRegisterConfig = async ({
|
|
|
133
134
|
options: {
|
|
134
135
|
prefix: `${API_VERSION}/app/:appId/auth/providers/custom-function`
|
|
135
136
|
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
pluginName: 'anonUserController',
|
|
140
|
+
plugin: anonUserController,
|
|
141
|
+
options: {
|
|
142
|
+
prefix: `${API_VERSION}/app/:appId/auth/providers/anon-user`
|
|
143
|
+
}
|
|
136
144
|
}
|
|
137
145
|
] as RegisterConfig[]
|
|
138
146
|
}
|
|
@@ -34,7 +34,7 @@ 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
40
|
const functionConfig = config['%function']
|
|
@@ -67,5 +67,6 @@ const evaluateComplexExpression = async (
|
|
|
67
67
|
functionsList,
|
|
68
68
|
services
|
|
69
69
|
})
|
|
70
|
-
|
|
70
|
+
const isTruthy = Boolean(response)
|
|
71
|
+
return key === '%%true' ? isTruthy : !isTruthy
|
|
71
72
|
}
|
|
@@ -154,6 +154,9 @@ const rulesMatcherUtils: RulesMatcherUtils = {
|
|
|
154
154
|
},
|
|
155
155
|
forceArray: (a) => (Array.isArray(a) ? a : [a]),
|
|
156
156
|
getPath: (path, prefix) => {
|
|
157
|
+
if (typeof path !== 'string' || path.length === 0) {
|
|
158
|
+
return ''
|
|
159
|
+
}
|
|
157
160
|
if (path.indexOf('^') === 0) {
|
|
158
161
|
return _trimStart(path, '^')
|
|
159
162
|
}
|