@drax/identity-back 0.8.11 → 0.10.0
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/dist/config/IdentityConfig.js +1 -0
- package/dist/controllers/UserController.js +152 -5
- package/dist/factory/UserRegistryServiceFactory.js +24 -0
- package/dist/html/RegistrationCompleteHtml.js +51 -0
- package/dist/models/UserModel.js +6 -1
- package/dist/repository/mongo/UserApiKeyMongoRepository.js +2 -2
- package/dist/repository/mongo/UserMongoRepository.js +28 -0
- package/dist/repository/sqlite/UserSqliteRepository.js +44 -1
- package/dist/routes/UserRoutes.js +11 -6
- package/dist/services/UserEmailService.js +54 -0
- package/dist/services/UserService.js +97 -6
- package/package.json +7 -6
- package/src/config/IdentityConfig.ts +2 -0
- package/src/controllers/UserController.ts +190 -21
- package/src/html/RegistrationCompleteHtml.ts +52 -0
- package/src/interfaces/IUserRepository.ts +5 -0
- package/src/models/UserModel.ts +6 -1
- package/src/repository/mongo/UserApiKeyMongoRepository.ts +2 -2
- package/src/repository/mongo/UserMongoRepository.ts +32 -1
- package/src/repository/sqlite/UserSqliteRepository.ts +51 -1
- package/src/routes/UserRoutes.ts +16 -6
- package/src/services/UserEmailService.ts +78 -0
- package/src/services/UserService.ts +107 -12
- package/tsconfig.tsbuildinfo +1 -1
- package/types/config/IdentityConfig.d.ts +2 -1
- package/types/config/IdentityConfig.d.ts.map +1 -1
- package/types/controllers/UserController.d.ts +7 -2
- package/types/controllers/UserController.d.ts.map +1 -1
- package/types/factory/UserApiKeyServiceFactory.d.ts +1 -1
- package/types/factory/UserRegistryServiceFactory.d.ts +4 -0
- package/types/factory/UserRegistryServiceFactory.d.ts.map +1 -0
- package/types/html/RegistrationCompleteHtml.d.ts +3 -0
- package/types/html/RegistrationCompleteHtml.d.ts.map +1 -0
- package/types/interfaces/IUserRepository.d.ts +4 -0
- package/types/interfaces/IUserRepository.d.ts.map +1 -1
- package/types/models/UserModel.d.ts.map +1 -1
- package/types/repository/mongo/UserApiKeyMongoRepository.d.ts +2 -2
- package/types/repository/mongo/UserApiKeyMongoRepository.d.ts.map +1 -1
- package/types/repository/mongo/UserMongoRepository.d.ts +5 -0
- package/types/repository/mongo/UserMongoRepository.d.ts.map +1 -1
- package/types/repository/sqlite/UserApiKeySqliteRepository.d.ts +1 -1
- package/types/repository/sqlite/UserSqliteRepository.d.ts +5 -0
- package/types/repository/sqlite/UserSqliteRepository.d.ts.map +1 -1
- package/types/routes/UserRoutes.d.ts.map +1 -1
- package/types/services/UserEmailService.d.ts +7 -0
- package/types/services/UserEmailService.d.ts.map +1 -0
- package/types/services/UserService.d.ts +6 -0
- package/types/services/UserService.d.ts.map +1 -1
|
@@ -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
|
|
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
|
|
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('
|
|
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
|
|
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/
|
|
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;
|
package/dist/models/UserModel.js
CHANGED
|
@@ -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
|
|
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
|
|
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/
|
|
13
|
-
fastify.
|
|
14
|
-
fastify.
|
|
15
|
-
fastify.
|
|
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 };
|