@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.
@@ -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
- return await userService.auth(username, password, { userAgent, ip });
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 result = await userService.changeUserPasswordByCode(recoveryCode, newPassword);
278
- if (result) {
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.authUser.username;
24
+ return this?.authUser?.username;
25
25
  }
26
26
  get userId() {
27
- return this.authUser?.id.toString();
27
+ return this?.authUser?.id.toString();
28
28
  }
29
29
  get session() {
30
- return this.authUser?.session;
30
+ return this?.authUser?.session;
31
31
  }
32
32
  get apiKeyId() {
33
- return this.authUser?.apiKeyId?.toString();
33
+ return this?.authUser?.apiKeyId?.toString();
34
34
  }
35
35
  get apiKeyName() {
36
- return this.authUser?.apiKeyName;
36
+ return this?.authUser?.apiKeyName;
37
37
  }
38
38
  get roleId() {
39
- return this.authUser?.roleId?.toString();
39
+ return this?.authUser?.roleId?.toString();
40
40
  }
41
41
  get roleName() {
42
- return this.authUser?.roleName;
42
+ return this?.authUser?.roleName;
43
43
  }
44
44
  get tenantId() {
45
- return this.authUser?.tenantId?.toString();
45
+ return this?.authUser?.tenantId?.toString();
46
46
  }
47
47
  get tenantName() {
48
- return this.authUser?.tenantName;
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
- return { accessToken: accessToken };
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
- return true;
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
- return true;
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 true;
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 true;
152
+ return user;
152
153
  }
153
154
  else {
154
155
  throw new ValidationError([{ field: 'recoveryCode', reason: 'validation.notFound' }]);
@@ -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
- jwtid: tokenPayload.id,
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.38.1",
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.38.0",
32
- "@drax/crud-back": "^0.38.1",
33
- "@drax/crud-share": "^0.38.0",
34
- "@drax/email-back": "^0.38.0",
35
- "@drax/identity-share": "^0.38.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": "33404c9f1b3df6a3f86624389916d1cae0c56904"
66
+ "gitHead": "269ec7921d83364dfba091cd74bd3f45a8135c26"
67
67
  }