@drax/identity-back 0.38.1 → 0.40.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/controllers/UserController.js +86 -8
- package/dist/graphql/resolvers/user.resolvers.js +8 -2
- package/dist/rbac/Rbac.js +9 -9
- package/dist/services/UserService.js +10 -9
- package/dist/utils/AuthUtils.js +3 -6
- package/package.json +7 -7
- package/src/controllers/UserController.ts +128 -38
- package/src/graphql/resolvers/user.resolvers.ts +9 -2
- package/src/rbac/Rbac.ts +9 -9
- package/src/services/UserService.ts +14 -13
- package/src/utils/AuthUtils.ts +5 -7
- package/tsconfig.tsbuildinfo +1 -1
- package/types/controllers/UserController.d.ts +3 -0
- package/types/controllers/UserController.d.ts.map +1 -1
- package/types/graphql/resolvers/user.resolvers.d.ts +5 -3
- package/types/graphql/resolvers/user.resolvers.d.ts.map +1 -1
- package/types/services/UserService.d.ts +7 -5
- package/types/services/UserService.d.ts.map +1 -1
- package/types/utils/AuthUtils.d.ts +1 -1
- package/types/utils/AuthUtils.d.ts.map +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AbstractFastifyController } from "@drax/crud-back";
|
|
2
2
|
import RegistrationCompleteHtml from "../html/RegistrationCompleteHtml.js";
|
|
3
|
-
import { CommonConfig, DraxConfig, StoreManager, ValidationError, UnauthorizedError, SecuritySensitiveError, } from "@drax/common-back";
|
|
3
|
+
import { CommonConfig, DraxConfig, StoreManager, ValidationError, UnauthorizedError, SecuritySensitiveError, BadRequestError, } from "@drax/common-back";
|
|
4
4
|
import UserServiceFactory from "../factory/UserServiceFactory.js";
|
|
5
5
|
import RoleServiceFactory from "../factory/RoleServiceFactory.js";
|
|
6
6
|
import UserPermissions from "../permissions/UserPermissions.js";
|
|
@@ -17,6 +17,39 @@ class UserController extends AbstractFastifyController {
|
|
|
17
17
|
super(UserServiceFactory(), UserPermissions);
|
|
18
18
|
this.tenantField = "tenant";
|
|
19
19
|
this.tenantFilter = true;
|
|
20
|
+
this.entityName = 'User';
|
|
21
|
+
}
|
|
22
|
+
async onUserLoggedIn(request, user, session) {
|
|
23
|
+
const eventData = {
|
|
24
|
+
action: 'loggedIn',
|
|
25
|
+
entity: this.entityName,
|
|
26
|
+
resourceId: user._id.toString(),
|
|
27
|
+
postItem: null,
|
|
28
|
+
preItem: null,
|
|
29
|
+
detail: `User ${user.username} logged in.`,
|
|
30
|
+
timestamp: new Date(),
|
|
31
|
+
ip: request.ip,
|
|
32
|
+
userAgent: request.headers['user-agent'],
|
|
33
|
+
requestId: request.id,
|
|
34
|
+
user: {
|
|
35
|
+
id: user._id.toString(),
|
|
36
|
+
username: user.username,
|
|
37
|
+
role: {
|
|
38
|
+
id: user?.role?._id.toString(),
|
|
39
|
+
name: user?.role?.name,
|
|
40
|
+
},
|
|
41
|
+
tenant: {
|
|
42
|
+
id: user?.tenant?._id.toString(),
|
|
43
|
+
name: user?.tenant?.name,
|
|
44
|
+
},
|
|
45
|
+
apiKey: {
|
|
46
|
+
id: null,
|
|
47
|
+
name: null,
|
|
48
|
+
},
|
|
49
|
+
session: session,
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
this.eventEmitter.emitCrudEvent(eventData);
|
|
20
53
|
}
|
|
21
54
|
async auth(request, reply) {
|
|
22
55
|
try {
|
|
@@ -25,7 +58,9 @@ class UserController extends AbstractFastifyController {
|
|
|
25
58
|
const userAgent = request.headers['user-agent'];
|
|
26
59
|
const ip = request.ip;
|
|
27
60
|
const userService = UserServiceFactory();
|
|
28
|
-
|
|
61
|
+
const { user, accessToken, session } = await userService.auth(username, password, { userAgent, ip });
|
|
62
|
+
this.onUserLoggedIn(request, user, session);
|
|
63
|
+
return { accessToken };
|
|
29
64
|
}
|
|
30
65
|
catch (e) {
|
|
31
66
|
console.error('/api/auth error', e);
|
|
@@ -62,13 +97,38 @@ class UserController extends AbstractFastifyController {
|
|
|
62
97
|
this.handleError(e, reply);
|
|
63
98
|
}
|
|
64
99
|
}
|
|
100
|
+
async onUserEvent(request, action, resourceId = null, detail = null) {
|
|
101
|
+
const requestData = this.extractRequestData(request);
|
|
102
|
+
const eventData = {
|
|
103
|
+
action: action,
|
|
104
|
+
entity: this.entityName,
|
|
105
|
+
resourceId: resourceId.toString(),
|
|
106
|
+
postItem: null,
|
|
107
|
+
preItem: null,
|
|
108
|
+
detail: detail,
|
|
109
|
+
timestamp: new Date(),
|
|
110
|
+
...requestData
|
|
111
|
+
};
|
|
112
|
+
this.eventEmitter.emitCrudEvent(eventData);
|
|
113
|
+
}
|
|
65
114
|
async switchTenant(request, reply) {
|
|
66
115
|
try {
|
|
67
116
|
request.rbac.assertPermission(UserPermissions.SwitchTenant);
|
|
68
117
|
if (request.authUser && request.token) {
|
|
69
118
|
const tenantId = request.body.tenantId;
|
|
119
|
+
if (!tenantId) {
|
|
120
|
+
throw new BadRequestError('Missing tenantId');
|
|
121
|
+
}
|
|
122
|
+
const tenant = await TenantServiceFactory().findById(tenantId);
|
|
123
|
+
if (!tenant) {
|
|
124
|
+
throw new BadRequestError('Invalid tenantId');
|
|
125
|
+
}
|
|
126
|
+
const tenantName = tenant?.name;
|
|
70
127
|
const userService = UserServiceFactory();
|
|
71
|
-
let { accessToken } = await userService.switchTenant(request.token, tenantId);
|
|
128
|
+
let { accessToken } = await userService.switchTenant(request.token, tenantId, tenantName);
|
|
129
|
+
const username = request.rbac.username;
|
|
130
|
+
const detail = `User ${username} switched to tenant "${tenantName}" (ID: ${tenantId})`;
|
|
131
|
+
this.onUserEvent(request, 'switchTenant', request.rbac.userId, detail);
|
|
72
132
|
return { accessToken };
|
|
73
133
|
}
|
|
74
134
|
else {
|
|
@@ -141,6 +201,8 @@ class UserController extends AbstractFastifyController {
|
|
|
141
201
|
const userService = UserServiceFactory();
|
|
142
202
|
let user = await userService.register(payload);
|
|
143
203
|
if (user) {
|
|
204
|
+
const detail = `User ${user?.username} registered successfully.`;
|
|
205
|
+
this.onUserEvent(request, 'register', user?._id, detail);
|
|
144
206
|
//SEND EMAIL FOR EMAIL VERIFICATION
|
|
145
207
|
await UserEmailService.emailVerifyCode(user.emailCode, user.email);
|
|
146
208
|
return {
|
|
@@ -187,6 +249,7 @@ class UserController extends AbstractFastifyController {
|
|
|
187
249
|
payload.origin ?? (payload.origin = 'Admin');
|
|
188
250
|
const userService = UserServiceFactory();
|
|
189
251
|
let user = await userService.create(payload);
|
|
252
|
+
this.onCreated(request, user);
|
|
190
253
|
return user;
|
|
191
254
|
}
|
|
192
255
|
catch (e) {
|
|
@@ -202,7 +265,9 @@ class UserController extends AbstractFastifyController {
|
|
|
202
265
|
payload.tenant = request.rbac.getAuthUser.tenantId;
|
|
203
266
|
}
|
|
204
267
|
const userService = UserServiceFactory();
|
|
268
|
+
let preUser = await userService.findById(id);
|
|
205
269
|
let user = await userService.update(id, payload);
|
|
270
|
+
this.onUpdated(request, preUser, user);
|
|
206
271
|
return user;
|
|
207
272
|
}
|
|
208
273
|
catch (e) {
|
|
@@ -214,8 +279,10 @@ class UserController extends AbstractFastifyController {
|
|
|
214
279
|
request.rbac.assertPermission(UserPermissions.Delete);
|
|
215
280
|
const id = request.params.id;
|
|
216
281
|
const userService = UserServiceFactory();
|
|
282
|
+
let preUser = await userService.findById(id);
|
|
217
283
|
let r = await userService.delete(id);
|
|
218
284
|
if (r) {
|
|
285
|
+
this.onDeleted(request, preUser);
|
|
219
286
|
reply.send({
|
|
220
287
|
id: id,
|
|
221
288
|
message: 'Item deleted successfully',
|
|
@@ -246,11 +313,14 @@ class UserController extends AbstractFastifyController {
|
|
|
246
313
|
throw new ValidationError([{ field: 'email', reason: 'validation.email.invalid' }]);
|
|
247
314
|
}
|
|
248
315
|
const userService = UserServiceFactory();
|
|
316
|
+
const user = await userService.findByEmail(email);
|
|
249
317
|
const code = await userService.recoveryCode(email);
|
|
250
318
|
console.log("CODE", code);
|
|
251
319
|
if (code) {
|
|
252
320
|
await UserEmailService.recoveryCode(code, email);
|
|
253
321
|
}
|
|
322
|
+
const detail = `User ${user?.username} request a password recovery .`;
|
|
323
|
+
this.onUserEvent(request, 'passwordRecoveryRequest', user?._id, detail);
|
|
254
324
|
reply.send({ message });
|
|
255
325
|
}
|
|
256
326
|
catch (e) {
|
|
@@ -274,8 +344,10 @@ class UserController extends AbstractFastifyController {
|
|
|
274
344
|
throw new ValidationError([{ field: 'newPassword', reason: 'validation.required' }]);
|
|
275
345
|
}
|
|
276
346
|
const userService = UserServiceFactory();
|
|
277
|
-
const
|
|
278
|
-
if (
|
|
347
|
+
const user = await userService.changeUserPasswordByCode(recoveryCode, newPassword);
|
|
348
|
+
if (user) {
|
|
349
|
+
const detail = `User ${user?.username} complete a password recovery .`;
|
|
350
|
+
this.onUserEvent(request, 'passwordRecoveryCompleted', user?._id, detail);
|
|
279
351
|
reply.send({ message: 'action.success' });
|
|
280
352
|
}
|
|
281
353
|
else {
|
|
@@ -296,7 +368,9 @@ class UserController extends AbstractFastifyController {
|
|
|
296
368
|
const currentPassword = request.body.currentPassword;
|
|
297
369
|
const newPassword = request.body.newPassword;
|
|
298
370
|
const userService = UserServiceFactory();
|
|
299
|
-
await userService.changeOwnPassword(userId, currentPassword, newPassword);
|
|
371
|
+
const user = await userService.changeOwnPassword(userId, currentPassword, newPassword);
|
|
372
|
+
const detail = `User ${user?.username} changed his password.`;
|
|
373
|
+
this.onUserEvent(request, 'changeMyPassword', user?._id, detail);
|
|
300
374
|
return { message: 'Password updated successfully' };
|
|
301
375
|
}
|
|
302
376
|
catch (e) {
|
|
@@ -312,7 +386,9 @@ class UserController extends AbstractFastifyController {
|
|
|
312
386
|
}
|
|
313
387
|
const newPassword = request.body.newPassword;
|
|
314
388
|
const userService = UserServiceFactory();
|
|
315
|
-
await userService.changeUserPassword(userId, newPassword);
|
|
389
|
+
const user = await userService.changeUserPassword(userId, newPassword);
|
|
390
|
+
const detail = `User ${request.rbac.username} changed password for user ${user.username}.`;
|
|
391
|
+
this.onUserEvent(request, 'changePassword', user?._id, detail);
|
|
316
392
|
return { message: 'Password updated successfully' };
|
|
317
393
|
}
|
|
318
394
|
catch (e) {
|
|
@@ -334,7 +410,9 @@ class UserController extends AbstractFastifyController {
|
|
|
334
410
|
const urlFile = BASE_URL + '/api/users/avatar/' + storedFile.filename;
|
|
335
411
|
//Save into DB
|
|
336
412
|
const userService = UserServiceFactory();
|
|
337
|
-
await userService.changeAvatar(userId, urlFile);
|
|
413
|
+
const user = await userService.changeAvatar(userId, urlFile);
|
|
414
|
+
const detail = `User ${request.rbac.username} changed avatar.`;
|
|
415
|
+
this.onUserEvent(request, 'changeAvatar', user?._id, detail);
|
|
338
416
|
return {
|
|
339
417
|
filename: storedFile.filename,
|
|
340
418
|
size: storedFile.size,
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import UserServiceFactory from "../../factory/UserServiceFactory.js";
|
|
2
2
|
import { GraphQLError } from "graphql";
|
|
3
|
-
import { ValidationErrorToGraphQLError, ValidationError, StoreManager, DraxConfig, CommonConfig } from "@drax/common-back";
|
|
3
|
+
import { ValidationErrorToGraphQLError, ValidationError, StoreManager, DraxConfig, CommonConfig, BadRequestError } from "@drax/common-back";
|
|
4
4
|
import { UnauthorizedError } from "@drax/common-back";
|
|
5
5
|
import BadCredentialsError from "../../errors/BadCredentialsError.js";
|
|
6
6
|
import { join } from "path";
|
|
7
7
|
import IdentityConfig from "../../config/IdentityConfig.js";
|
|
8
8
|
import UserPermissions from "../../permissions/UserPermissions.js";
|
|
9
|
+
import TenantServiceFactory from "../../factory/TenantServiceFactory.js";
|
|
9
10
|
export default {
|
|
10
11
|
Query: {
|
|
11
12
|
me: async (_, {}, { authUser }) => {
|
|
@@ -79,8 +80,13 @@ export default {
|
|
|
79
80
|
rbac.assertPermission(UserPermissions.SwitchTenant);
|
|
80
81
|
if (authUser && token) {
|
|
81
82
|
const tenantId = input.tenantId;
|
|
83
|
+
const tenant = await TenantServiceFactory().findById(tenantId);
|
|
84
|
+
if (!tenant) {
|
|
85
|
+
throw new BadRequestError('Invalid tenantId');
|
|
86
|
+
}
|
|
87
|
+
const tenantName = tenant?.name;
|
|
82
88
|
const userService = UserServiceFactory();
|
|
83
|
-
let { accessToken } = await userService.switchTenant(token, tenantId);
|
|
89
|
+
let { accessToken } = await userService.switchTenant(token, tenantId, tenantName);
|
|
84
90
|
return { accessToken };
|
|
85
91
|
}
|
|
86
92
|
else {
|
package/dist/rbac/Rbac.js
CHANGED
|
@@ -21,31 +21,31 @@ class Rbac {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
get username() {
|
|
24
|
-
return this
|
|
24
|
+
return this?.authUser?.username;
|
|
25
25
|
}
|
|
26
26
|
get userId() {
|
|
27
|
-
return this
|
|
27
|
+
return this?.authUser?.id.toString();
|
|
28
28
|
}
|
|
29
29
|
get session() {
|
|
30
|
-
return this
|
|
30
|
+
return this?.authUser?.session;
|
|
31
31
|
}
|
|
32
32
|
get apiKeyId() {
|
|
33
|
-
return this
|
|
33
|
+
return this?.authUser?.apiKeyId?.toString();
|
|
34
34
|
}
|
|
35
35
|
get apiKeyName() {
|
|
36
|
-
return this
|
|
36
|
+
return this?.authUser?.apiKeyName;
|
|
37
37
|
}
|
|
38
38
|
get roleId() {
|
|
39
|
-
return this
|
|
39
|
+
return this?.authUser?.roleId?.toString();
|
|
40
40
|
}
|
|
41
41
|
get roleName() {
|
|
42
|
-
return this
|
|
42
|
+
return this?.authUser?.roleName;
|
|
43
43
|
}
|
|
44
44
|
get tenantId() {
|
|
45
|
-
return this
|
|
45
|
+
return this?.authUser?.tenantId?.toString();
|
|
46
46
|
}
|
|
47
47
|
get tenantName() {
|
|
48
|
-
return this
|
|
48
|
+
return this?.authUser?.tenantName ?? undefined;
|
|
49
49
|
}
|
|
50
50
|
assertAuthenticated() {
|
|
51
51
|
if (!this.authUser) {
|
|
@@ -28,7 +28,8 @@ class UserService extends AbstractService {
|
|
|
28
28
|
session: sessionUUID
|
|
29
29
|
};
|
|
30
30
|
const accessToken = AuthUtils.generateToken(tokenPayload);
|
|
31
|
-
|
|
31
|
+
delete user.password;
|
|
32
|
+
return { accessToken: accessToken, user: user, session: sessionUUID };
|
|
32
33
|
}
|
|
33
34
|
else {
|
|
34
35
|
const userLoginFailService = UserLoginFailServiceFactory();
|
|
@@ -51,8 +52,8 @@ class UserService extends AbstractService {
|
|
|
51
52
|
});
|
|
52
53
|
return sessionUUID;
|
|
53
54
|
}
|
|
54
|
-
async switchTenant(accessToken, tenantId) {
|
|
55
|
-
const newAccessToken = AuthUtils.switchTenant(accessToken, tenantId);
|
|
55
|
+
async switchTenant(accessToken, tenantId, tenantName) {
|
|
56
|
+
const newAccessToken = AuthUtils.switchTenant(accessToken, tenantId, tenantName);
|
|
56
57
|
return { accessToken: newAccessToken };
|
|
57
58
|
}
|
|
58
59
|
async authByEmail(email, createIfNotFound = false, userData, { userAgent, ip }) {
|
|
@@ -87,7 +88,8 @@ class UserService extends AbstractService {
|
|
|
87
88
|
if (user) {
|
|
88
89
|
newPassword = AuthUtils.hashPassword(newPassword);
|
|
89
90
|
await this._repository.changePassword(userId, newPassword);
|
|
90
|
-
|
|
91
|
+
delete user.password;
|
|
92
|
+
return user;
|
|
91
93
|
}
|
|
92
94
|
else {
|
|
93
95
|
throw new ValidationError([{ field: 'userId', reason: 'validation.notFound' }]);
|
|
@@ -102,7 +104,8 @@ class UserService extends AbstractService {
|
|
|
102
104
|
if (AuthUtils.checkPassword(currentPassword, user.password)) {
|
|
103
105
|
newPassword = AuthUtils.hashPassword(newPassword);
|
|
104
106
|
await this._repository.changePassword(userId, newPassword);
|
|
105
|
-
|
|
107
|
+
delete user.password;
|
|
108
|
+
return user;
|
|
106
109
|
}
|
|
107
110
|
else {
|
|
108
111
|
throw new ValidationError([{ field: 'currentPassword', reason: 'validation.notMatch' }]);
|
|
@@ -116,7 +119,7 @@ class UserService extends AbstractService {
|
|
|
116
119
|
const user = await this.findById(userId);
|
|
117
120
|
if (user && user.active) {
|
|
118
121
|
await this._repository.changeAvatar(userId, avatar);
|
|
119
|
-
return
|
|
122
|
+
return user;
|
|
120
123
|
}
|
|
121
124
|
else {
|
|
122
125
|
throw new BadCredentialsError();
|
|
@@ -141,14 +144,12 @@ class UserService extends AbstractService {
|
|
|
141
144
|
}
|
|
142
145
|
async changeUserPasswordByCode(recoveryCode, newPassword) {
|
|
143
146
|
try {
|
|
144
|
-
console.log("changeUserPasswordByCode recoveryCode", recoveryCode);
|
|
145
147
|
const user = await this._repository.findByRecoveryCode(recoveryCode);
|
|
146
|
-
console.log("changeUserPasswordByCode user", user);
|
|
147
148
|
if (user && user.active) {
|
|
148
149
|
newPassword = AuthUtils.hashPassword(newPassword);
|
|
149
150
|
await this._repository.changePassword(user._id, newPassword);
|
|
150
151
|
await this._repository.updatePartial(user._id, { recoveryCode: null });
|
|
151
|
-
return
|
|
152
|
+
return user;
|
|
152
153
|
}
|
|
153
154
|
else {
|
|
154
155
|
throw new ValidationError([{ field: 'recoveryCode', reason: 'validation.notFound' }]);
|
package/dist/utils/AuthUtils.js
CHANGED
|
@@ -54,23 +54,20 @@ class AuthUtils {
|
|
|
54
54
|
// Generar el hash en formato hexadecimal
|
|
55
55
|
return hmac.digest('hex');
|
|
56
56
|
}
|
|
57
|
-
static switchTenant(accessToken, newTenantId) {
|
|
57
|
+
static switchTenant(accessToken, newTenantId, tenantName) {
|
|
58
58
|
// Verificar que el token actual sea válido
|
|
59
59
|
const tokenPayload = AuthUtils.verifyToken(accessToken);
|
|
60
60
|
if (!tokenPayload) {
|
|
61
61
|
throw new Error("Invalid access token");
|
|
62
62
|
}
|
|
63
63
|
tokenPayload.tenantId = newTenantId;
|
|
64
|
+
tokenPayload.tenantName = tenantName;
|
|
64
65
|
const JWT_SECRET = DraxConfig.getOrLoad(IdentityConfig.JwtSecret);
|
|
65
66
|
if (!JWT_SECRET) {
|
|
66
67
|
throw new Error("JWT_SECRET ENV must be provided");
|
|
67
68
|
}
|
|
68
|
-
const JWT_ISSUER = DraxConfig.getOrLoad(IdentityConfig.JwtIssuer) || 'DRAX';
|
|
69
69
|
const options = {
|
|
70
|
-
|
|
71
|
-
algorithm: 'HS256',
|
|
72
|
-
audience: tokenPayload.username,
|
|
73
|
-
issuer: JWT_ISSUER
|
|
70
|
+
algorithm: 'HS256'
|
|
74
71
|
};
|
|
75
72
|
return jsonwebtoken.sign(tokenPayload, JWT_SECRET, options);
|
|
76
73
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.40.0",
|
|
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,11 +28,11 @@
|
|
|
28
28
|
"author": "Cristian Incarnato & Drax Team",
|
|
29
29
|
"license": "ISC",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@drax/common-back": "^0.
|
|
32
|
-
"@drax/crud-back": "^0.
|
|
33
|
-
"@drax/crud-share": "^0.
|
|
34
|
-
"@drax/email-back": "^0.
|
|
35
|
-
"@drax/identity-share": "^0.
|
|
31
|
+
"@drax/common-back": "^0.40.0",
|
|
32
|
+
"@drax/crud-back": "^0.40.0",
|
|
33
|
+
"@drax/crud-share": "^0.40.0",
|
|
34
|
+
"@drax/email-back": "^0.40.0",
|
|
35
|
+
"@drax/identity-share": "^0.40.0",
|
|
36
36
|
"bcryptjs": "^2.4.3",
|
|
37
37
|
"graphql": "^16.8.2",
|
|
38
38
|
"jsonwebtoken": "^9.0.2"
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"debug": "0"
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "269ec7921d83364dfba091cd74bd3f45a8135c26"
|
|
67
67
|
}
|