@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
|
@@ -34,6 +34,13 @@ const logFunctionCall = (method: string, user: Record<string, any> | undefined,
|
|
|
34
34
|
console.log('[functions-debug]', method, user ? { id: user.id, role: user.role, email: user.email } : 'no-user', args)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
const formatFunctionExecutionError = (error: unknown) => {
|
|
38
|
+
const err = error as { message?: string; name?: string }
|
|
39
|
+
const message = typeof err?.message === 'string' ? err.message : String(error)
|
|
40
|
+
const name = typeof err?.name === 'string' ? err.name : 'Error'
|
|
41
|
+
return JSON.stringify({ message, name })
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
/**
|
|
38
45
|
* > Creates a pre handler for every query
|
|
39
46
|
* @param app -> the fastify instance
|
|
@@ -61,7 +68,18 @@ export const functionsController: FunctionController = async (
|
|
|
61
68
|
if (!serviceFn) {
|
|
62
69
|
throw new Error(`Service "${req.body.service}" does not exist`)
|
|
63
70
|
}
|
|
64
|
-
const [{
|
|
71
|
+
const [{
|
|
72
|
+
database,
|
|
73
|
+
collection,
|
|
74
|
+
query,
|
|
75
|
+
filter,
|
|
76
|
+
update,
|
|
77
|
+
options,
|
|
78
|
+
returnNewDocument,
|
|
79
|
+
document,
|
|
80
|
+
documents,
|
|
81
|
+
pipeline = []
|
|
82
|
+
}] = args
|
|
65
83
|
|
|
66
84
|
const currentMethod = serviceFn(app, { rules, user })
|
|
67
85
|
.db(database)
|
|
@@ -71,7 +89,10 @@ export const functionsController: FunctionController = async (
|
|
|
71
89
|
const operatorsByType = await executeQuery({
|
|
72
90
|
currentMethod,
|
|
73
91
|
query,
|
|
92
|
+
filter,
|
|
74
93
|
update,
|
|
94
|
+
options,
|
|
95
|
+
returnNewDocument,
|
|
75
96
|
document,
|
|
76
97
|
documents,
|
|
77
98
|
pipeline,
|
|
@@ -91,17 +112,26 @@ export const functionsController: FunctionController = async (
|
|
|
91
112
|
}
|
|
92
113
|
|
|
93
114
|
logFunctionCall(`function:${method}`, user, args)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
115
|
+
try {
|
|
116
|
+
const result = await GenerateContext({
|
|
117
|
+
args: req.body.arguments,
|
|
118
|
+
app,
|
|
119
|
+
rules,
|
|
120
|
+
user: { ...user, _id: new ObjectId(user.id) },
|
|
121
|
+
currentFunction,
|
|
122
|
+
functionsList,
|
|
123
|
+
services
|
|
124
|
+
})
|
|
125
|
+
res.type('application/json')
|
|
126
|
+
return JSON.stringify(result)
|
|
127
|
+
} catch (error) {
|
|
128
|
+
res.status(500)
|
|
129
|
+
res.type('application/json')
|
|
130
|
+
return JSON.stringify({
|
|
131
|
+
error: formatFunctionExecutionError(error),
|
|
132
|
+
error_code: 'FunctionExecutionError'
|
|
133
|
+
})
|
|
134
|
+
}
|
|
105
135
|
})
|
|
106
136
|
app.get<{
|
|
107
137
|
Querystring: FunctionCallBase64Dto
|
|
@@ -23,8 +23,11 @@ export type FunctionCallBase64Dto = {
|
|
|
23
23
|
type ArgumentsData = Arguments<{
|
|
24
24
|
database: string
|
|
25
25
|
collection: string
|
|
26
|
+
filter?: Document
|
|
26
27
|
query: Parameters<GetOperatorsFunction>
|
|
27
28
|
update: Document
|
|
29
|
+
options?: Document
|
|
30
|
+
returnNewDocument?: boolean
|
|
28
31
|
document: Document
|
|
29
32
|
documents: Document[]
|
|
30
33
|
pipeline?: Document[]
|
|
@@ -24,6 +24,9 @@ export type ExecuteQueryParams = {
|
|
|
24
24
|
currentMethod: ReturnType<GetOperatorsFunction>[keyof ReturnType<GetOperatorsFunction>]
|
|
25
25
|
query: Parameters<GetOperatorsFunction>
|
|
26
26
|
update: Document
|
|
27
|
+
filter?: Document
|
|
28
|
+
options?: Document
|
|
29
|
+
returnNewDocument?: boolean
|
|
27
30
|
document: Document
|
|
28
31
|
documents: Document[]
|
|
29
32
|
pipeline: Document[]
|
|
@@ -44,29 +44,51 @@ export const executeQuery = async ({
|
|
|
44
44
|
currentMethod,
|
|
45
45
|
query,
|
|
46
46
|
update,
|
|
47
|
+
filter,
|
|
48
|
+
options,
|
|
49
|
+
returnNewDocument,
|
|
47
50
|
document,
|
|
48
51
|
documents,
|
|
49
52
|
pipeline,
|
|
50
53
|
isClient = false
|
|
51
54
|
}: ExecuteQueryParams) => {
|
|
55
|
+
const resolvedQuery =
|
|
56
|
+
typeof query !== 'undefined'
|
|
57
|
+
? query
|
|
58
|
+
: typeof filter !== 'undefined'
|
|
59
|
+
? filter
|
|
60
|
+
: {}
|
|
61
|
+
const resolvedUpdate = typeof update !== 'undefined' ? update : {}
|
|
62
|
+
const resolvedOptions =
|
|
63
|
+
typeof options !== 'undefined'
|
|
64
|
+
? options
|
|
65
|
+
: typeof returnNewDocument === 'boolean'
|
|
66
|
+
? { returnDocument: returnNewDocument ? 'after' : 'before' }
|
|
67
|
+
: undefined
|
|
52
68
|
return {
|
|
53
69
|
find: async () =>
|
|
54
70
|
await (currentMethod as ReturnType<GetOperatorsFunction>['find'])(
|
|
55
|
-
EJSON.deserialize(
|
|
71
|
+
EJSON.deserialize(resolvedQuery)
|
|
56
72
|
).toArray(),
|
|
57
73
|
findOne: () =>
|
|
58
74
|
(currentMethod as ReturnType<GetOperatorsFunction>['findOne'])(
|
|
59
|
-
EJSON.deserialize(
|
|
75
|
+
EJSON.deserialize(resolvedQuery)
|
|
60
76
|
),
|
|
61
77
|
deleteOne: () =>
|
|
62
78
|
(currentMethod as ReturnType<GetOperatorsFunction>['deleteOne'])(
|
|
63
|
-
EJSON.deserialize(
|
|
79
|
+
EJSON.deserialize(resolvedQuery)
|
|
64
80
|
),
|
|
65
81
|
insertOne: () =>
|
|
66
82
|
(currentMethod as ReturnType<GetOperatorsFunction>['insertOne'])(
|
|
67
83
|
EJSON.deserialize(document)
|
|
68
84
|
),
|
|
69
|
-
updateOne: () => currentMethod(EJSON.deserialize(
|
|
85
|
+
updateOne: () => currentMethod(EJSON.deserialize(resolvedQuery), EJSON.deserialize(resolvedUpdate)),
|
|
86
|
+
findOneAndUpdate: () =>
|
|
87
|
+
(currentMethod as ReturnType<GetOperatorsFunction>['findOneAndUpdate'])(
|
|
88
|
+
EJSON.deserialize(resolvedQuery),
|
|
89
|
+
EJSON.deserialize(resolvedUpdate),
|
|
90
|
+
resolvedOptions ? EJSON.deserialize(resolvedOptions) : undefined
|
|
91
|
+
),
|
|
70
92
|
aggregate: async () =>
|
|
71
93
|
(await (currentMethod as ReturnType<GetOperatorsFunction>['aggregate'])(
|
|
72
94
|
EJSON.deserialize(pipeline),
|
|
@@ -79,13 +101,12 @@ export const executeQuery = async ({
|
|
|
79
101
|
),
|
|
80
102
|
updateMany: () =>
|
|
81
103
|
(currentMethod as ReturnType<GetOperatorsFunction>['updateMany'])(
|
|
82
|
-
EJSON.deserialize(
|
|
83
|
-
EJSON.deserialize(
|
|
104
|
+
EJSON.deserialize(resolvedQuery),
|
|
105
|
+
EJSON.deserialize(resolvedUpdate)
|
|
84
106
|
),
|
|
85
107
|
deleteMany: () =>
|
|
86
108
|
(currentMethod as ReturnType<GetOperatorsFunction>['deleteMany'])(
|
|
87
|
-
EJSON.deserialize(
|
|
109
|
+
EJSON.deserialize(resolvedQuery)
|
|
88
110
|
)
|
|
89
111
|
}
|
|
90
112
|
}
|
|
91
|
-
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AUTH_CONFIG, DB_NAME } from '../../constants'
|
|
1
2
|
import { services } from '../../services'
|
|
2
3
|
import { Function, Functions } from '../functions/interface'
|
|
3
4
|
import { ActivateTriggersParams } from './dtos'
|
|
@@ -17,7 +18,49 @@ export const activateTriggers = async ({
|
|
|
17
18
|
}: ActivateTriggersParams) => {
|
|
18
19
|
console.log('START ACTIVATION TRIGGERS')
|
|
19
20
|
try {
|
|
20
|
-
|
|
21
|
+
const triggersToActivate = [...triggersList]
|
|
22
|
+
if (AUTH_CONFIG.on_user_creation_function_name) {
|
|
23
|
+
const alreadyDeclared = triggersToActivate.some(
|
|
24
|
+
(trigger) =>
|
|
25
|
+
trigger.content.type === 'AUTHENTICATION' &&
|
|
26
|
+
trigger.content.event_processors?.FUNCTION?.config?.function_name ===
|
|
27
|
+
AUTH_CONFIG.on_user_creation_function_name
|
|
28
|
+
)
|
|
29
|
+
if (!alreadyDeclared) {
|
|
30
|
+
triggersToActivate.push({
|
|
31
|
+
fileName: '__auto_on_user_creation_trigger__.json',
|
|
32
|
+
content: {
|
|
33
|
+
name: 'onUserCreation',
|
|
34
|
+
type: 'AUTHENTICATION',
|
|
35
|
+
disabled: false,
|
|
36
|
+
config: {
|
|
37
|
+
isAutoTrigger: true,
|
|
38
|
+
collection: AUTH_CONFIG.authCollection ?? 'auth_users',
|
|
39
|
+
database: DB_NAME,
|
|
40
|
+
full_document: true,
|
|
41
|
+
full_document_before_change: false,
|
|
42
|
+
match: {},
|
|
43
|
+
operation_types: ['insert', 'update', 'replace'],
|
|
44
|
+
project: {},
|
|
45
|
+
service_name: 'mongodb-atlas',
|
|
46
|
+
skip_catchup_events: false,
|
|
47
|
+
tolerate_resume_errors: false,
|
|
48
|
+
unordered: false,
|
|
49
|
+
schedule: ''
|
|
50
|
+
},
|
|
51
|
+
event_processors: {
|
|
52
|
+
FUNCTION: {
|
|
53
|
+
config: {
|
|
54
|
+
function_name: AUTH_CONFIG.on_user_creation_function_name
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for await (const trigger of triggersToActivate) {
|
|
21
64
|
const { content } = trigger
|
|
22
65
|
const { type, config, event_processors } = content
|
|
23
66
|
|
|
@@ -110,55 +110,107 @@ const handleAuthenticationTrigger = async ({
|
|
|
110
110
|
services,
|
|
111
111
|
app
|
|
112
112
|
}: HandlerParams) => {
|
|
113
|
-
const { database } = config
|
|
113
|
+
const { database, isAutoTrigger } = config
|
|
114
|
+
const authCollection = AUTH_CONFIG.authCollection ?? 'auth_users'
|
|
115
|
+
const collection = app.mongo.client.db(database || DB_NAME).collection(authCollection)
|
|
114
116
|
const pipeline = [
|
|
115
117
|
{
|
|
116
118
|
$match: {
|
|
117
|
-
operationType: { $in: ['insert'] }
|
|
119
|
+
operationType: { $in: ['insert', 'update', 'replace'] }
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
]
|
|
121
|
-
const changeStream =
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
.watch(pipeline, {
|
|
125
|
-
fullDocument: 'whenAvailable'
|
|
126
|
-
})
|
|
123
|
+
const changeStream = collection.watch(pipeline, {
|
|
124
|
+
fullDocument: 'whenAvailable'
|
|
125
|
+
})
|
|
127
126
|
changeStream.on('error', (error) => {
|
|
128
127
|
if (shouldIgnoreStreamError(error)) return
|
|
129
128
|
console.error('Authentication trigger change stream error', error)
|
|
130
129
|
})
|
|
131
130
|
changeStream.on('change', async function (change) {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
delete currentUser.password
|
|
142
|
-
await GenerateContext({
|
|
143
|
-
args: [{
|
|
144
|
-
user: {
|
|
145
|
-
...currentUser,
|
|
146
|
-
id: currentUser._id.toString(),
|
|
147
|
-
data: {
|
|
148
|
-
_id: currentUser._id.toString(),
|
|
149
|
-
email: currentUser.email
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}],
|
|
153
|
-
app,
|
|
154
|
-
rules: StateManager.select("rules"),
|
|
155
|
-
user: {}, // TODO from currentUser ??
|
|
156
|
-
currentFunction: triggerHandler,
|
|
157
|
-
functionsList,
|
|
158
|
-
services,
|
|
159
|
-
runAsSystem: true
|
|
160
|
-
})
|
|
131
|
+
const operationType = change['operationType' as keyof typeof change] as string | undefined
|
|
132
|
+
const documentKey = change['documentKey' as keyof typeof change] as
|
|
133
|
+
| { _id?: unknown }
|
|
134
|
+
| undefined
|
|
135
|
+
const fullDocument = change['fullDocument' as keyof typeof change] as
|
|
136
|
+
| Record<string, unknown>
|
|
137
|
+
| null
|
|
138
|
+
if (!documentKey?._id) {
|
|
139
|
+
return
|
|
161
140
|
}
|
|
141
|
+
|
|
142
|
+
const updateDescription = change[
|
|
143
|
+
'updateDescription' as keyof typeof change
|
|
144
|
+
] as { updatedFields?: Record<string, unknown> } | undefined
|
|
145
|
+
const updatedStatus = updateDescription?.updatedFields?.status
|
|
146
|
+
let confirmedCandidate = false
|
|
147
|
+
let confirmedDocument =
|
|
148
|
+
fullDocument as Record<string, unknown> | null
|
|
149
|
+
|
|
150
|
+
if (operationType === 'update') {
|
|
151
|
+
if (updatedStatus === 'confirmed') {
|
|
152
|
+
confirmedCandidate = true
|
|
153
|
+
} else if (updatedStatus === undefined) {
|
|
154
|
+
const fetched = await collection.findOne({
|
|
155
|
+
_id: documentKey._id
|
|
156
|
+
}) as Record<string, unknown> | null
|
|
157
|
+
confirmedDocument = fetched ?? confirmedDocument
|
|
158
|
+
confirmedCandidate = (confirmedDocument as { status?: string } | null)?.status === 'confirmed'
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
confirmedCandidate = (confirmedDocument as { status?: string } | null)?.status === 'confirmed'
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!confirmedCandidate) {
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const updateResult = await collection.findOneAndUpdate(
|
|
169
|
+
{
|
|
170
|
+
_id: documentKey._id,
|
|
171
|
+
status: 'confirmed',
|
|
172
|
+
on_user_creation_triggered_at: { $exists: false }
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
$set: {
|
|
176
|
+
on_user_creation_triggered_at: new Date()
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
returnDocument: 'after'
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
const document =
|
|
185
|
+
(updateResult?.value as Record<string, unknown> | null) ?? confirmedDocument
|
|
186
|
+
if (!document) {
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
delete (document as { password?: unknown }).password
|
|
191
|
+
|
|
192
|
+
const currentUser = { ...document }
|
|
193
|
+
delete (currentUser as { password?: unknown }).password
|
|
194
|
+
|
|
195
|
+
const userData = {
|
|
196
|
+
...currentUser,
|
|
197
|
+
id: (currentUser as { _id: { toString: () => string } })._id.toString(),
|
|
198
|
+
data: {
|
|
199
|
+
_id: (currentUser as { _id: { toString: () => string } })._id.toString(),
|
|
200
|
+
email: (currentUser as { email?: string }).email
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// TODO change va ripulito
|
|
204
|
+
await GenerateContext({
|
|
205
|
+
args: isAutoTrigger ? [userData] : [{ user: userData /*, ...change */ }],
|
|
206
|
+
app,
|
|
207
|
+
rules: StateManager.select("rules"),
|
|
208
|
+
user: {}, // TODO from currentUser ??
|
|
209
|
+
currentFunction: triggerHandler,
|
|
210
|
+
functionsList,
|
|
211
|
+
services,
|
|
212
|
+
runAsSystem: true
|
|
213
|
+
})
|
|
162
214
|
})
|
|
163
215
|
registerOnClose(
|
|
164
216
|
app,
|
package/src/index.ts
CHANGED
|
@@ -49,24 +49,34 @@ export async function initialize({
|
|
|
49
49
|
corsConfig = DEFAULT_CONFIG.CORS_OPTIONS,
|
|
50
50
|
basePath
|
|
51
51
|
}: InitializeConfig) {
|
|
52
|
+
if (!jwtSecret || jwtSecret.trim().length === 0) {
|
|
53
|
+
throw new Error('JWT secret missing: set JWT_SECRET or pass jwtSecret to initialize()')
|
|
54
|
+
}
|
|
55
|
+
|
|
52
56
|
const resolvedBasePath = basePath ?? require.main?.path ?? process.cwd()
|
|
53
57
|
const fastify = Fastify({
|
|
54
58
|
logger: !!DEFAULT_CONFIG.ENABLE_LOGGER
|
|
55
59
|
})
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
const isTest = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined
|
|
62
|
+
const logInfo = (...args: unknown[]) => {
|
|
63
|
+
if (!isTest) {
|
|
64
|
+
console.log(...args)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
logInfo("BASE PATH", resolvedBasePath)
|
|
69
|
+
logInfo("CURRENT PORT", port)
|
|
70
|
+
logInfo("CURRENT HOST", host)
|
|
61
71
|
|
|
62
72
|
const functionsList = await loadFunctions(resolvedBasePath)
|
|
63
|
-
|
|
73
|
+
logInfo("Functions LOADED")
|
|
64
74
|
const triggersList = await loadTriggers(resolvedBasePath)
|
|
65
|
-
|
|
75
|
+
logInfo("Triggers LOADED")
|
|
66
76
|
const endpointsList = await loadEndpoints(resolvedBasePath)
|
|
67
|
-
|
|
77
|
+
logInfo("Endpoints LOADED")
|
|
68
78
|
const rulesList = await loadRules(resolvedBasePath)
|
|
69
|
-
|
|
79
|
+
logInfo("Rules LOADED")
|
|
70
80
|
|
|
71
81
|
const stateConfig = {
|
|
72
82
|
functions: functionsList,
|
|
@@ -90,7 +100,33 @@ export async function initialize({
|
|
|
90
100
|
deepLinking: false
|
|
91
101
|
},
|
|
92
102
|
uiHooks: {
|
|
93
|
-
onRequest: function (request, reply, next) {
|
|
103
|
+
onRequest: function (request, reply, next) {
|
|
104
|
+
const swaggerUser = DEFAULT_CONFIG.SWAGGER_UI_USER
|
|
105
|
+
const swaggerPassword = DEFAULT_CONFIG.SWAGGER_UI_PASSWORD
|
|
106
|
+
if (!swaggerUser && !swaggerPassword) {
|
|
107
|
+
next()
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
const authHeader = request.headers.authorization
|
|
111
|
+
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
|
112
|
+
reply
|
|
113
|
+
.code(401)
|
|
114
|
+
.header('WWW-Authenticate', 'Basic realm="Swagger UI"')
|
|
115
|
+
.send({ message: 'Unauthorized' })
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
const encoded = authHeader.slice('Basic '.length)
|
|
119
|
+
const decoded = Buffer.from(encoded, 'base64').toString('utf8')
|
|
120
|
+
const [user, pass] = decoded.split(':')
|
|
121
|
+
if (user !== swaggerUser || pass !== swaggerPassword) {
|
|
122
|
+
reply
|
|
123
|
+
.code(401)
|
|
124
|
+
.header('WWW-Authenticate', 'Basic realm="Swagger UI"')
|
|
125
|
+
.send({ message: 'Unauthorized' })
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
next()
|
|
129
|
+
},
|
|
94
130
|
preHandler: function (request, reply, next) { next() }
|
|
95
131
|
},
|
|
96
132
|
staticCSP: true,
|
|
@@ -107,15 +143,15 @@ export async function initialize({
|
|
|
107
143
|
corsConfig
|
|
108
144
|
})
|
|
109
145
|
|
|
110
|
-
|
|
146
|
+
logInfo('Plugins registration COMPLETED')
|
|
111
147
|
await exposeRoutes(fastify)
|
|
112
|
-
|
|
148
|
+
logInfo('APP Routes registration COMPLETED')
|
|
113
149
|
await registerFunctions({ app: fastify, functionsList, rulesList })
|
|
114
|
-
|
|
150
|
+
logInfo('Functions registration COMPLETED')
|
|
115
151
|
await generateEndpoints({ app: fastify, functionsList, endpointsList, rulesList })
|
|
116
|
-
|
|
152
|
+
logInfo('HTTP Endpoints registration COMPLETED')
|
|
117
153
|
fastify.ready(() => {
|
|
118
|
-
|
|
154
|
+
logInfo("FASTIFY IS READY")
|
|
119
155
|
if (triggersList?.length > 0) activateTriggers({ fastify, triggersList, functionsList })
|
|
120
156
|
})
|
|
121
157
|
await fastify.listen({ port, host })
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Document, ObjectId } from 'mongodb'
|
|
2
|
+
import MongoDbAtlas from '..'
|
|
3
|
+
import { Role, Rules } from '../../../features/rules/interface'
|
|
4
|
+
|
|
5
|
+
const createAppWithCollection = (collection: Record<string, unknown>) => ({
|
|
6
|
+
mongo: {
|
|
7
|
+
client: {
|
|
8
|
+
db: jest.fn().mockReturnValue({
|
|
9
|
+
collection: jest.fn().mockReturnValue(collection)
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const createRules = (roleOverrides: Partial<Role> = {}): Rules => ({
|
|
16
|
+
todos: {
|
|
17
|
+
database: 'db',
|
|
18
|
+
collection: 'todos',
|
|
19
|
+
filters: [],
|
|
20
|
+
roles: [
|
|
21
|
+
{
|
|
22
|
+
name: 'owner',
|
|
23
|
+
apply_when: {},
|
|
24
|
+
insert: true,
|
|
25
|
+
delete: true,
|
|
26
|
+
search: true,
|
|
27
|
+
read: true,
|
|
28
|
+
write: true,
|
|
29
|
+
...roleOverrides
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('mongodb-atlas findOneAndUpdate', () => {
|
|
36
|
+
it('applies write/read validation and returns the updated document', async () => {
|
|
37
|
+
const id = new ObjectId()
|
|
38
|
+
const existingDoc = { _id: id, title: 'Old', userId: 'user-1' }
|
|
39
|
+
const updatedDoc = { _id: id, title: 'New', userId: 'user-1' }
|
|
40
|
+
const findOne = jest.fn().mockResolvedValue(existingDoc)
|
|
41
|
+
const aggregate = jest.fn().mockReturnValue({
|
|
42
|
+
toArray: jest.fn().mockResolvedValue([updatedDoc])
|
|
43
|
+
})
|
|
44
|
+
const findOneAndUpdate = jest.fn().mockResolvedValue(updatedDoc)
|
|
45
|
+
const collection = {
|
|
46
|
+
collectionName: 'todos',
|
|
47
|
+
findOne,
|
|
48
|
+
aggregate,
|
|
49
|
+
findOneAndUpdate
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const app = createAppWithCollection(collection)
|
|
53
|
+
const operators = MongoDbAtlas(app as any, {
|
|
54
|
+
rules: createRules(),
|
|
55
|
+
user: { id: 'user-1' }
|
|
56
|
+
})
|
|
57
|
+
.db('db')
|
|
58
|
+
.collection('todos')
|
|
59
|
+
|
|
60
|
+
const result = await operators.findOneAndUpdate({ _id: id }, { $set: { title: 'New' } })
|
|
61
|
+
|
|
62
|
+
expect(findOne).toHaveBeenCalled()
|
|
63
|
+
expect(aggregate).toHaveBeenCalled()
|
|
64
|
+
expect(findOneAndUpdate).toHaveBeenCalledWith(
|
|
65
|
+
{ $and: [{ _id: id }] },
|
|
66
|
+
{ $set: { title: 'New' } }
|
|
67
|
+
)
|
|
68
|
+
expect(result).toEqual(updatedDoc)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('rejects updates when write permission is denied', async () => {
|
|
72
|
+
const id = new ObjectId()
|
|
73
|
+
const existingDoc = { _id: id, title: 'Old', userId: 'user-1' }
|
|
74
|
+
const findOne = jest.fn().mockResolvedValue(existingDoc)
|
|
75
|
+
const findOneAndUpdate = jest.fn()
|
|
76
|
+
const collection = {
|
|
77
|
+
collectionName: 'todos',
|
|
78
|
+
findOne,
|
|
79
|
+
findOneAndUpdate
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const app = createAppWithCollection(collection)
|
|
83
|
+
const operators = MongoDbAtlas(app as any, {
|
|
84
|
+
rules: createRules({ write: false }),
|
|
85
|
+
user: { id: 'user-1' }
|
|
86
|
+
})
|
|
87
|
+
.db('db')
|
|
88
|
+
.collection('todos')
|
|
89
|
+
|
|
90
|
+
await expect(
|
|
91
|
+
operators.findOneAndUpdate({ _id: id }, { title: 'Denied' } as Document)
|
|
92
|
+
).rejects.toThrow('Update not permitted')
|
|
93
|
+
expect(findOneAndUpdate).not.toHaveBeenCalled()
|
|
94
|
+
})
|
|
95
|
+
})
|