@drax/identity-back 0.9.0 → 0.11.3

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 (50) hide show
  1. package/dist/config/IdentityConfig.js +1 -0
  2. package/dist/controllers/UserController.js +152 -5
  3. package/dist/factory/UserRegistryServiceFactory.js +24 -0
  4. package/dist/html/RegistrationCompleteHtml.js +51 -0
  5. package/dist/models/UserModel.js +6 -1
  6. package/dist/repository/mongo/UserApiKeyMongoRepository.js +2 -2
  7. package/dist/repository/mongo/UserMongoRepository.js +28 -0
  8. package/dist/repository/sqlite/UserSqliteRepository.js +44 -1
  9. package/dist/routes/UserRoutes.js +11 -6
  10. package/dist/services/UserEmailService.js +54 -0
  11. package/dist/services/UserService.js +97 -6
  12. package/package.json +7 -6
  13. package/src/config/IdentityConfig.ts +2 -0
  14. package/src/controllers/UserController.ts +190 -21
  15. package/src/html/RegistrationCompleteHtml.ts +52 -0
  16. package/src/interfaces/IUserRepository.ts +5 -0
  17. package/src/models/UserModel.ts +6 -1
  18. package/src/repository/mongo/UserApiKeyMongoRepository.ts +2 -2
  19. package/src/repository/mongo/UserMongoRepository.ts +32 -1
  20. package/src/repository/sqlite/UserSqliteRepository.ts +51 -1
  21. package/src/routes/UserRoutes.ts +16 -6
  22. package/src/services/UserEmailService.ts +78 -0
  23. package/src/services/UserService.ts +107 -12
  24. package/test/repository/sqlite/role-sqlite-repository.test.ts +10 -10
  25. package/tsconfig.tsbuildinfo +1 -1
  26. package/types/config/IdentityConfig.d.ts +2 -1
  27. package/types/config/IdentityConfig.d.ts.map +1 -1
  28. package/types/controllers/UserController.d.ts +7 -2
  29. package/types/controllers/UserController.d.ts.map +1 -1
  30. package/types/factory/UserApiKeyServiceFactory.d.ts +1 -1
  31. package/types/factory/UserRegistryServiceFactory.d.ts +4 -0
  32. package/types/factory/UserRegistryServiceFactory.d.ts.map +1 -0
  33. package/types/html/RegistrationCompleteHtml.d.ts +3 -0
  34. package/types/html/RegistrationCompleteHtml.d.ts.map +1 -0
  35. package/types/interfaces/IUserRepository.d.ts +4 -0
  36. package/types/interfaces/IUserRepository.d.ts.map +1 -1
  37. package/types/models/UserModel.d.ts.map +1 -1
  38. package/types/repository/mongo/UserApiKeyMongoRepository.d.ts +2 -2
  39. package/types/repository/mongo/UserApiKeyMongoRepository.d.ts.map +1 -1
  40. package/types/repository/mongo/UserMongoRepository.d.ts +5 -0
  41. package/types/repository/mongo/UserMongoRepository.d.ts.map +1 -1
  42. package/types/repository/sqlite/UserApiKeySqliteRepository.d.ts +1 -1
  43. package/types/repository/sqlite/UserSqliteRepository.d.ts +5 -0
  44. package/types/repository/sqlite/UserSqliteRepository.d.ts.map +1 -1
  45. package/types/routes/UserRoutes.d.ts.map +1 -1
  46. package/types/services/UserEmailService.d.ts +7 -0
  47. package/types/services/UserEmailService.d.ts.map +1 -0
  48. package/types/services/UserService.d.ts +6 -0
  49. package/types/services/UserService.d.ts.map +1 -1
  50. package/types/zod/UserZod.d.ts +8 -8
@@ -1,5 +1,5 @@
1
1
  import { ZodError } from "zod";
2
- import { ValidationError, ZodErrorToValidationError } from "@drax/common-back";
2
+ import { SecuritySensitiveError, ValidationError, ZodErrorToValidationError } from "@drax/common-back";
3
3
  import AuthUtils from "../utils/AuthUtils.js";
