@volcanicminds/backend 2.2.21 → 2.3.2
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 +19 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -169
- package/dist/index.js.map +1 -1
- package/dist/lib/api/auth/controller/auth.d.ts +0 -9
- package/dist/lib/api/auth/controller/auth.d.ts.map +1 -1
- package/dist/lib/api/auth/controller/auth.js +32 -75
- package/dist/lib/api/auth/controller/auth.js.map +1 -1
- package/dist/lib/api/auth/routes.d.ts +0 -45
- package/dist/lib/api/auth/routes.d.ts.map +1 -1
- package/dist/lib/api/auth/routes.js +1 -32
- package/dist/lib/api/auth/routes.js.map +1 -1
- package/dist/lib/api/tool/controller/tool.d.ts.map +1 -1
- package/dist/lib/api/tool/controller/tool.js +4 -0
- package/dist/lib/api/tool/controller/tool.js.map +1 -1
- package/dist/lib/api/users/controller/user.d.ts +10 -1
- package/dist/lib/api/users/controller/user.d.ts.map +1 -1
- package/dist/lib/api/users/controller/user.js +56 -2
- package/dist/lib/api/users/controller/user.js.map +1 -1
- package/dist/lib/api/users/routes.d.ts +67 -0
- package/dist/lib/api/users/routes.d.ts.map +1 -1
- package/dist/lib/api/users/routes.js +55 -2
- package/dist/lib/api/users/routes.js.map +1 -1
- package/dist/lib/config/general.d.ts +7 -0
- package/dist/lib/config/general.d.ts.map +1 -1
- package/dist/lib/config/general.js +8 -1
- package/dist/lib/config/general.js.map +1 -1
- package/dist/lib/defaults/managers.d.ts +8 -0
- package/dist/lib/defaults/managers.d.ts.map +1 -0
- package/dist/lib/defaults/managers.js +71 -0
- package/dist/lib/defaults/managers.js.map +1 -0
- package/dist/lib/hooks/onRequest.d.ts.map +1 -1
- package/dist/lib/hooks/onRequest.js +54 -2
- package/dist/lib/hooks/onRequest.js.map +1 -1
- package/dist/lib/hooks/onResponse.d.ts.map +1 -1
- package/dist/lib/hooks/onResponse.js +5 -0
- package/dist/lib/hooks/onResponse.js.map +1 -1
- package/dist/lib/loader/general.d.ts.map +1 -1
- package/dist/lib/loader/general.js +6 -1
- package/dist/lib/loader/general.js.map +1 -1
- package/dist/lib/loader/tenant.d.ts +3 -0
- package/dist/lib/loader/tenant.d.ts.map +1 -0
- package/dist/lib/loader/tenant.js +67 -0
- package/dist/lib/loader/tenant.js.map +1 -0
- package/lib/api/auth/controller/auth.ts +35 -81
- package/lib/api/auth/routes.ts +4 -33
- package/lib/api/tool/controller/tool.ts +5 -0
- package/lib/api/users/controller/user.ts +69 -2
- package/lib/api/users/routes.ts +58 -2
- package/lib/config/general.ts +8 -1
- package/lib/defaults/managers.ts +88 -0
- package/lib/hooks/onRequest.ts +72 -7
- package/lib/hooks/onResponse.ts +6 -0
- package/lib/loader/general.ts +6 -1
- package/lib/loader/tenant.ts +86 -0
- package/package.json +1 -1
|
@@ -23,13 +23,13 @@ export async function register(req: FastifyRequest, reply: FastifyReply) {
|
|
|
23
23
|
return reply.status(400).send(new Error('Repeated password not match'))
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
let existings = await req.server['userManager'].retrieveUserByEmail(data.email)
|
|
26
|
+
let existings = await req.server['userManager'].retrieveUserByEmail(data.email, req.runner)
|
|
27
27
|
if (existings) {
|
|
28
28
|
return reply.status(400).send(new Error('Email already registered'))
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
if ((data.requiredRoles || []).includes('admin')) {
|
|
32
|
-
existings = await req.server['userManager'].findQuery({ 'roles:in': 'admin' })
|
|
32
|
+
existings = await req.server['userManager'].findQuery({ 'roles:in': 'admin' }, req.runner)
|
|
33
33
|
if (existings?.records?.length) {
|
|
34
34
|
return reply.status(400).send(new Error('User admin already registered'))
|
|
35
35
|
}
|
|
@@ -42,7 +42,7 @@ export async function register(req: FastifyRequest, reply: FastifyReply) {
|
|
|
42
42
|
data.roles.push(publicRole)
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const user = await req.server['userManager'].createUser({ ...data, password: password })
|
|
45
|
+
const user = await req.server['userManager'].createUser({ ...data, password: password }, req.runner)
|
|
46
46
|
if (!user) {
|
|
47
47
|
return reply.status(400).send(new Error('User not registered'))
|
|
48
48
|
}
|
|
@@ -53,7 +53,7 @@ export async function register(req: FastifyRequest, reply: FastifyReply) {
|
|
|
53
53
|
export async function unregister(req: FastifyRequest, reply: FastifyReply) {
|
|
54
54
|
const { email, password } = req.data()
|
|
55
55
|
|
|
56
|
-
let user = await req.server['userManager'].retrieveUserByPassword(email, password)
|
|
56
|
+
let user = await req.server['userManager'].retrieveUserByPassword(email, password, req.runner)
|
|
57
57
|
let isValid = await req.server['userManager'].isValidUser(user)
|
|
58
58
|
|
|
59
59
|
if (!isValid) {
|
|
@@ -64,7 +64,7 @@ export async function unregister(req: FastifyRequest, reply: FastifyReply) {
|
|
|
64
64
|
return reply.status(403).send(new Error('User blocked'))
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
user = await req.server['userManager'].disableUserById(user.getId())
|
|
67
|
+
user = await req.server['userManager'].disableUserById(user.getId(), req.runner)
|
|
68
68
|
isValid = await req.server['userManager'].isValidUser(user)
|
|
69
69
|
|
|
70
70
|
if (!isValid) {
|
|
@@ -113,7 +113,7 @@ export async function changePassword(req: FastifyRequest, reply: FastifyReply) {
|
|
|
113
113
|
return reply.status(400).send(new Error('Repeated new password not match'))
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
let user = await req.server['userManager'].retrieveUserByPassword(email, oldPassword)
|
|
116
|
+
let user = await req.server['userManager'].retrieveUserByPassword(email, oldPassword, req.runner)
|
|
117
117
|
let isValid = await req.server['userManager'].isValidUser(user)
|
|
118
118
|
|
|
119
119
|
if (!isValid) {
|
|
@@ -124,7 +124,7 @@ export async function changePassword(req: FastifyRequest, reply: FastifyReply) {
|
|
|
124
124
|
return reply.status(403).send(new Error('User blocked'))
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
user = await req.server['userManager'].changePassword(email, newPassword1, oldPassword)
|
|
127
|
+
user = await req.server['userManager'].changePassword(email, newPassword1, oldPassword, req.runner)
|
|
128
128
|
isValid = await req.server['userManager'].isValidUser(user)
|
|
129
129
|
return { ok: isValid }
|
|
130
130
|
}
|
|
@@ -142,9 +142,9 @@ export async function forgotPassword(req: FastifyRequest, reply: FastifyReply) {
|
|
|
142
142
|
|
|
143
143
|
let user = null as any
|
|
144
144
|
if (email) {
|
|
145
|
-
user = await req.server['userManager'].retrieveUserByEmail(email)
|
|
145
|
+
user = await req.server['userManager'].retrieveUserByEmail(email, req.runner)
|
|
146
146
|
} else if (username) {
|
|
147
|
-
user = await req.server['userManager'].retrieveUserByUsername(username)
|
|
147
|
+
user = await req.server['userManager'].retrieveUserByUsername(username, req.runner)
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
let isValid = await req.server['userManager'].isValidUser(user)
|
|
@@ -157,7 +157,7 @@ export async function forgotPassword(req: FastifyRequest, reply: FastifyReply) {
|
|
|
157
157
|
return reply.status(403).send(new Error('User blocked'))
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
user = await req.server['userManager'].forgotPassword(user.email)
|
|
160
|
+
user = await req.server['userManager'].forgotPassword(user.email, req.runner)
|
|
161
161
|
isValid = await req.server['userManager'].isValidUser(user)
|
|
162
162
|
|
|
163
163
|
return { ok: isValid }
|
|
@@ -170,7 +170,7 @@ export async function confirmEmail(req: FastifyRequest, reply: FastifyReply) {
|
|
|
170
170
|
return reply.status(400).send(new Error('Missing the confirm email token'))
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
let user = await req.server['userManager'].retrieveUserByConfirmationToken(code)
|
|
173
|
+
let user = await req.server['userManager'].retrieveUserByConfirmationToken(code, req.runner)
|
|
174
174
|
let isValid = await req.server['userManager'].isValidUser(user)
|
|
175
175
|
|
|
176
176
|
if (!isValid) {
|
|
@@ -181,7 +181,7 @@ export async function confirmEmail(req: FastifyRequest, reply: FastifyReply) {
|
|
|
181
181
|
return reply.status(403).send(new Error('User blocked'))
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
user = await req.server['userManager'].userConfirmation(user)
|
|
184
|
+
user = await req.server['userManager'].userConfirmation(user, req.runner)
|
|
185
185
|
isValid = await req.server['userManager'].isValidUser(user)
|
|
186
186
|
|
|
187
187
|
return { ok: isValid }
|
|
@@ -202,7 +202,7 @@ export async function resetPassword(req: FastifyRequest, reply: FastifyReply) {
|
|
|
202
202
|
return reply.status(400).send(new Error('Repeated new password not match'))
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
let user = await req.server['userManager'].retrieveUserByResetPasswordToken(code)
|
|
205
|
+
let user = await req.server['userManager'].retrieveUserByResetPasswordToken(code, req.runner)
|
|
206
206
|
let isValid = await req.server['userManager'].isValidUser(user)
|
|
207
207
|
|
|
208
208
|
if (!isValid) {
|
|
@@ -213,7 +213,7 @@ export async function resetPassword(req: FastifyRequest, reply: FastifyReply) {
|
|
|
213
213
|
return reply.status(403).send(new Error('User blocked'))
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
user = await req.server['userManager'].resetPassword(user, newPassword1)
|
|
216
|
+
user = await req.server['userManager'].resetPassword(user, newPassword1, req.runner)
|
|
217
217
|
isValid = await req.server['userManager'].isValidUser(user)
|
|
218
218
|
return { ok: isValid, user }
|
|
219
219
|
}
|
|
@@ -233,7 +233,7 @@ export async function login(req: FastifyRequest, reply: FastifyReply) {
|
|
|
233
233
|
return reply.status(400).send(new Error('Password not valid'))
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
let user = await req.server['userManager'].retrieveUserByPassword(email, password)
|
|
236
|
+
let user = await req.server['userManager'].retrieveUserByPassword(email, password, req.runner)
|
|
237
237
|
if (!user) {
|
|
238
238
|
return reply.status(403).send(new Error('Wrong credentials'))
|
|
239
239
|
}
|
|
@@ -262,7 +262,10 @@ export async function login(req: FastifyRequest, reply: FastifyReply) {
|
|
|
262
262
|
const isMandatory = mfa_policy === MfaPolicy.MANDATORY
|
|
263
263
|
|
|
264
264
|
if (isMfaEnabled || isMandatory) {
|
|
265
|
-
const tempToken = await reply.jwtSign(
|
|
265
|
+
const tempToken = await reply.jwtSign(
|
|
266
|
+
{ sub: user.externalId, role: 'pre-auth-mfa', tid: req.tenant?.id },
|
|
267
|
+
{ expiresIn: '5m' }
|
|
268
|
+
)
|
|
266
269
|
// Use 202 Accepted to bypass 200 OK strict schema filtering
|
|
267
270
|
return reply.status(202).send({
|
|
268
271
|
mfaRequired: isMfaEnabled, // If enabled, verify. If not enabled but mandatory, setup.
|
|
@@ -272,13 +275,13 @@ export async function login(req: FastifyRequest, reply: FastifyReply) {
|
|
|
272
275
|
}
|
|
273
276
|
|
|
274
277
|
if (config.options.reset_external_id_on_login) {
|
|
275
|
-
user = await req.server['userManager'].resetExternalId(user.getId())
|
|
278
|
+
user = await req.server['userManager'].resetExternalId(user.getId(), req.runner)
|
|
276
279
|
}
|
|
277
280
|
|
|
278
281
|
// https://www.iana.org/assignments/jwt/jwt.xhtml
|
|
279
|
-
const token = await reply.jwtSign({ sub: user.externalId })
|
|
282
|
+
const token = await reply.jwtSign({ sub: user.externalId, tid: req.tenant?.id })
|
|
280
283
|
const refreshToken = reply.server.jwt['refreshToken']
|
|
281
|
-
? await reply.server.jwt['refreshToken'].sign({ sub: user.externalId })
|
|
284
|
+
? await reply.server.jwt['refreshToken'].sign({ sub: user.externalId, tid: req.tenant?.id })
|
|
282
285
|
: undefined
|
|
283
286
|
|
|
284
287
|
const AUTH_MODE = process.env.AUTH_MODE || 'BEARER'
|
|
@@ -342,14 +345,14 @@ export async function refreshToken(req: FastifyRequest, reply: FastifyReply) {
|
|
|
342
345
|
return reply.status(403).send(new Error('Mismatched tokens'))
|
|
343
346
|
}
|
|
344
347
|
|
|
345
|
-
const user = await req.server['userManager'].retrieveUserByExternalId(tokenData.sub)
|
|
348
|
+
const user = await req.server['userManager'].retrieveUserByExternalId(tokenData.sub, req.runner)
|
|
346
349
|
const isValid = await req.server['userManager'].isValidUser(user)
|
|
347
350
|
|
|
348
351
|
if (!isValid) {
|
|
349
352
|
return reply.status(403).send(new Error('Wrong refresh token'))
|
|
350
353
|
}
|
|
351
354
|
|
|
352
|
-
const newToken = await reply.jwtSign({ sub: user.externalId })
|
|
355
|
+
const newToken = await reply.jwtSign({ sub: user.externalId, tid: req.tenant?.id })
|
|
353
356
|
return {
|
|
354
357
|
token: newToken
|
|
355
358
|
}
|
|
@@ -361,43 +364,12 @@ export async function invalidateTokens(req: FastifyRequest, reply: FastifyReply)
|
|
|
361
364
|
return reply.status(403).send(new Error('User not linked'))
|
|
362
365
|
}
|
|
363
366
|
|
|
364
|
-
const user = await req.server['userManager'].resetExternalId(req.user.getId())
|
|
367
|
+
const user = await req.server['userManager'].resetExternalId(req.user.getId(), req.runner)
|
|
365
368
|
isValid = await req.server['userManager'].isValidUser(user)
|
|
366
369
|
return { ok: isValid }
|
|
367
370
|
}
|
|
368
371
|
|
|
369
|
-
export async function block(req: FastifyRequest, reply: FastifyReply) {
|
|
370
|
-
if (!req.server['userManager'].isImplemented()) {
|
|
371
|
-
throw new Error('Not implemented')
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
if (!req.hasRole(roles.admin) && !req.hasRole(roles.backoffice)) {
|
|
375
|
-
return reply.status(403).send({ statusCode: 403, code: 'ROLE_NOT_ALLOWED', message: 'Not allowed to block a user' })
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const { id: userId } = req.parameters()
|
|
379
|
-
const { reason } = req.data()
|
|
380
|
-
|
|
381
|
-
let user = await req.server['userManager'].blockUserById(userId, reason)
|
|
382
|
-
user = await req.server['userManager'].resetExternalId(user.getId())
|
|
383
|
-
return { ok: !!user.getId() }
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
export async function unblock(req: FastifyRequest, reply: FastifyReply) {
|
|
387
|
-
if (!req.server['userManager'].isImplemented()) {
|
|
388
|
-
throw new Error('Not implemented')
|
|
389
|
-
}
|
|
390
372
|
|
|
391
|
-
if (!req.hasRole(roles.admin) && !req.hasRole(roles.backoffice)) {
|
|
392
|
-
return reply
|
|
393
|
-
.status(403)
|
|
394
|
-
.send({ statusCode: 403, code: 'ROLE_NOT_ALLOWED', message: 'Not allowed to unblock a user' })
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const { id: userId } = req.parameters()
|
|
398
|
-
const user = await req.server['userManager'].unblockUserById(userId)
|
|
399
|
-
return { ok: !!user.getId() }
|
|
400
|
-
}
|
|
401
373
|
|
|
402
374
|
export async function mfaSetup(req: FastifyRequest, reply: FastifyReply) {
|
|
403
375
|
const user = req.user
|
|
@@ -429,16 +401,16 @@ export async function mfaEnable(req: FastifyRequest, reply: FastifyReply) {
|
|
|
429
401
|
}
|
|
430
402
|
|
|
431
403
|
// 2. Save using userManager (typeorm)
|
|
432
|
-
await req.server['userManager'].saveMfaSecret(user.getId(), secret)
|
|
433
|
-
await req.server['userManager'].enableMfa(user.getId())
|
|
404
|
+
await req.server['userManager'].saveMfaSecret(user.getId(), secret, req.runner)
|
|
405
|
+
await req.server['userManager'].enableMfa(user.getId(), req.runner)
|
|
434
406
|
|
|
435
407
|
// IMPORTANT: Return full tokens upon enablement if user was in pending state
|
|
436
408
|
// BUT usually user is already logged in via temp token or full token.
|
|
437
409
|
// If user is setting up from "Forced Setup", they need tokens now.
|
|
438
410
|
|
|
439
|
-
const finalToken = await reply.jwtSign({ sub: user.externalId })
|
|
411
|
+
const finalToken = await reply.jwtSign({ sub: user.externalId, tid: req.tenant?.id })
|
|
440
412
|
const refreshToken = reply.server.jwt['refreshToken']
|
|
441
|
-
? await reply.server.jwt['refreshToken'].sign({ sub: user.externalId })
|
|
413
|
+
? await reply.server.jwt['refreshToken'].sign({ sub: user.externalId, tid: req.tenant?.id })
|
|
442
414
|
: undefined
|
|
443
415
|
|
|
444
416
|
return {
|
|
@@ -480,10 +452,10 @@ export async function mfaVerify(req: FastifyRequest, reply: FastifyReply) {
|
|
|
480
452
|
if (!token) return reply.status(400).send(new Error('Missing token'))
|
|
481
453
|
|
|
482
454
|
// 1. Retrieve secret via userManager
|
|
483
|
-
const user = await req.server['userManager'].retrieveUserByExternalId(subjectId)
|
|
455
|
+
const user = await req.server['userManager'].retrieveUserByExternalId(subjectId, req.runner)
|
|
484
456
|
if (!user) return reply.status(404).send(new Error('User not found'))
|
|
485
457
|
|
|
486
|
-
const secret = await req.server['userManager'].retrieveMfaSecret(user.getId())
|
|
458
|
+
const secret = await req.server['userManager'].retrieveMfaSecret(user.getId(), req.runner)
|
|
487
459
|
if (!secret) return reply.status(403).send(new Error('MFA not configured for user'))
|
|
488
460
|
|
|
489
461
|
// 2. Verify via mfaManager
|
|
@@ -491,12 +463,12 @@ export async function mfaVerify(req: FastifyRequest, reply: FastifyReply) {
|
|
|
491
463
|
if (!isValid) return reply.status(403).send(new Error('Invalid MFA token'))
|
|
492
464
|
|
|
493
465
|
if (config.options.reset_external_id_on_login) {
|
|
494
|
-
await req.server['userManager'].resetExternalId(user.getId())
|
|
466
|
+
await req.server['userManager'].resetExternalId(user.getId(), req.runner)
|
|
495
467
|
}
|
|
496
468
|
|
|
497
|
-
const finalToken = await reply.jwtSign({ sub: user.externalId })
|
|
469
|
+
const finalToken = await reply.jwtSign({ sub: user.externalId, tid: req.tenant?.id })
|
|
498
470
|
const refreshToken = reply.server.jwt['refreshToken']
|
|
499
|
-
? await reply.server.jwt['refreshToken'].sign({ sub: user.externalId })
|
|
471
|
+
? await reply.server.jwt['refreshToken'].sign({ sub: user.externalId, tid: req.tenant?.id })
|
|
500
472
|
: undefined
|
|
501
473
|
|
|
502
474
|
return {
|
|
@@ -520,7 +492,7 @@ export async function mfaDisable(req: FastifyRequest, reply: FastifyReply) {
|
|
|
520
492
|
}
|
|
521
493
|
|
|
522
494
|
try {
|
|
523
|
-
await req.server['userManager'].disableMfa(user.getId())
|
|
495
|
+
await req.server['userManager'].disableMfa(user.getId(), req.runner)
|
|
524
496
|
return { ok: true }
|
|
525
497
|
} catch (error: any) {
|
|
526
498
|
req.log.error({ err: error }, 'MFA Disable failed')
|
|
@@ -528,22 +500,4 @@ export async function mfaDisable(req: FastifyRequest, reply: FastifyReply) {
|
|
|
528
500
|
}
|
|
529
501
|
}
|
|
530
502
|
|
|
531
|
-
export async function resetMfa(req: FastifyRequest, reply: FastifyReply) {
|
|
532
|
-
const { id } = req.parameters()
|
|
533
|
-
|
|
534
|
-
if (!req.hasRole(roles.admin)) {
|
|
535
|
-
return reply.status(403).send(new Error('Only admins can reset MFA'))
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
if (!id) {
|
|
539
|
-
return reply.status(400).send(new Error('Missing user id'))
|
|
540
|
-
}
|
|
541
503
|
|
|
542
|
-
try {
|
|
543
|
-
await req.server['userManager'].disableMfa(id)
|
|
544
|
-
return { ok: true }
|
|
545
|
-
} catch (error: any) {
|
|
546
|
-
req.log.error({ err: error }, 'MFA Reset failed')
|
|
547
|
-
return reply.status(500).send(new Error('Failed to reset MFA'))
|
|
548
|
-
}
|
|
549
|
-
}
|
package/lib/api/auth/routes.ts
CHANGED
|
@@ -174,37 +174,7 @@ export default {
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
},
|
|
177
|
-
|
|
178
|
-
method: 'POST',
|
|
179
|
-
path: '/block/:id',
|
|
180
|
-
roles: [roles.admin, roles.backoffice],
|
|
181
|
-
handler: 'auth.block',
|
|
182
|
-
middlewares: ['global.isAuthenticated'],
|
|
183
|
-
config: {
|
|
184
|
-
title: 'Block a user by id',
|
|
185
|
-
description: 'Block a user by id',
|
|
186
|
-
params: { $ref: 'onlyIdSchema#' },
|
|
187
|
-
body: { $ref: 'blockBodySchema#' },
|
|
188
|
-
response: {
|
|
189
|
-
200: { $ref: 'defaultResponse#' }
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
method: 'POST',
|
|
195
|
-
path: '/unblock/:id',
|
|
196
|
-
roles: [roles.admin, roles.backoffice],
|
|
197
|
-
handler: 'auth.unblock',
|
|
198
|
-
middlewares: ['global.isAuthenticated'],
|
|
199
|
-
config: {
|
|
200
|
-
title: 'Unblock a user by id',
|
|
201
|
-
description: 'Unblock a user by id',
|
|
202
|
-
params: { $ref: 'onlyIdSchema#' },
|
|
203
|
-
response: {
|
|
204
|
-
200: { $ref: 'defaultResponse#' }
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
},
|
|
177
|
+
|
|
208
178
|
{
|
|
209
179
|
method: 'POST',
|
|
210
180
|
path: '/mfa/setup',
|
|
@@ -237,7 +207,7 @@ export default {
|
|
|
237
207
|
{
|
|
238
208
|
method: 'POST',
|
|
239
209
|
path: '/mfa/verify',
|
|
240
|
-
roles: [],
|
|
210
|
+
roles: [],
|
|
241
211
|
handler: 'auth.mfaVerify',
|
|
242
212
|
middlewares: [],
|
|
243
213
|
config: {
|
|
@@ -263,6 +233,7 @@ export default {
|
|
|
263
233
|
200: { $ref: 'defaultResponse#' }
|
|
264
234
|
}
|
|
265
235
|
}
|
|
266
|
-
}
|
|
236
|
+
},
|
|
237
|
+
|
|
267
238
|
]
|
|
268
239
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { FastifyReply, FastifyRequest } from 'fastify'
|
|
2
2
|
|
|
3
3
|
export async function synchronizeSchemas(req: FastifyRequest, reply: FastifyReply) {
|
|
4
|
+
const { multi_tenant } = global.config?.options || {}
|
|
5
|
+
if (multi_tenant?.enabled) {
|
|
6
|
+
throw new Error('Schema synchronization is not supported in multi-tenant mode.')
|
|
7
|
+
}
|
|
8
|
+
|
|
4
9
|
if (!req.server['dataBaseManager'].isImplemented()) {
|
|
5
10
|
throw new Error('Not implemented')
|
|
6
11
|
}
|
|
@@ -90,7 +90,40 @@ export async function isAdmin(req: FastifyRequest, reply: FastifyReply) {
|
|
|
90
90
|
return reply.send({ isAdmin: user?.getId() && req.hasRole(roles.admin) })
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
export async function
|
|
93
|
+
export async function block(req: FastifyRequest, reply: FastifyReply) {
|
|
94
|
+
if (!req.server['userManager'].isImplemented()) {
|
|
95
|
+
throw new Error('Not implemented')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!req.hasRole(roles.admin) && !req.hasRole(roles.backoffice)) {
|
|
99
|
+
return reply.status(403).send({ statusCode: 403, code: 'ROLE_NOT_ALLOWED', message: 'Not allowed to block a user' })
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { id: userId } = req.parameters()
|
|
103
|
+
const { reason } = req.data()
|
|
104
|
+
|
|
105
|
+
let user = await req.server['userManager'].blockUserById(userId, reason, req.runner)
|
|
106
|
+
user = await req.server['userManager'].resetExternalId(user.getId(), req.runner)
|
|
107
|
+
return { ok: !!user.getId() }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function unblock(req: FastifyRequest, reply: FastifyReply) {
|
|
111
|
+
if (!req.server['userManager'].isImplemented()) {
|
|
112
|
+
throw new Error('Not implemented')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!req.hasRole(roles.admin) && !req.hasRole(roles.backoffice)) {
|
|
116
|
+
return reply
|
|
117
|
+
.status(403)
|
|
118
|
+
.send({ statusCode: 403, code: 'ROLE_NOT_ALLOWED', message: 'Not allowed to unblock a user' })
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const { id: userId } = req.parameters()
|
|
122
|
+
const user = await req.server['userManager'].unblockUserById(userId, req.runner)
|
|
123
|
+
return { ok: !!user.getId() }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function resetMfaByAdmin(req: FastifyRequest, reply: FastifyReply) {
|
|
94
127
|
const { id } = req.parameters()
|
|
95
128
|
|
|
96
129
|
if (!req.hasRole(roles.admin)) {
|
|
@@ -102,10 +135,44 @@ export async function resetMfa(req: FastifyRequest, reply: FastifyReply) {
|
|
|
102
135
|
}
|
|
103
136
|
|
|
104
137
|
try {
|
|
105
|
-
await req.server['userManager'].disableMfa(id)
|
|
138
|
+
await req.server['userManager'].disableMfa(id, req.runner)
|
|
106
139
|
return { ok: true }
|
|
107
140
|
} catch (error) {
|
|
108
141
|
req.log.error(error)
|
|
109
142
|
return reply.status(500).send(new Error('Failed to reset MFA'))
|
|
110
143
|
}
|
|
111
144
|
}
|
|
145
|
+
|
|
146
|
+
export async function resetPasswordByAdmin(req: FastifyRequest, reply: FastifyReply) {
|
|
147
|
+
// Check if feature is enabled via config
|
|
148
|
+
if (config.options?.allow_admin_change_password_users !== true) {
|
|
149
|
+
return reply.status(404).send()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!req.hasRole(roles.admin)) {
|
|
153
|
+
return reply.status(403).send(new Error('Only admins can reset user passwords'))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const { id } = req.parameters()
|
|
157
|
+
if (!id) {
|
|
158
|
+
return reply.status(400).send(new Error('Missing user id'))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const { password } = req.data()
|
|
162
|
+
if (!password) {
|
|
163
|
+
return reply.status(400).send(new Error('Missing password in request body'))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const user = await req.server['userManager'].retrieveUserById(id)
|
|
168
|
+
if (!user) {
|
|
169
|
+
return reply.status(404).send(new Error('User not found'))
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
await req.server['userManager'].resetPassword(user, password)
|
|
173
|
+
return { ok: true }
|
|
174
|
+
} catch (error) {
|
|
175
|
+
req.log.error(error)
|
|
176
|
+
return reply.status(500).send(new Error('Failed to reset password'))
|
|
177
|
+
}
|
|
178
|
+
}
|
package/lib/api/users/routes.ts
CHANGED
|
@@ -180,20 +180,76 @@ export default {
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
},
|
|
183
|
+
{
|
|
184
|
+
method: 'POST',
|
|
185
|
+
path: '/:id/block',
|
|
186
|
+
roles: [roles.admin, roles.backoffice],
|
|
187
|
+
handler: 'user.block',
|
|
188
|
+
middlewares: ['global.isAuthenticated'],
|
|
189
|
+
config: {
|
|
190
|
+
title: 'Block a user by id',
|
|
191
|
+
description: 'Block a user by id',
|
|
192
|
+
params: { $ref: 'onlyIdSchema#' },
|
|
193
|
+
body: { $ref: 'blockBodySchema#' },
|
|
194
|
+
response: {
|
|
195
|
+
200: { $ref: 'defaultResponse#' }
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
method: 'POST',
|
|
201
|
+
path: '/:id/unblock',
|
|
202
|
+
roles: [roles.admin, roles.backoffice],
|
|
203
|
+
handler: 'user.unblock',
|
|
204
|
+
middlewares: ['global.isAuthenticated'],
|
|
205
|
+
config: {
|
|
206
|
+
title: 'Unblock a user by id',
|
|
207
|
+
description: 'Unblock a user by id',
|
|
208
|
+
params: { $ref: 'onlyIdSchema#' },
|
|
209
|
+
response: {
|
|
210
|
+
200: { $ref: 'defaultResponse#' }
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
},
|
|
183
214
|
{
|
|
184
215
|
method: 'POST',
|
|
185
216
|
path: '/:id/mfa/reset',
|
|
186
217
|
roles: [roles.admin],
|
|
187
|
-
handler: 'user.
|
|
218
|
+
handler: 'user.resetMfaByAdmin',
|
|
188
219
|
middlewares: ['global.isAuthenticated'],
|
|
189
220
|
config: {
|
|
190
|
-
title: 'Reset MFA for user',
|
|
221
|
+
title: 'Reset MFA for specific user',
|
|
191
222
|
description: 'Disable MFA for a specific user (Admin only)',
|
|
192
223
|
params: { $ref: 'globalParamsSchema#' },
|
|
193
224
|
response: {
|
|
194
225
|
200: { $ref: 'defaultResponse#' }
|
|
195
226
|
}
|
|
196
227
|
}
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
method: 'POST',
|
|
231
|
+
path: '/:id/password/reset',
|
|
232
|
+
roles: [roles.admin],
|
|
233
|
+
handler: 'user.resetPasswordByAdmin',
|
|
234
|
+
middlewares: ['global.isAuthenticated'],
|
|
235
|
+
config: {
|
|
236
|
+
title: 'Reset password for specific user',
|
|
237
|
+
description:
|
|
238
|
+
'Admin can reset password for a specific user. Requires config option allow_admin_change_password_users to be enabled.',
|
|
239
|
+
params: { $ref: 'globalParamsSchema#' },
|
|
240
|
+
body: {
|
|
241
|
+
type: 'object',
|
|
242
|
+
required: ['password'],
|
|
243
|
+
properties: {
|
|
244
|
+
password: { type: 'string', minLength: 6 }
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
response: {
|
|
248
|
+
200: { $ref: 'defaultResponse#' }
|
|
249
|
+
}
|
|
250
|
+
}
|
|
197
251
|
}
|
|
198
252
|
]
|
|
253
|
+
|
|
254
|
+
|
|
199
255
|
}
|
package/lib/config/general.ts
CHANGED
|
@@ -3,11 +3,18 @@ export default {
|
|
|
3
3
|
options: {
|
|
4
4
|
allow_multiple_admin: false,
|
|
5
5
|
admin_can_change_passwords: false,
|
|
6
|
+
allow_admin_change_password_users: false,
|
|
6
7
|
reset_external_id_on_login: false,
|
|
7
8
|
scheduler: false,
|
|
8
9
|
embedded_auth: true,
|
|
9
10
|
mfa_policy: process.env.MFA_POLICY || 'OPTIONAL', // OPTIONAL, MANDATORY, ONE_WAY
|
|
10
11
|
mfa_admin_forced_reset_email: null,
|
|
11
|
-
mfa_admin_forced_reset_until: null
|
|
12
|
+
mfa_admin_forced_reset_until: null,
|
|
13
|
+
multi_tenant: {
|
|
14
|
+
enabled: false,
|
|
15
|
+
resolver: 'subdomain', // subdomain, header, query
|
|
16
|
+
header_key: 'x-tenant-id',
|
|
17
|
+
query_key: 'tid'
|
|
18
|
+
}
|
|
12
19
|
}
|
|
13
20
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
|
|
2
|
+
import {
|
|
3
|
+
UserManagement,
|
|
4
|
+
TokenManagement,
|
|
5
|
+
DataBaseManagement,
|
|
6
|
+
MfaManagement,
|
|
7
|
+
TransferManagement,
|
|
8
|
+
TenantManagement,
|
|
9
|
+
TransferCallback
|
|
10
|
+
} from '../../types/global.js'
|
|
11
|
+
import { FastifyReply, FastifyRequest } from 'fastify'
|
|
12
|
+
|
|
13
|
+
// Default implementations that throw errors or return not implemented
|
|
14
|
+
export const defaultTenantManager: TenantManagement = {
|
|
15
|
+
isImplemented() { return false },
|
|
16
|
+
resolveTenant(_req) { throw new Error('Not implemented') },
|
|
17
|
+
switchContext(_tenant, _db?) { throw new Error('Not implemented') },
|
|
18
|
+
createTenant(_data) { throw new Error('Not implemented') },
|
|
19
|
+
deleteTenant(_id) { throw new Error('Not implemented') }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const defaultUserManager: UserManagement = {
|
|
23
|
+
isImplemented() { return false },
|
|
24
|
+
isValidUser(_data: unknown) { throw new Error('Not implemented.') },
|
|
25
|
+
createUser(_data: unknown) { throw new Error('Not implemented.') },
|
|
26
|
+
deleteUser(_data: unknown) { throw new Error('Not implemented.') },
|
|
27
|
+
resetExternalId(_data: unknown) { throw new Error('Not implemented.') },
|
|
28
|
+
updateUserById(_id: string, _user: unknown) { throw new Error('Not implemented.') },
|
|
29
|
+
retrieveUserById(_id: string) { throw new Error('Not implemented.') },
|
|
30
|
+
retrieveUserByEmail(_email: string) { throw new Error('Not implemented.') },
|
|
31
|
+
retrieveUserByConfirmationToken(_code: string) { throw new Error('Not implemented.') },
|
|
32
|
+
retrieveUserByResetPasswordToken(_code: string) { throw new Error('Not implemented.') },
|
|
33
|
+
retrieveUserByUsername(_username: string) { throw new Error('Not implemented.') },
|
|
34
|
+
retrieveUserByExternalId(_externalId: string) { throw new Error('Not implemented.') },
|
|
35
|
+
retrieveUserByPassword(_email: string, _password: string) { throw new Error('Not implemented.') },
|
|
36
|
+
changePassword(_email: string, _password: string, _oldPassword: string) { throw new Error('Not implemented.') },
|
|
37
|
+
forgotPassword(_email: string) { throw new Error('Not implemented.') },
|
|
38
|
+
userConfirmation(_user: unknown) { throw new Error('Not implemented.') },
|
|
39
|
+
resetPassword(_user: unknown, _password: string) { throw new Error('Not implemented.') },
|
|
40
|
+
blockUserById(_id: string, _reason: string) { throw new Error('Not implemented.') },
|
|
41
|
+
unblockUserById(_data: unknown) { throw new Error('Not implemented.') },
|
|
42
|
+
countQuery(_data: unknown) { throw new Error('Not implemented.') },
|
|
43
|
+
findQuery(_data: unknown) { throw new Error('Not implemented.') },
|
|
44
|
+
disableUserById(_id: string) { throw new Error('Not implemented.') },
|
|
45
|
+
saveMfaSecret(_userId: string, _secret: string) { throw new Error('Not implemented.') },
|
|
46
|
+
retrieveMfaSecret(_userId: string) { throw new Error('Not implemented.') },
|
|
47
|
+
enableMfa(_userId: string) { throw new Error('Not implemented.') },
|
|
48
|
+
disableMfa(_userId: string) { throw new Error('Not implemented.') },
|
|
49
|
+
forceDisableMfaForAdmin(_email: string) { throw new Error('Not implemented.') }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const defaultTokenManager: TokenManagement = {
|
|
53
|
+
isImplemented() { return false },
|
|
54
|
+
isValidToken(_data: unknown) { throw new Error('Not implemented.') },
|
|
55
|
+
createToken(_data: unknown) { throw new Error('Not implemented.') },
|
|
56
|
+
resetExternalId(_id: string) { throw new Error('Not implemented.') },
|
|
57
|
+
updateTokenById(_id: string, _token: unknown) { throw new Error('Not implemented.') },
|
|
58
|
+
retrieveTokenById(_id: string) { throw new Error('Not implemented.') },
|
|
59
|
+
retrieveTokenByExternalId(_id: string) { throw new Error('Not implemented.') },
|
|
60
|
+
blockTokenById(_id: string, _reason: string) { throw new Error('Not implemented.') },
|
|
61
|
+
unblockTokenById(_id: string) { throw new Error('Not implemented.') },
|
|
62
|
+
countQuery(_data: unknown) { throw new Error('Not implemented.') },
|
|
63
|
+
findQuery(_data: unknown) { throw new Error('Not implemented.') },
|
|
64
|
+
removeTokenById(_id: string) { throw new Error('Not implemented.') }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const defaultDataBaseManager: DataBaseManagement = {
|
|
68
|
+
isImplemented() { return false },
|
|
69
|
+
synchronizeSchemas() { throw new Error('Not implemented.') },
|
|
70
|
+
retrieveBy(_entityName, _entityId) { throw new Error('Not implemented.') },
|
|
71
|
+
addChange(_entityName, _entityId, _status, _userId, _contents, _changeEntity) { throw new Error('Not implemented.') }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const defaultMfaManager: MfaManagement = {
|
|
75
|
+
generateSetup(_appName: string, _email: string) { throw new Error('Not implemented.') },
|
|
76
|
+
verify(_token: string, _secret: string) { throw new Error('Not implemented.') }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const defaultTransferManager: TransferManagement = {
|
|
80
|
+
isImplemented() { return false },
|
|
81
|
+
getPath() { throw new Error('Not implemented.') },
|
|
82
|
+
getServer() { throw new Error('Not implemented.') },
|
|
83
|
+
onUploadCreate(_callback: TransferCallback) { throw new Error('Not implemented.') },
|
|
84
|
+
onUploadFinish(_callback: TransferCallback) { throw new Error('Not implemented.') },
|
|
85
|
+
onUploadTerminate(_callback: TransferCallback) { throw new Error('Not implemented.') },
|
|
86
|
+
handle(_req: FastifyRequest, _res: FastifyReply) { throw new Error('Not implemented.') },
|
|
87
|
+
isValid(_req: FastifyRequest) { throw new Error('Not implemented.') }
|
|
88
|
+
}
|