@flowerforce/flowerbase 1.2.1-beta.2 → 1.2.1-beta.20
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/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/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 +100 -2
- package/src/services/mongodb-atlas/model.ts +16 -3
- 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/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
|
+
})
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import isEqual from 'lodash/isEqual'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Collection,
|
|
4
|
+
Document,
|
|
5
|
+
EventsDescription,
|
|
6
|
+
Filter as MongoFilter,
|
|
7
|
+
FindOneAndUpdateOptions,
|
|
8
|
+
UpdateFilter,
|
|
9
|
+
WithId
|
|
10
|
+
} from 'mongodb'
|
|
3
11
|
import { checkValidation } from '../../utils/roles/machines'
|
|
4
12
|
import { getWinningRole } from '../../utils/roles/machines/utils'
|
|
5
13
|
import { CRUD_OPERATIONS, GetOperatorsFunction, MongodbAtlasFunction } from './model'
|
|
@@ -305,6 +313,96 @@ const getOperators: GetOperatorsFunction = (
|
|
|
305
313
|
}
|
|
306
314
|
return collection.updateOne(query, data, options)
|
|
307
315
|
},
|
|
316
|
+
/**
|
|
317
|
+
* Finds and updates a single document with role-based validation and access control.
|
|
318
|
+
*
|
|
319
|
+
* @param {Filter<Document>} query - The MongoDB query used to match the document to update.
|
|
320
|
+
* @param {UpdateFilter<Document> | Partial<Document>} data - The update operations or replacement document.
|
|
321
|
+
* @param {FindOneAndUpdateOptions} [options] - Optional settings for the findOneAndUpdate operation.
|
|
322
|
+
* @returns {Promise<FindAndModifyResult<Document>>} The result of the findOneAndUpdate operation.
|
|
323
|
+
*
|
|
324
|
+
* @throws {Error} If the user is not authorized to update the document.
|
|
325
|
+
*/
|
|
326
|
+
findOneAndUpdate: async (
|
|
327
|
+
query: MongoFilter<Document>,
|
|
328
|
+
data: UpdateFilter<Document> | Document[],
|
|
329
|
+
options?: FindOneAndUpdateOptions
|
|
330
|
+
) => {
|
|
331
|
+
if (!run_as_system) {
|
|
332
|
+
checkDenyOperation(normalizedRules, collection.collectionName, CRUD_OPERATIONS.UPDATE)
|
|
333
|
+
const formattedQuery = getFormattedQuery(filters, query, user)
|
|
334
|
+
const safeQuery = Array.isArray(formattedQuery)
|
|
335
|
+
? normalizeQuery(formattedQuery)
|
|
336
|
+
: formattedQuery
|
|
337
|
+
|
|
338
|
+
const result = await collection.findOne({ $and: safeQuery })
|
|
339
|
+
|
|
340
|
+
if (!result) {
|
|
341
|
+
throw new Error('Update not permitted')
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const winningRole = getWinningRole(result, user, roles)
|
|
345
|
+
const hasOperators = Object.keys(data).some((key) => key.startsWith('$'))
|
|
346
|
+
const pipeline = [
|
|
347
|
+
{
|
|
348
|
+
$match: { $and: safeQuery }
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
$limit: 1
|
|
352
|
+
},
|
|
353
|
+
...Object.entries(data).map(([key, value]) => ({ [key]: value }))
|
|
354
|
+
]
|
|
355
|
+
const [docToCheck] = hasOperators
|
|
356
|
+
? await collection.aggregate(pipeline).toArray()
|
|
357
|
+
: ([data] as [Document])
|
|
358
|
+
|
|
359
|
+
const { status, document } = winningRole
|
|
360
|
+
? await checkValidation(
|
|
361
|
+
winningRole,
|
|
362
|
+
{
|
|
363
|
+
type: 'write',
|
|
364
|
+
roles,
|
|
365
|
+
cursor: docToCheck,
|
|
366
|
+
expansions: {}
|
|
367
|
+
},
|
|
368
|
+
user
|
|
369
|
+
)
|
|
370
|
+
: fallbackAccess(docToCheck)
|
|
371
|
+
|
|
372
|
+
const areDocumentsEqual = isEqual(document, docToCheck)
|
|
373
|
+
if (!status || !areDocumentsEqual) {
|
|
374
|
+
throw new Error('Update not permitted')
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const updateResult = options
|
|
378
|
+
? await collection.findOneAndUpdate({ $and: safeQuery }, data, options)
|
|
379
|
+
: await collection.findOneAndUpdate({ $and: safeQuery }, data)
|
|
380
|
+
if (!updateResult) {
|
|
381
|
+
return updateResult
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const readRole = getWinningRole(updateResult, user, roles)
|
|
385
|
+
const readResult = readRole
|
|
386
|
+
? await checkValidation(
|
|
387
|
+
readRole,
|
|
388
|
+
{
|
|
389
|
+
type: 'read',
|
|
390
|
+
roles,
|
|
391
|
+
cursor: updateResult,
|
|
392
|
+
expansions: {}
|
|
393
|
+
},
|
|
394
|
+
user
|
|
395
|
+
)
|
|
396
|
+
: fallbackAccess(updateResult)
|
|
397
|
+
|
|
398
|
+
const sanitizedDoc = readResult.status ? (readResult.document ?? updateResult) : {}
|
|
399
|
+
return sanitizedDoc
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return options
|
|
403
|
+
? collection.findOneAndUpdate(query, data, options)
|
|
404
|
+
: collection.findOneAndUpdate(query, data)
|
|
405
|
+
},
|
|
308
406
|
/**
|
|
309
407
|
* Finds documents in a MongoDB collection with optional role-based access control and post-query validation.
|
|
310
408
|
*
|
|
@@ -481,7 +579,7 @@ const getOperators: GetOperatorsFunction = (
|
|
|
481
579
|
return collection.watch(pipeline, options)
|
|
482
580
|
},
|
|
483
581
|
//TODO -> add filter & rules in aggregate
|
|
484
|
-
aggregate:
|
|
582
|
+
aggregate: (pipeline = [], options, isClient) => {
|
|
485
583
|
if (run_as_system || !isClient) {
|
|
486
584
|
return collection.aggregate(pipeline, options)
|
|
487
585
|
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { FastifyInstance } from 'fastify'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Collection,
|
|
4
|
+
Document,
|
|
5
|
+
Filter as MongoFilter,
|
|
6
|
+
FindCursor,
|
|
7
|
+
FindOneAndUpdateOptions,
|
|
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
|
+
}
|
|
@@ -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 () => {
|