4
4
  import { createUserSchema, editUserSchema, userBaseSchema } from "../zod/UserZod.js";
5
5
  import BadCredentialsError from "../errors/BadCredentialsError.js";
@@ -14,7 +14,7 @@ class UserService extends AbstractService {
14
14
  async auth(username, password) {
15
15
  let user = null;
16
16
  console.log("auth username", username);
17
- user = await this.findByUsername(username);
17
+ user = await this.findByUsernameWithPassword(username);
18
18
  if (user && user.active && AuthUtils.checkPassword(password, user.password)) {
19
19
  //TODO: Generar session
20
20
  const session = randomUUID();
@@ -83,11 +83,92 @@ class UserService extends AbstractService {
83
83
  throw new BadCredentialsError();
84
84
  }
85
85
  }
86
+ async recoveryCode(email) {
87
+ try {
88
+ const recoveryCode = randomUUID();
89
+ const user = await this._repository.findByEmail(email);
90
+ if (user && user.active) {
91
+ await this._repository.updatePartial(user.id, { recoveryCode: recoveryCode });
92
+ return recoveryCode;
93
+ }
94
+ else {
95
+ throw new SecuritySensitiveError();
96
+ }
97
+ }
98
+ catch (e) {
99
+ console.error("recoveryCode:", e);
100
+ throw e;
101
+ }
102
+ }
103
+ async changeUserPasswordByCode(recoveryCode, newPassword) {
104
+ try {
105
+ console.log("changeUserPasswordByCode recoveryCode", recoveryCode);
106
+ const user = await this._repository.findByRecoveryCode(recoveryCode);
107
+ console.log("changeUserPasswordByCode user", user);
108
+ if (user && user.active) {
109
+ newPassword = AuthUtils.hashPassword(newPassword);
110
+ await this._repository.changePassword(user.id, newPassword);
111
+ await this._repository.updatePartial(user.id, { recoveryCode: null });
112
+ return true;
113
+ }
114
+ else {
115
+ throw new ValidationError([{ field: 'recoveryCode', reason: 'validation.notFound' }]);
116
+ }
117
+ }
118
+ catch (e) {
119
+ console.error("changeUserPasswordByCode", e);
120
+ throw e;
121
+ }
122
+ }
123
+ async register(userData) {
124
+ try {
125
+ userData.emailVerified = false;
126
+ userData.phoneVerified = false;
127
+ userData.active = false;
128
+ userData.emailCode = randomUUID();
129
+ userData.phoneCode = randomUUID();
130
+ let user = await this.create(userData);
131
+ return user;
132
+ }
133
+ catch (e) {
134
+ console.error("Error registry user", e);
135
+ if (e instanceof ZodError) {
136
+ throw ZodErrorToValidationError(e, userData);
137
+ }
138
+ throw e;
139
+ }
140
+ }
141
+ async verifyEmail(emailCode) {
142
+ const user = await this._repository.findByEmailCode(emailCode);
143
+ if (user && user.emailVerified === false) {
144
+ await this._repository.updatePartial(user.id, {
145
+ emailVerified: true,
146
+ active: true
147
+ });
148
+ return true;
149
+ }
150
+ else {
151
+ throw new ValidationError([{ field: 'emailCode', reason: 'validation.notFound' }]);
152
+ }
153
+ }
154
+ async verifyPhone(phoneCode) {
155
+ const user = await this._repository.findByPhoneCode(phoneCode);
156
+ if (user && user.phoneVerified === false) {
157
+ await this._repository.updatePartial(user.id, {
158
+ phoneVerified: true,
159
+ active: true
160
+ });
161
+ return true;
162
+ }
163
+ else {
164
+ throw new ValidationError([{ field: 'phoneCode', reason: 'validation.notFound' }]);
165
+ }
166
+ }
86
167
  async create(userData) {
87
168
  try {
88
169
  userData.name = userData?.name?.trim();
89
- userData.username = userData.username.trim();
90
- userData.password = userData.password.trim();
170
+ userData.username = userData?.username.trim();
171
+ userData.password = userData?.password.trim();
91
172
  userData.tenant = userData.tenant === "" ? null : userData.tenant;
92
173
  await createUserSchema.parseAsync(userData);
93
174
  userData.password = AuthUtils.hashPassword(userData.password.trim());
@@ -104,8 +185,8 @@ class UserService extends AbstractService {
104
185
  }
105
186
  async update(id, userData) {
106
187
  try {
107
- userData.name = userData.name.trim();
108
- userData.username = userData.username.trim();
188
+ userData.name = userData?.name.trim();
189
+ userData.username = userData?.username.trim();
109
190
  delete userData.password;
110
191
  userData.tenant = userData.tenant === "" ? null : userData.tenant;
111
192
  await editUserSchema.parseAsync(userData);
@@ -153,6 +234,16 @@ class UserService extends AbstractService {
153
234
  throw e;
154
235
  }
155
236
  }
237
+ async findByUsernameWithPassword(username) {
238
+ try {
239
+ const user = await this._repository.findByUsernameWithPassword(username);
240
+ return user;
241
+ }
242
+ catch (e) {
243
+ console.error("Error finding user by username", e);
244
+ throw e;
245
+ }
246
+ }
156
247
  async findByEmail(email) {
157
248
  try {
158
249
  const user = await this._repository.findByEmail(email);
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.9.0",
6
+ "version": "0.11.3",
7
7
  "description": "Identity module for user management, authentication and authorization.",
8
8
  "main": "dist/index.js",
9
9
  "types": "types/index.d.ts",
@@ -28,10 +28,11 @@
28
28
  "author": "Cristian Incarnato & Drax Team",
29
29
  "license": "ISC",
30
30
  "dependencies": {
31
- "@drax/common-back": "^0.9.0",
32
- "@drax/crud-back": "^0.9.0",
33
- "@drax/crud-share": "^0.9.0",
34
- "@drax/identity-share": "^0.9.0",
31
+ "@drax/common-back": "^0.11.3",
32
+ "@drax/crud-back": "^0.11.3",
33
+ "@drax/crud-share": "^0.11.3",
34
+ "@drax/email-back": "^0.11.3",
35
+ "@drax/identity-share": "^0.11.3",
35
36
  "bcryptjs": "^2.4.3",
36
37
  "express-jwt": "^8.4.1",
37
38
  "graphql": "^16.8.2",
@@ -62,5 +63,5 @@
62
63
  "debug": "0"
63
64
  }
64
65
  },
65
- "gitHead": "aae0068b0343cbb23e85c2a9ea454e183739a8a7"
66
+ "gitHead": "54216d94d68dac488969f9c95d3c6be780935f9d"
66
67
  }
@@ -13,6 +13,8 @@ enum IdentityConfig {
13
13
 
14
14
  AvatarDir = "DRAX_AVATAR_DIR",
15
15
 
16
+ defaultRole = "DRAX_DEFAULT_ROLE",
17
+
16
18
 
17
19
  }
18
20
 
@@ -1,6 +1,15 @@
1
1
  import type {IUser, IUserUpdate, IUserCreate} from "@drax/identity-share";
2
2
  import {AbstractFastifyController} from "@drax/crud-back";
3
- import {CommonConfig, DraxConfig, StoreManager, UploadFileError, ValidationError, UnauthorizedError} from "@drax/common-back";
3
+ import RegistrationCompleteHtml from "../html/RegistrationCompleteHtml.js";
4
+ import {
5
+ CommonConfig,
6
+ DraxConfig,
7
+ StoreManager,
8
+ UploadFileError,
9
+ ValidationError,
10
+ UnauthorizedError,
11
+ SecuritySensitiveError
12
+ } from "@drax/common-back";
4
13
 
5
14
  import UserServiceFactory from "../factory/UserServiceFactory.js";
6
15
  import RoleServiceFactory from "../factory/RoleServiceFactory.js";
@@ -9,13 +18,14 @@ import UserPermissions from "../permissions/UserPermissions.js";
9
18
  import BadCredentialsError from "../errors/BadCredentialsError.js";
10
19
  import {join} from "path";
11
20
  import {IdentityConfig} from "../config/IdentityConfig.js";
21
+ import UserEmailService from "../services/UserEmailService.js";
12
22
 
13
23
  const BASE_FILE_DIR = DraxConfig.getOrLoad(CommonConfig.FileDir) || 'files';
14
24
  const AVATAR_DIR = DraxConfig.getOrLoad(IdentityConfig.AvatarDir) || 'avatar';
15
25
  const BASE_URL = DraxConfig.getOrLoad(CommonConfig.BaseUrl) ? DraxConfig.get(CommonConfig.BaseUrl).replace(/\/$/, '') : ''
16
26
 
17
27
 
18
- class UserController extends AbstractFastifyController<IUser, IUserCreate, IUserUpdate> {
28
+ class UserController extends AbstractFastifyController<IUser, IUserCreate, IUserUpdate> {
19
29
 
20
30
  protected service: UserService
21
31
 
@@ -76,11 +86,11 @@ class UserController extends AbstractFastifyController<IUser, IUserCreate, IUser
76
86
  const search = request.query.search
77
87
  const userService = UserServiceFactory()
78
88
  const filters = []
79
- if(request.rbac.getAuthUser.tenantId){
89
+ if (request.rbac.getAuthUser.tenantId) {
80
90
  filters.push({field: 'tenant', operator: 'eq', value: request.rbac.getAuthUser.tenantId})
81
91
  }
82
92
  let paginateResult = await userService.paginate({page, limit, orderBy, order, search, filters})
83
- for(let item of paginateResult.items){
93
+ for (let item of paginateResult.items) {
84
94
  item.password = undefined
85
95
  delete item.password
86
96
  }
@@ -104,11 +114,11 @@ class UserController extends AbstractFastifyController<IUser, IUserCreate, IUser
104
114
  try {
105
115
  request.rbac.assertPermission(UserPermissions.View)
106
116
  const filters = []
107
- if(request.rbac.getAuthUser.tenantId){
117
+ if (request.rbac.getAuthUser.tenantId) {
108
118
  filters.push({field: 'tenant', operator: 'eq', value: request.rbac.getAuthUser.tenantId})
109
119
  }
110
120
  const search = request.query.search
111
- let item = await this.service.search(search,1000,filters)
121
+ let item = await this.service.search(search, 1000, filters)
112
122
  return item
113
123
  } catch (e) {
114
124
  console.error(e)
@@ -125,6 +135,90 @@ class UserController extends AbstractFastifyController<IUser, IUserCreate, IUser
125
135
  }
126
136
  }
127
137
 
138
+ async register(request, reply) {
139
+ try {
140
+ if (request.rbac.getAuthUser) {
141
+ throw new UnauthorizedError()
142
+ }
143
+
144
+ const payload = request.body
145
+
146
+ const defaultRole = DraxConfig.getOrLoad(IdentityConfig.defaultRole)
147
+
148
+ if (!defaultRole) {
149
+ throw new ValidationError([{field: 'username', reason: 'Default role not found'}])
150
+ }
151
+
152
+ const roleService = RoleServiceFactory()
153
+ const role = await roleService.findByName(defaultRole)
154
+
155
+ if (!role) {
156
+ throw new ValidationError([{field: 'role', reason: 'Role not found'}])
157
+ } else if (role.name === 'Admin') {
158
+ payload.tenant = null
159
+ }
160
+
161
+ payload.role = role.id
162
+ payload.origin ??= 'Registry'
163
+
164
+ const userService = UserServiceFactory()
165
+ let user = await userService.register(payload)
166
+
167
+ //SEND EMAIL FOR EMAIL VERIFICATION
168
+ await UserEmailService.emailVerifyCode(user.emailCode, user.email)
169
+
170
+ return user
171
+ } catch (e) {
172
+ console.error(e)
173
+ if (e instanceof ValidationError) {
174
+ reply.statusCode = e.statusCode
175
+ reply.send({error: e.message, inputErrors: e.errors})
176
+ } else if (e instanceof UnauthorizedError) {
177
+ reply.statusCode = e.statusCode
178
+ reply.send({error: e.message})
179
+ } else {
180
+ reply.statusCode = 500
181
+ reply.send({error: 'error.server'})
182
+ }
183
+ }
184
+ }
185
+
186
+ async verifyEmail(request, reply) {
187
+ try {
188
+ const emailCode = request.params.code
189
+ const userService = UserServiceFactory()
190
+ const r = await userService.verifyEmail(emailCode)
191
+ if(r){
192
+ const html = RegistrationCompleteHtml
193
+ reply.header('Content-Type', 'text/html; charset=utf-8').send(html)
194
+ }
195
+ } catch (e) {
196
+ console.error(e)
197
+ if (e instanceof ValidationError) {
198
+ reply.statusCode = e.statusCode
199
+ reply.send({error: e.message, inputErrors: e.errors})
200
+ }
201
+ reply.code(500)
202
+ reply.send({error: 'error.server'})
203
+ }
204
+ }
205
+
206
+ async verifyPhone(request, reply) {
207
+ try {
208
+ const phoneCode = request.params.code
209
+ const userService = UserServiceFactory()
210
+ return await userService.verifyPhone(phoneCode)
211
+ } catch (e) {
212
+ console.error(e)
213
+ if (e instanceof ValidationError) {
214
+ reply.statusCode = e.statusCode
215
+ reply.send({error: e.message, inputErrors: e.errors})
216
+ }
217
+ reply.code(500)
218
+ reply.send({error: 'error.server'})
219
+ }
220
+ }
221
+
128
222
  async create(request, reply) {
129
223
  try {
130
224
  request.rbac.assertPermission(UserPermissions.Create)
@@ -132,11 +226,11 @@ class UserController extends AbstractFastifyController<IUser, IUserCreate, IUser
132
226
 
133
227
  const roleService = RoleServiceFactory()
134
228
  const role = await roleService.findById(payload.role)
135
- if(!role){
229
+ if (!role) {
136
230
  throw new ValidationError([{field: 'role', reason: 'Role not found'}])
137
- }else if(role.name === 'Admin'){
231
+ } else if (role.name === 'Admin') {
138
232
  payload.tenant = null
139
- }else if(request.rbac.getAuthUser.tenantId){
233
+ } else if (request.rbac.getAuthUser.tenantId) {
140
234
  payload.tenant = request.rbac.getAuthUser.tenantId
141
235
  }
142
236
 
@@ -146,6 +240,7 @@ class UserController extends AbstractFastifyController<IUser, IUserCreate, IUser
146
240
  let user = await userService.create(payload)
147
241
  return user
148
242
  } catch (e) {
243
+ console.error(e)
149
244
  if (e instanceof ValidationError) {
150
245
  reply.statusCode = e.statusCode
151
246
  reply.send({error: e.message, inputErrors: e.errors})
@@ -168,11 +263,11 @@ class UserController extends AbstractFastifyController<IUser, IUserCreate, IUser
168
263
 
169
264
  const roleService = RoleServiceFactory()
170
265
  const role = await roleService.findById(payload.role)
171
- if(!role){
266
+ if (!role) {
172
267
  throw new ValidationError([{field: 'role', reason: 'Role not found'}])
173
- }else if(role.name === 'Admin'){
268
+ } else if (role.name === 'Admin') {
174
269
  payload.tenant = null
175
- }else if(request.rbac.getAuthUser.tenantId){
270
+ } else if (request.rbac.getAuthUser.tenantId) {
176
271
  payload.tenant = request.rbac.getAuthUser.tenantId
177
272
  }
178
273
 
@@ -180,6 +275,7 @@ class UserController extends AbstractFastifyController<IUser, IUserCreate, IUser
180
275
  let user = await userService.update(id, payload)
181
276
  return user
182
277
  } catch (e) {
278
+ console.error(e)
183
279
  if (e instanceof ValidationError) {
184
280
  reply.statusCode = e.statusCode
185
281
  reply.send({error: e.message, inputErrors: e.errors})
@@ -202,13 +298,14 @@ class UserController extends AbstractFastifyController<IUser, IUserCreate, IUser
202
298
  request.rbac.assertPermission(UserPermissions.Delete)
203
299
  const id = request.params.id
204
300
  const userService = UserServiceFactory()
205
- let r : boolean = await userService.delete(id)
206
- if(r){
301
+ let r: boolean = await userService.delete(id)
302
+ if (r) {
207
303
  reply.send({message: 'Deleted successfully'})
208
- }else{
304
+ } else {
209
305
  reply.statusCode(400).send({message: 'Not deleted'})
210
306
  }
211
307
  } catch (e) {
308
+ console.error(e)
212
309
  if (e instanceof ValidationError) {
213
310
  reply.statusCode = e.statusCode
214
311
  reply.send({error: e.message, inputErrors: e.errors})
@@ -222,9 +319,81 @@ class UserController extends AbstractFastifyController<IUser, IUserCreate, IUser
222
319
  }
223
320
  }
224
321
 
225
- async myPassword(request, reply) {
322
+ async passwordRecoveryRequest(request, reply) {
323
+ //let message = 'Si el correo electrónico existe, se han enviado instrucciones.'
324
+ let message = 'user.events.recoveryPasswordInfo'
325
+
326
+ try {
327
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
328
+ const email = request.body.email
329
+
330
+ if(!email || !emailRegex.test(email)){
331
+ throw new ValidationError([{field: 'email', reason: 'validation.email.invalid'}])
332
+ }
333
+
334
+ const userService = UserServiceFactory()
335
+ const code = await userService.recoveryCode(email)
336
+
337
+ if (code) {
338
+ await UserEmailService.recoveryCode(code, email)
339
+ }
340
+
341
+ reply.send({message})
342
+
343
+ } catch (e) {
344
+ console.error('recoveryPassword error', e)
345
+ if (e instanceof ValidationError) {
346
+ reply.statusCode = e.statusCode
347
+ reply.send({error: e.message, inputErrors: e.errors})
348
+ }else if (e instanceof SecuritySensitiveError) {
349
+ reply.statusCode = e.statusCode
350
+ reply.send({message})
351
+ } else {
352
+ reply.statusCode = 500
353
+ reply.send({error: 'error.server'})
354
+ }
355
+ }
356
+ }
357
+
358
+ async recoveryPasswordComplete(request, reply) {
359
+
226
360
  try {
227
- if(!request.authUser){
361
+ const recoveryCode = request.body.recoveryCode
362
+ const newPassword = request.body.newPassword
363
+
364
+ if(!recoveryCode){
365
+ throw new ValidationError([{field:'recoveryCode', reason: 'validation.required'}])
366
+ }
367
+
368
+ if(!newPassword){
369
+ throw new ValidationError([{field:'newPassword', reason: 'validation.required'}])
370
+ }
371
+
372
+ const userService = UserServiceFactory()
373
+ const result: boolean = await userService.changeUserPasswordByCode(recoveryCode, newPassword)
374
+ if(result){
375
+ reply.send({message: 'action.success'})
376
+ }else{
377
+ reply.statusCode = 400
378
+ reply.send({message: 'action.failure'})
379
+ }
380
+
381
+ } catch (e) {
382
+ console.error('recoveryPassword error', e)
383
+ if (e instanceof ValidationError) {
384
+ reply.statusCode = e.statusCode
385
+ reply.send({error: e.message, inputErrors: e.errors})
386
+ } else {
387
+ reply.statusCode = 500
388
+ reply.send({error: 'error.server'})
389
+ }
390
+ }
391
+ }
392
+
393
+
394
+ async changeMyPassword(request, reply) {
395
+ try {
396
+ if (!request.authUser) {
228
397
  throw new UnauthorizedError()
229
398
  }
230
399
  const userId = request.authUser.id
@@ -233,7 +402,7 @@ class UserController extends AbstractFastifyController<IUser, IUserCreate, IUser
233
402
  const userService = UserServiceFactory()
234
403
  return await userService.changeOwnPassword(userId, currentPassword, newPassword)
235
404
  } catch (e) {
236
- console.error('/api/password error', e)
405
+ console.error('changeMyPassword error', e)
237
406
  if (e instanceof ValidationError) {
238
407
  reply.statusCode = e.statusCode
239
408
  reply.send({error: e.message, inputErrors: e.errors})
@@ -247,11 +416,11 @@ class UserController extends AbstractFastifyController<IUser, IUserCreate, IUser
247
416
  }
248
417
  }
249
418
 
250
- async password(request, reply) {
419
+ async changePassword(request, reply) {
251
420
  try {
252
421
  request.rbac.assertPermission(UserPermissions.Update)
253
422
  const userId = request.params.id
254
- if(!userId){
423
+ if (!userId) {
255
424
  throw new UnauthorizedError()
256
425
  }
257
426
  const newPassword = request.body.newPassword
@@ -288,7 +457,7 @@ class UserController extends AbstractFastifyController<IUser, IUserCreate, IUser
288
457
 
289
458
  const destinationPath = join(BASE_FILE_DIR, AVATAR_DIR)
290
459
  const storedFile = await StoreManager.saveFile(file, destinationPath)
291
- const urlFile = BASE_URL + '/api/user/avatar/' + storedFile.filename
460
+ const urlFile = BASE_URL + '/api/users/avatar/' + storedFile.filename
292
461
 
293
462
  //Save into DB
294
463
  const userService = UserServiceFactory()
@@ -0,0 +1,52 @@
1
+ const html = `
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Registro Completo</title>
8
+ <style>
9
+ body {
10
+ margin: 0;
11
+ height: 100vh;
12
+ display: flex;
13
+ justify-content: center;
14
+ align-items: center;
15
+ background-color: #f5f5f5;
16
+ font-family: Arial, sans-serif;
17
+ }
18
+ .container {
19
+ width: 300px;
20
+ border: 1px solid grey;
21
+ padding: 20px;
22
+ text-align: center;
23
+ background: white;
24
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
25
+ border-radius: 8px;
26
+ }
27
+ .title {
28
+ color: darkgreen;
29
+ font-size: 20px;
30
+ font-weight: bold;
31
+ margin-bottom: 20px;
32
+ }
33
+ .link {
34
+ margin-top: 20px;
35
+ text-decoration: none;
36
+ color: #007bff;
37
+ }
38
+ .link:hover {
39
+ text-decoration: underline;
40
+ }
41
+ </style>
42
+ </head>
43
+ <body>
44
+ <div class="container">
45
+ <div class="title">Registro Completo</div>
46
+ <a href="/login" class="link">Login</a>
47
+ </div>
48
+ </body>
49
+ </html>
50
+ `
51
+
52
+ export default html
@@ -4,9 +4,14 @@ import {IDraxCrud, IDraxFieldFilter} from "@drax/crud-share";
4
4
  interface IUserRepository extends IDraxCrud<IUser, IUserCreate, IUserUpdate>{
5
5
  findById(id: string): Promise<IUser | null>;
6
6
  findByUsername(username: string): Promise<IUser | null>;
7
+ findByUsernameWithPassword(username: string): Promise<IUser | null>;
7
8
  findByEmail(email: string): Promise<IUser | null>;
8
9
  changePassword(id: string, password:string):Promise<Boolean>;
9
10
  changeAvatar(id: string, avatarUrl: string): Promise<Boolean>;
11
+
12
+ findByEmailCode(code: string): Promise<IUser | null>;
13
+ findByPhoneCode(code: string): Promise<IUser | null>;
14
+ findByRecoveryCode(code: string): Promise<IUser | null>;
10
15
  }
11
16
 
12
17
  export {IUserRepository}
@@ -33,7 +33,7 @@ const UserSchema = new mongoose.Schema<IUser>({
33
33
  message: "Invalid email format"
34
34
  }
35
35
  },
36
- password: {type: String, required: true, index: false},
36
+ password: {type: String, required: true, index: false, select: false},
37
37
  name: {type: String, required: false, index: false},
38
38
  active: {type: Boolean, required: true, default: false, index: false},
39
39
  phone: {
@@ -69,6 +69,11 @@ const UserSchema = new mongoose.Schema<IUser>({
69
69
  required: false,
70
70
  index: false
71
71
  }],
72
+ emailVerified: {type: Boolean, required: true, default: false, index: false},
73
+ phoneVerified: {type: Boolean, required: true, default: false, index: false},
74
+ emailCode: {type: String, required: false, index: false, select: false},
75
+ phoneCode: {type: String, required: false, index: false, select: false},
76
+ recoveryCode: {type: String, required: false, index: false, select: false},
72
77
  }, {timestamps: true});
73
78
 
74
79
  UserSchema.set('toJSON', {getters: true});
@@ -11,7 +11,7 @@ import {PaginateResult} from "mongoose";
11
11
  import {IDraxPaginateOptions, IDraxPaginateResult} from "@drax/crud-share";
12
12
  import {IUserApiKeyRepository} from "../../interfaces/IUserApiKeyRepository";
13
13
 
14
- class UserMongoRepository implements IUserApiKeyRepository {
14
+ class UserApiKeyMongoRepository implements IUserApiKeyRepository {
15
15
 
16
16
 
17
17
  constructor() {
@@ -101,4 +101,4 @@ class UserMongoRepository implements IUserApiKeyRepository {
101
101
 
102
102
  }
103
103
 
104
- export default UserMongoRepository
104
+ export default UserApiKeyMongoRepository
@@ -12,7 +12,6 @@ import type {IUserRepository} from "../../interfaces/IUserRepository";
12
12
  import {Cursor, PaginateResult} from "mongoose";
13
13
  import RoleMongoRepository from "./RoleMongoRepository.js";
14
14
  import {IDraxFindOptions, IDraxPaginateOptions, IDraxPaginateResult} from "@drax/crud-share";
15
- import {IRole, ITenant} from "@drax/identity-share";
16
15
  import type {IDraxFieldFilter} from "@drax/crud-share/dist";
17
16
 
18
17
  class UserMongoRepository implements IUserRepository {
@@ -58,6 +57,18 @@ class UserMongoRepository implements IUserRepository {
58
57
  }
59
58
  }
60
59
 
60
+ async updatePartial(id: string, data: any): Promise<IUser> {
61
+ try {
62
+ const item: mongoose.HydratedDocument<IUser> = await UserModel.findOneAndUpdate({_id: id}, data, {new: true}).populate(['role','tenant']).exec()
63
+ return item
64
+ } catch (e) {
65
+ if (e instanceof mongoose.Error.ValidationError) {
66
+ throw MongooseErrorToValidationError(e)
67
+ }
68
+ throw e
69
+ }
70
+ }
71
+
61
72
  async delete(id: string): Promise<boolean> {
62
73
  const result: DeleteResult = await UserModel.deleteOne({_id: id}).exec()
63
74
  return result.deletedCount == 1
@@ -74,11 +85,31 @@ class UserMongoRepository implements IUserRepository {
74
85
  return user
75
86
  }
76
87
 
88
+ async findByUsernameWithPassword(username: string): Promise<IUser> {
89
+ const user: mongoose.HydratedDocument<IUser> = await UserModel.findOne({username: username}).select('+password').populate(['role','tenant']).exec()
90
+ return user
91
+ }
92
+
77
93
  async findByEmail(email: string): Promise<IUser> {
78
94
  const user: mongoose.HydratedDocument<IUser> = await UserModel.findOne({email: email}).populate(['role','tenant']).exec()
79
95
  return user
80
96
  }
81
97
 
98
+ async findByEmailCode(code: string): Promise<IUser> {
99
+ const user: mongoose.HydratedDocument<IUser> = await UserModel.findOne({emailCode: {$eq: code}, emailVerified: {$eq: false} }).populate(['role','tenant']).exec()
100
+ return user
101
+ }
102
+
103
+ async findByPhoneCode(code: string): Promise<IUser> {
104
+ const user: mongoose.HydratedDocument<IUser> = await UserModel.findOne({phoneCode: {$eq: code}, phoneVerified: {$eq: false} }).populate(['role','tenant']).exec()
105
+ return user
106
+ }
107
+
108
+ async findByRecoveryCode(code: string): Promise<IUser> {
109
+ const user: mongoose.HydratedDocument<IUser> = await UserModel.findOne({recoveryCode: {$eq: code}, active: {$eq: true} }).populate(['role','tenant']).exec()
110
+ return user
111
+ }
112
+
82
113
  async paginate({
83
114
  page= 1,
84
115
  limit= 5,