@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.
Files changed (56) hide show
  1. package/README.md +19 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +9 -169
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/api/auth/controller/auth.d.ts +0 -9
  6. package/dist/lib/api/auth/controller/auth.d.ts.map +1 -1
  7. package/dist/lib/api/auth/controller/auth.js +32 -75
  8. package/dist/lib/api/auth/controller/auth.js.map +1 -1
  9. package/dist/lib/api/auth/routes.d.ts +0 -45
  10. package/dist/lib/api/auth/routes.d.ts.map +1 -1
  11. package/dist/lib/api/auth/routes.js +1 -32
  12. package/dist/lib/api/auth/routes.js.map +1 -1
  13. package/dist/lib/api/tool/controller/tool.d.ts.map +1 -1
  14. package/dist/lib/api/tool/controller/tool.js +4 -0
  15. package/dist/lib/api/tool/controller/tool.js.map +1 -1
  16. package/dist/lib/api/users/controller/user.d.ts +10 -1
  17. package/dist/lib/api/users/controller/user.d.ts.map +1 -1
  18. package/dist/lib/api/users/controller/user.js +56 -2
  19. package/dist/lib/api/users/controller/user.js.map +1 -1
  20. package/dist/lib/api/users/routes.d.ts +67 -0
  21. package/dist/lib/api/users/routes.d.ts.map +1 -1
  22. package/dist/lib/api/users/routes.js +55 -2
  23. package/dist/lib/api/users/routes.js.map +1 -1
  24. package/dist/lib/config/general.d.ts +7 -0
  25. package/dist/lib/config/general.d.ts.map +1 -1
  26. package/dist/lib/config/general.js +8 -1
  27. package/dist/lib/config/general.js.map +1 -1
  28. package/dist/lib/defaults/managers.d.ts +8 -0
  29. package/dist/lib/defaults/managers.d.ts.map +1 -0
  30. package/dist/lib/defaults/managers.js +71 -0
  31. package/dist/lib/defaults/managers.js.map +1 -0
  32. package/dist/lib/hooks/onRequest.d.ts.map +1 -1
  33. package/dist/lib/hooks/onRequest.js +54 -2
  34. package/dist/lib/hooks/onRequest.js.map +1 -1
  35. package/dist/lib/hooks/onResponse.d.ts.map +1 -1
  36. package/dist/lib/hooks/onResponse.js +5 -0
  37. package/dist/lib/hooks/onResponse.js.map +1 -1
  38. package/dist/lib/loader/general.d.ts.map +1 -1
  39. package/dist/lib/loader/general.js +6 -1
  40. package/dist/lib/loader/general.js.map +1 -1
  41. package/dist/lib/loader/tenant.d.ts +3 -0
  42. package/dist/lib/loader/tenant.d.ts.map +1 -0
  43. package/dist/lib/loader/tenant.js +67 -0
  44. package/dist/lib/loader/tenant.js.map +1 -0
  45. package/lib/api/auth/controller/auth.ts +35 -81
  46. package/lib/api/auth/routes.ts +4 -33
  47. package/lib/api/tool/controller/tool.ts +5 -0
  48. package/lib/api/users/controller/user.ts +69 -2
  49. package/lib/api/users/routes.ts +58 -2
  50. package/lib/config/general.ts +8 -1
  51. package/lib/defaults/managers.ts +88 -0
  52. package/lib/hooks/onRequest.ts +72 -7
  53. package/lib/hooks/onResponse.ts +6 -0
  54. package/lib/loader/general.ts +6 -1
  55. package/lib/loader/tenant.ts +86 -0
  56. 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({ sub: user.externalId, role: 'pre-auth-mfa' }, { expiresIn: '5m' })
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
- }
@@ -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: [], // Open, validation via temp token manually in controller
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 resetMfa(req: FastifyRequest, reply: FastifyReply) {
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
+ }
@@ -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.resetMfa',
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
  }
@@ -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
+ }