@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
@@ -7,6 +7,7 @@ var IdentityConfig;
7
7
  IdentityConfig["ApiKeyCacheTTL"] = "DRAX_APIKEY_CACHE_TTL";
8
8
  IdentityConfig["RbacCacheTTL"] = "DRAX_RBAC_CACHE_TTL";
9
9
  IdentityConfig["AvatarDir"] = "DRAX_AVATAR_DIR";
10
+ IdentityConfig["defaultRole"] = "DRAX_DEFAULT_ROLE";
10
11
  })(IdentityConfig || (IdentityConfig = {}));
11
12
  export default IdentityConfig;
12
13
  export { IdentityConfig };
@@ -1,11 +1,13 @@
1
1
  import { AbstractFastifyController } from "@drax/crud-back";
2
- import { CommonConfig, DraxConfig, StoreManager, UploadFileError, ValidationError, UnauthorizedError } from "@drax/common-back";
2
+ import RegistrationCompleteHtml from "../html/RegistrationCompleteHtml.js";
3
+ import { CommonConfig, DraxConfig, StoreManager, UploadFileError, ValidationError, UnauthorizedError, SecuritySensitiveError } from "@drax/common-back";
3
4
  import UserServiceFactory from "../factory/UserServiceFactory.js";
4
5
  import RoleServiceFactory from "../factory/RoleServiceFactory.js";
5
6
  import UserPermissions from "../permissions/UserPermissions.js";
6
7
  import BadCredentialsError from "../errors/BadCredentialsError.js";
7
8
  import { join } from "path";
8
9
  import { IdentityConfig } from "../config/IdentityConfig.js";
10
+ import UserEmailService from "../services/UserEmailService.js";
9
11
  const BASE_FILE_DIR = DraxConfig.getOrLoad(CommonConfig.FileDir) || 'files';
10
12
  const AVATAR_DIR = DraxConfig.getOrLoad(IdentityConfig.AvatarDir) || 'avatar';
11
13
  const BASE_URL = DraxConfig.getOrLoad(CommonConfig.BaseUrl) ? DraxConfig.get(CommonConfig.BaseUrl).replace(/\/$/, '') : '';
@@ -120,6 +122,84 @@ class UserController extends AbstractFastifyController {
120
122
  }
121
123
  }
122
124
  }
125
+ async register(request, reply) {
126
+ try {
127
+ if (request.rbac.getAuthUser) {
128
+ throw new UnauthorizedError();
129
+ }
130
+ const payload = request.body;
131
+ const defaultRole = DraxConfig.getOrLoad(IdentityConfig.defaultRole);
132
+ if (!defaultRole) {
133
+ throw new ValidationError([{ field: 'username', reason: 'Default role not found' }]);
134
+ }
135
+ const roleService = RoleServiceFactory();
136
+ const role = await roleService.findByName(defaultRole);
137
+ if (!role) {
138
+ throw new ValidationError([{ field: 'role', reason: 'Role not found' }]);
139
+ }
140
+ else if (role.name === 'Admin') {
141
+ payload.tenant = null;
142
+ }
143
+ payload.role = role.id;
144
+ payload.origin ?? (payload.origin = 'Registry');
145
+ const userService = UserServiceFactory();
146
+ let user = await userService.register(payload);
147
+ //SEND EMAIL FOR EMAIL VERIFICATION
148
+ await UserEmailService.emailVerifyCode(user.emailCode, user.email);
149
+ return user;
150
+ }
151
+ catch (e) {
152
+ console.error(e);
153
+ if (e instanceof ValidationError) {
154
+ reply.statusCode = e.statusCode;
155
+ reply.send({ error: e.message, inputErrors: e.errors });
156
+ }
157
+ else if (e instanceof UnauthorizedError) {
158
+ reply.statusCode = e.statusCode;
159
+ reply.send({ error: e.message });
160
+ }
161
+ else {
162
+ reply.statusCode = 500;
163
+ reply.send({ error: 'error.server' });
164
+ }
165
+ }
166
+ }
167
+ async verifyEmail(request, reply) {
168
+ try {
169
+ const emailCode = request.params.code;
170
+ const userService = UserServiceFactory();
171
+ const r = await userService.verifyEmail(emailCode);
172
+ if (r) {
173
+ const html = RegistrationCompleteHtml;
174
+ reply.header('Content-Type', 'text/html; charset=utf-8').send(html);
175
+ }
176
+ }
177
+ catch (e) {
178
+ console.error(e);
179
+ if (e instanceof ValidationError) {
180
+ reply.statusCode = e.statusCode;
181
+ reply.send({ error: e.message, inputErrors: e.errors });
182
+ }
183
+ reply.code(500);
184
+ reply.send({ error: 'error.server' });
185
+ }
186
+ }
187
+ async verifyPhone(request, reply) {
188
+ try {
189
+ const phoneCode = request.params.code;
190
+ const userService = UserServiceFactory();
191
+ return await userService.verifyPhone(phoneCode);
192
+ }
193
+ catch (e) {
194
+ console.error(e);
195
+ if (e instanceof ValidationError) {
196
+ reply.statusCode = e.statusCode;
197
+ reply.send({ error: e.message, inputErrors: e.errors });
198
+ }
199
+ reply.code(500);
200
+ reply.send({ error: 'error.server' });
201
+ }
202
+ }
123
203
  async create(request, reply) {
124
204
  try {
125
205
  request.rbac.assertPermission(UserPermissions.Create);
@@ -141,6 +221,7 @@ class UserController extends AbstractFastifyController {
141
221
  return user;
142
222
  }
143
223
  catch (e) {
224
+ console.error(e);
144
225
  if (e instanceof ValidationError) {
145
226
  reply.statusCode = e.statusCode;
146
227
  reply.send({ error: e.message, inputErrors: e.errors });
@@ -176,6 +257,7 @@ class UserController extends AbstractFastifyController {
176
257
  return user;
177
258
  }
178
259
  catch (e) {
260
+ console.error(e);
179
261
  if (e instanceof ValidationError) {
180
262
  reply.statusCode = e.statusCode;
181
263
  reply.send({ error: e.message, inputErrors: e.errors });
@@ -208,6 +290,7 @@ class UserController extends AbstractFastifyController {
208
290
  }
209
291
  }
210
292
  catch (e) {
293
+ console.error(e);
211
294
  if (e instanceof ValidationError) {
212
295
  reply.statusCode = e.statusCode;
213
296
  reply.send({ error: e.message, inputErrors: e.errors });
@@ -222,7 +305,71 @@ class UserController extends AbstractFastifyController {
222
305
  }
223
306
  }
224
307
  }
225
- async myPassword(request, reply) {
308
+ async passwordRecoveryRequest(request, reply) {
309
+ //let message = 'Si el correo electrónico existe, se han enviado instrucciones.'
310
+ let message = 'user.events.recoveryPasswordInfo';
311
+ try {
312
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
313
+ const email = request.body.email;
314
+ if (!email || !emailRegex.test(email)) {
315
+ throw new ValidationError([{ field: 'email', reason: 'validation.email.invalid' }]);
316
+ }
317
+ const userService = UserServiceFactory();
318
+ const code = await userService.recoveryCode(email);
319
+ if (code) {
320
+ await UserEmailService.recoveryCode(code, email);
321
+ }
322
+ reply.send({ message });
323
+ }
324
+ catch (e) {
325
+ console.error('recoveryPassword error', e);
326
+ if (e instanceof ValidationError) {
327
+ reply.statusCode = e.statusCode;
328
+ reply.send({ error: e.message, inputErrors: e.errors });
329
+ }
330
+ else if (e instanceof SecuritySensitiveError) {
331
+ reply.statusCode = e.statusCode;
332
+ reply.send({ message });
333
+ }
334
+ else {
335
+ reply.statusCode = 500;
336
+ reply.send({ error: 'error.server' });
337
+ }
338
+ }
339
+ }
340
+ async recoveryPasswordComplete(request, reply) {
341
+ try {
342
+ const recoveryCode = request.body.recoveryCode;
343
+ const newPassword = request.body.newPassword;
344
+ if (!recoveryCode) {
345
+ throw new ValidationError([{ field: 'recoveryCode', reason: 'validation.required' }]);
346
+ }
347
+ if (!newPassword) {
348
+ throw new ValidationError([{ field: 'newPassword', reason: 'validation.required' }]);
349
+ }
350
+ const userService = UserServiceFactory();
351
+ const result = await userService.changeUserPasswordByCode(recoveryCode, newPassword);
352
+ if (result) {
353
+ reply.send({ message: 'action.success' });
354
+ }
355
+ else {
356
+ reply.statusCode = 400;
357
+ reply.send({ message: 'action.failure' });
358
+ }
359
+ }
360
+ catch (e) {
361
+ console.error('recoveryPassword error', e);
362
+ if (e instanceof ValidationError) {
363
+ reply.statusCode = e.statusCode;
364
+ reply.send({ error: e.message, inputErrors: e.errors });
365
+ }
366
+ else {
367
+ reply.statusCode = 500;
368
+ reply.send({ error: 'error.server' });
369
+ }
370
+ }
371
+ }
372
+ async changeMyPassword(request, reply) {
226
373
  try {
227
374
  if (!request.authUser) {
228
375
  throw new UnauthorizedError();
@@ -234,7 +381,7 @@ class UserController extends AbstractFastifyController {
234
381
  return await userService.changeOwnPassword(userId, currentPassword, newPassword);
235
382
  }
236
383
  catch (e) {
237
- console.error('/api/password error', e);
384
+ console.error('changeMyPassword error', e);
238
385
  if (e instanceof ValidationError) {
239
386
  reply.statusCode = e.statusCode;
240
387
  reply.send({ error: e.message, inputErrors: e.errors });
@@ -249,7 +396,7 @@ class UserController extends AbstractFastifyController {
249
396
  }
250
397
  }
251
398
  }
252
- async password(request, reply) {
399
+ async changePassword(request, reply) {
253
400
  try {
254
401
  request.rbac.assertPermission(UserPermissions.Update);
255
402
  const userId = request.params.id;
@@ -288,7 +435,7 @@ class UserController extends AbstractFastifyController {
288
435
  };
289
436
  const destinationPath = join(BASE_FILE_DIR, AVATAR_DIR);
290
437
  const storedFile = await StoreManager.saveFile(file, destinationPath);
291
- const urlFile = BASE_URL + '/api/user/avatar/' + storedFile.filename;
438
+ const urlFile = BASE_URL + '/api/users/avatar/' + storedFile.filename;
292
439
  //Save into DB
293
440
  const userService = UserServiceFactory();
294
441
  await userService.changeAvatar(userId, urlFile);
@@ -0,0 +1,24 @@
1
+ import UserRegistryService from "../services/UserRegistryService.js";
2
+ import { COMMON, CommonConfig, DraxConfig } from "@drax/common-back";
3
+ import UserRegistrySqliteRepository from "../repository/sqlite/UserRegistrySqliteRepository.js";
4
+ import UserRegistryMongoRepository from "../repository/mongo/UserRegistryMongoRepository.js";
5
+ let userRegistryService;
6
+ const UserRegistryServiceFactory = (verbose = false) => {
7
+ if (!userRegistryService) {
8
+ let repository;
9
+ switch (DraxConfig.getOrLoad(CommonConfig.DbEngine)) {
10
+ case COMMON.DB_ENGINES.MONGODB:
11
+ repository = new UserRegistryMongoRepository();
12
+ break;
13
+ case COMMON.DB_ENGINES.SQLITE:
14
+ const dbFile = DraxConfig.getOrLoad(CommonConfig.SqliteDbFile);
15
+ repository = new UserRegistrySqliteRepository(dbFile, verbose);
16
+ break;
17
+ default:
18
+ throw new Error("DraxConfig.DB_ENGINE must be one of " + Object.values(COMMON.DB_ENGINES).join(", "));
19
+ }
20
+ userRegistryService = new UserRegistryService(repository);
21
+ }
22
+ return userRegistryService;
23
+ };
24
+ export default UserRegistryServiceFactory;
@@ -0,0 +1,51 @@
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
+ export default html;
@@ -29,7 +29,7 @@ const UserSchema = new mongoose.Schema({
29
29
  message: "Invalid email format"
30
30
  }
31
31
  },
32
- password: { type: String, required: true, index: false },
32
+ password: { type: String, required: true, index: false, select: false },
33
33
  name: { type: String, required: false, index: false },
34
34
  active: { type: Boolean, required: true, default: false, index: false },
35
35
  phone: {
@@ -64,6 +64,11 @@ const UserSchema = new mongoose.Schema({
64
64
  required: false,
65
65
  index: false
66
66
  }],
67
+ emailVerified: { type: Boolean, required: true, default: false, index: false },
68
+ phoneVerified: { type: Boolean, required: true, default: false, index: false },
69
+ emailCode: { type: String, required: false, index: false, select: false },
70
+ phoneCode: { type: String, required: false, index: false, select: false },
71
+ recoveryCode: { type: String, required: false, index: false, select: false },
67
72
  }, { timestamps: true });
68
73
  UserSchema.set('toJSON', { getters: true });
69
74
  UserSchema.plugin(uniqueValidator, { message: 'validation.unique' });
@@ -1,7 +1,7 @@
1
1
  import { UserApiKeyModel } from "../../models/UserApiKeyModel.js";
2
2
  import { mongoose, MongooseErrorToValidationError, MongooseQueryFilter, MongooseSort, MongoServerErrorToValidationError } from "@drax/common-back";
3
3
  import { MongoServerError } from "mongodb";
4
- class UserMongoRepository {
4
+ class UserApiKeyMongoRepository {
5
5
  constructor() {
6
6
  }
7
7
  async create(data) {
@@ -68,4 +68,4 @@ class UserMongoRepository {
68
68
  };
69
69
  }
70
70
  }
71
- export default UserMongoRepository;
71
+ export default UserApiKeyMongoRepository;
@@ -39,6 +39,18 @@ class UserMongoRepository {
39
39
  throw e;
40
40
  }
41
41
  }
42
+ async updatePartial(id, data) {
43
+ try {
44
+ const item = await UserModel.findOneAndUpdate({ _id: id }, data, { new: true }).populate(['role', 'tenant']).exec();
45
+ return item;
46
+ }
47
+ catch (e) {
48
+ if (e instanceof mongoose.Error.ValidationError) {
49
+ throw MongooseErrorToValidationError(e);
50
+ }
51
+ throw e;
52
+ }
53
+ }
42
54
  async delete(id) {
43
55
  const result = await UserModel.deleteOne({ _id: id }).exec();
44
56
  return result.deletedCount == 1;
@@ -51,10 +63,26 @@ class UserMongoRepository {
51
63
  const user = await UserModel.findOne({ username: username }).populate(['role', 'tenant']).exec();
52
64
  return user;
53
65
  }
66
+ async findByUsernameWithPassword(username) {
67
+ const user = await UserModel.findOne({ username: username }).select('+password').populate(['role', 'tenant']).exec();
68
+ return user;
69
+ }
54
70
  async findByEmail(email) {
55
71
  const user = await UserModel.findOne({ email: email }).populate(['role', 'tenant']).exec();
56
72
  return user;
57
73
  }
74
+ async findByEmailCode(code) {
75
+ const user = await UserModel.findOne({ emailCode: { $eq: code }, emailVerified: { $eq: false } }).populate(['role', 'tenant']).exec();
76
+ return user;
77
+ }
78
+ async findByPhoneCode(code) {
79
+ const user = await UserModel.findOne({ phoneCode: { $eq: code }, phoneVerified: { $eq: false } }).populate(['role', 'tenant']).exec();
80
+ return user;
81
+ }
82
+ async findByRecoveryCode(code) {
83
+ const user = await UserModel.findOne({ recoveryCode: { $eq: code }, active: { $eq: true } }).populate(['role', 'tenant']).exec();
84
+ return user;
85
+ }
58
86
  async paginate({ page = 1, limit = 5, orderBy = '', order = false, search = '', filters = [] }) {
59
87
  const query = {};
60
88
  if (search) {
@@ -17,7 +17,11 @@ const tableFields = [
17
17
  { name: "avatar", type: "TEXT", unique: false, primary: false },
18
18
  { name: "origin", type: "TEXT", unique: false, primary: false },
19
19
  { name: "createdAt", type: "TEXT", unique: false, primary: false },
20
- { name: "updatedAt", type: "TEXT", unique: false, primary: false }
20
+ { name: "updatedAt", type: "TEXT", unique: false, primary: false },
21
+ { name: "emailVerified", type: "INTEGER", unique: false, primary: false },
22
+ { name: "phoneVerified", type: "INTEGER", unique: false, primary: false },
23
+ { name: "emailCode", type: "TEXT", unique: false, primary: false },
24
+ { name: "phoneCode", type: "TEXT", unique: false, primary: false },
21
25
  ];
22
26
  class UserSqliteRepository {
23
27
  constructor(dataBaseFile, verbose = false) {
@@ -62,6 +66,9 @@ class UserSqliteRepository {
62
66
  throw SqliteErrorToValidationError(e, userData);
63
67
  }
64
68
  }
69
+ async updatePartial(id, userData) {
70
+ return this.update(id, userData);
71
+ }
65
72
  async update(id, userData) {
66
73
  try {
67
74
  if (!await this.findRoleById(userData.role)) {
@@ -111,6 +118,15 @@ class UserSqliteRepository {
111
118
  user.tenant = await this.findTenantById(user.tenant);
112
119
  return user;
113
120
  }
121
+ async findByUsernameWithPassword(username) {
122
+ const user = this.db.prepare('SELECT * FROM users WHERE username = ?').get(username);
123
+ if (!user) {
124
+ return null;
125
+ }
126
+ user.role = await this.findRoleById(user.role);
127
+ user.tenant = await this.findTenantById(user.tenant);
128
+ return user;
129
+ }
114
130
  async findByEmail(email) {
115
131
  const user = this.db.prepare('SELECT * FROM users WHERE email = ?').get(email);
116
132
  if (!user) {
@@ -120,6 +136,33 @@ class UserSqliteRepository {
120
136
  user.tenant = await this.findTenantById(user.tenant);
121
137
  return user;
122
138
  }
139
+ async findByEmailCode(code) {
140
+ const user = this.db.prepare('SELECT * FROM users WHERE emailVerifyCode = ? AND emailVerified = 0').get(code);
141
+ if (!user) {
142
+ return null;
143
+ }
144
+ user.role = await this.findRoleById(user.role);
145
+ user.tenant = await this.findTenantById(user.tenant);
146
+ return user;
147
+ }
148
+ async findByRecoveryCode(code) {
149
+ const user = this.db.prepare('SELECT * FROM users WHERE recoveryCode = ? AND active = 1').get(code);
150
+ if (!user) {
151
+ return null;
152
+ }
153
+ user.role = await this.findRoleById(user.role);
154
+ user.tenant = await this.findTenantById(user.tenant);
155
+ return user;
156
+ }
157
+ async findByPhoneCode(code) {
158
+ const user = this.db.prepare('SELECT * FROM users WHERE phoneCode = ? AND phoneVerified = 0').get(code);
159
+ if (!user) {
160
+ return null;
161
+ }
162
+ user.role = await this.findRoleById(user.role);
163
+ user.tenant = await this.findTenantById(user.tenant);
164
+ return user;
165
+ }
123
166
  async paginate({ page = 1, limit = 5, orderBy = '', order = false, search = '', filters = [] }) {
124
167
  const offset = page > 1 ? (page - 1) * limit : 0;
125
168
  let where = "";
@@ -1,18 +1,23 @@
1
1
  import UserController from "../controllers/UserController.js";
2
2
  async function UserRoutes(fastify, options) {
3
3
  const controller = new UserController();
4
- fastify.post('/api/auth', (req, rep) => controller.auth(req, rep));
5
- fastify.get('/api/me', (req, rep) => controller.me(req, rep));
4
+ fastify.post('/api/auth/login', (req, rep) => controller.auth(req, rep));
5
+ fastify.get('/api/auth/me', (req, rep) => controller.me(req, rep));
6
6
  fastify.get('/api/users/search', (req, rep) => controller.search(req, rep));
7
7
  fastify.get('/api/users/export', (req, rep) => controller.export(req, rep));
8
8
  fastify.get('/api/users', (req, rep) => controller.paginate(req, rep));
9
9
  fastify.post('/api/users', (req, rep) => controller.create(req, rep));
10
10
  fastify.put('/api/users/:id', (req, rep) => controller.update(req, rep));
11
11
  fastify.delete('/api/users/:id', (req, rep) => controller.delete(req, rep));
12
- fastify.post('/api/password', (req, rep) => controller.myPassword(req, rep));
13
- fastify.post('/api/password/:id', (req, rep) => controller.password(req, rep));
14
- fastify.post('/api/user/avatar', (req, rep) => controller.updateAvatar(req, rep));
15
- fastify.get('/api/user/avatar/:filename', (req, rep) => controller.getAvatar(req, rep));
12
+ fastify.post('/api/users/register', (req, rep) => controller.register(req, rep));
13
+ fastify.get('/api/users/verify-email/:code', (req, rep) => controller.verifyEmail(req, rep));
14
+ fastify.get('/api/users/verify-phone/:code', (req, rep) => controller.verifyPhone(req, rep));
15
+ fastify.post('/api/users/password/change', (req, rep) => controller.changeMyPassword(req, rep));
16
+ fastify.post('/api/users/password/change/:id', (req, rep) => controller.changePassword(req, rep));
17
+ fastify.post('/api/users/password/recovery/request', (req, rep) => controller.passwordRecoveryRequest(req, rep));
18
+ fastify.post('/api/users/password/recovery/complete', (req, rep) => controller.recoveryPasswordComplete(req, rep));
19
+ fastify.post('/api/users/avatar', (req, rep) => controller.updateAvatar(req, rep));
20
+ fastify.get('/api/users/avatar/:filename', (req, rep) => controller.getAvatar(req, rep));
16
21
  }
17
22
  export default UserRoutes;
18
23
  export { UserRoutes };
@@ -0,0 +1,54 @@
1
+ import { EmailTransportConfig, EmailLayoutServiceFactory, EmailTransportServiceFactory } from "@drax/email-back";
2
+ import { CommonConfig, DraxConfig } from "@drax/common-back";
3
+ class UserEmailService {
4
+ static async emailVerifyCode(emailCode, emailTo) {
5
+ let emailLayout = EmailLayoutServiceFactory.instance;
6
+ let baseurl = DraxConfig.getOrLoad(CommonConfig.BaseUrl);
7
+ let body = `
8
+ <h2 style="font-size: 22px; color: #333333; font-weight: 600; margin: 0 0 10px 0;">
9
+ Verificación de Email
10
+ </h2>
11
+ <p style="font-size: 16px; line-height: 1.6; color: #555555; margin: 0 0 15px 0;">
12
+ Para confirmar tu email, haz clic en el siguiente enlace:
13
+ </p>
14
+ <a href="${baseurl}/api/users/verify-email/${emailCode}" style="color: #333333; text-decoration: none; border: 1px solid #333333; padding: 10px 20px; text-align: center; text-decoration: none; display: inline-block;">Verificar Email</a>
15
+ `;
16
+ const emailFrom = DraxConfig.getOrLoad(EmailTransportConfig.authUsername);
17
+ const emailOptions = {
18
+ subject: "Verificación de Email",
19
+ from: emailFrom,
20
+ to: emailTo,
21
+ html: emailLayout.html(body)
22
+ };
23
+ await EmailTransportServiceFactory.instance.sendEmail(emailOptions);
24
+ }
25
+ static async recoveryCode(recoveryCode, emailTo) {
26
+ let emailLayout = EmailLayoutServiceFactory.instance;
27
+ let baseurl = DraxConfig.getOrLoad(CommonConfig.BaseUrl);
28
+ let body = `
29
+ <h2 style="font-size: 22px; color: #333333; font-weight: 600; margin: 0 0 10px 0;">
30
+ Recuperación de Contraseña
31
+ </h2>
32
+ <p style="font-size: 16px; line-height: 1.6; color: #555555; margin: 0 0 15px 0;">
33
+ Accede al siguiente link para recuperar tu contraseña:
34
+ </p>
35
+ <p style="text-align: center; width: 100%;">
36
+ <a href="${baseurl}/password/recovery/complete/${recoveryCode}"
37
+ style="color: #333333; text-decoration: none; border: 1px solid #333333; padding: 10px 20px; text-align: center; text-decoration: none; display: inline-block;">
38
+ Recuperar Contraseña
39
+ </a>
40
+ </p>
41
+
42
+ `;
43
+ const emailFrom = DraxConfig.getOrLoad(EmailTransportConfig.authUsername);
44
+ const emailOptions = {
45
+ subject: "Recuperación de Contraseña",
46
+ from: emailFrom,
47
+ to: emailTo,
48
+ html: emailLayout.html(body)
49
+ };
50
+ await EmailTransportServiceFactory.instance.sendEmail(emailOptions);
51
+ }
52
+ }
53
+ export default UserEmailService;
54
+ export { UserEmailService };