@drax/identity-back 0.29.0 → 0.30.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.
Files changed (39) hide show
  1. package/dist/controllers/TenantController.js +2 -1
  2. package/dist/controllers/UserController.js +26 -0
  3. package/dist/graphql/resolvers/user.resolvers.js +24 -7
  4. package/dist/graphql/types/user.graphql +4 -0
  5. package/dist/middleware/jwtMiddleware.js +1 -0
  6. package/dist/permissions/UserPermissions.js +1 -0
  7. package/dist/routes/UserRoutes.js +11 -0
  8. package/dist/schemas/SwitchTenantSchema.js +8 -0
  9. package/dist/services/UserService.js +4 -0
  10. package/dist/utils/AuthUtils.js +28 -0
  11. package/package.json +7 -7
  12. package/src/controllers/TenantController.ts +2 -3
  13. package/src/controllers/UserController.ts +28 -0
  14. package/src/graphql/resolvers/user.resolvers.ts +23 -7
  15. package/src/graphql/types/user.graphql +4 -0
  16. package/src/middleware/jwtMiddleware.ts +1 -0
  17. package/src/permissions/UserPermissions.ts +1 -1
  18. package/src/routes/UserRoutes.ts +14 -0
  19. package/src/schemas/SwitchTenantSchema.ts +11 -0
  20. package/src/services/UserService.ts +5 -0
  21. package/src/utils/AuthUtils.ts +36 -0
  22. package/tsconfig.tsbuildinfo +1 -1
  23. package/types/controllers/TenantController.d.ts.map +1 -1
  24. package/types/controllers/UserController.d.ts +3 -0
  25. package/types/controllers/UserController.d.ts.map +1 -1
  26. package/types/graphql/resolvers/user.resolvers.d.ts +9 -0
  27. package/types/graphql/resolvers/user.resolvers.d.ts.map +1 -1
  28. package/types/middleware/jwtMiddleware.d.ts.map +1 -1
  29. package/types/permissions/UserPermissions.d.ts +2 -1
  30. package/types/permissions/UserPermissions.d.ts.map +1 -1
  31. package/types/permissions/index.d.ts +2 -0
  32. package/types/permissions/index.d.ts.map +1 -1
  33. package/types/routes/UserRoutes.d.ts.map +1 -1
  34. package/types/schemas/SwitchTenantSchema.d.ts +17 -0
  35. package/types/schemas/SwitchTenantSchema.d.ts.map +1 -0
  36. package/types/services/UserService.d.ts +3 -0
  37. package/types/services/UserService.d.ts.map +1 -1
  38. package/types/utils/AuthUtils.d.ts +1 -0
  39. package/types/utils/AuthUtils.d.ts.map +1 -1
@@ -2,6 +2,7 @@ import { AbstractFastifyController } from "@drax/crud-back";
2
2
  import { ValidationError, UnauthorizedError } from "@drax/common-back";
3
3
  import TenantServiceFactory from "../factory/TenantServiceFactory.js";
4
4
  import TenantPermissions from "../permissions/TenantPermissions.js";
5
+ import UserPermissions from "../permissions/UserPermissions.js";
5
6
  class TenantController extends AbstractFastifyController {
6
7
  constructor() {
7
8
  super(TenantServiceFactory(), TenantPermissions);
@@ -10,7 +11,7 @@ class TenantController extends AbstractFastifyController {
10
11
  try {
11
12
  request.rbac.assertPermission(this.permission.View);
12
13
  let tenants = await this.service.fetchAll();
13
- if (request.rbac.getAuthUser.tenantId) {
14
+ if (request.rbac.getAuthUser.tenantId && !request.rbac.hasPermission(UserPermissions.SwitchTenant)) {
14
15
  return tenants.filter(t => t._id === request.rbac.getAuthUser.tenantId);
15
16
  }
16
17
  else {
@@ -8,6 +8,7 @@ import BadCredentialsError from "../errors/BadCredentialsError.js";
8
8
  import { join } from "path";
9
9
  import { IdentityConfig } from "../config/IdentityConfig.js";
10
10
  import UserEmailService from "../services/UserEmailService.js";
11
+ import TenantServiceFactory from "../factory/TenantServiceFactory.js";
11
12
  const BASE_FILE_DIR = DraxConfig.getOrLoad(CommonConfig.FileDir) || 'files';
12
13
  const AVATAR_DIR = DraxConfig.getOrLoad(IdentityConfig.AvatarDir) || 'avatar';
13
14
  const BASE_URL = DraxConfig.getOrLoad(CommonConfig.BaseUrl) ? DraxConfig.get(CommonConfig.BaseUrl).replace(/\/$/, '') : '';
@@ -39,6 +40,14 @@ class UserController extends AbstractFastifyController {
39
40
  let user = await userService.findById(request.rbac.userId);
40
41
  user.password = undefined;
41
42
  delete user.password;
43
+ //handle SwitchTenant setted in accessToken
44
+ if (request.authUser.tenantId != user?.tenant?._id) {
45
+ const tenantService = TenantServiceFactory();
46
+ const tenant = await tenantService.findById(request.authUser.tenantId);
47
+ if (tenant) {
48
+ user.tenant = tenant;
49
+ }
50
+ }
42
51
  return user;
43
52
  }
44
53
  else {
@@ -49,6 +58,23 @@ class UserController extends AbstractFastifyController {
49
58
  this.handleError(e, reply);
50
59
  }
51
60
  }
61
+ async switchTenant(request, reply) {
62
+ try {
63
+ request.rbac.assertPermission(UserPermissions.SwitchTenant);
64
+ if (request.authUser && request.token) {
65
+ const tenantId = request.body.tenantId;
66
+ const userService = UserServiceFactory();
67
+ let { accessToken } = await userService.switchTenant(request.token, tenantId);
68
+ return { accessToken };
69
+ }
70
+ else {
71
+ throw new UnauthorizedError();
72
+ }
73
+ }
74
+ catch (e) {
75
+ this.handleError(e, reply);
76
+ }
77
+ }
52
78
  async paginate(request, reply) {
53
79
  try {
54
80
  request.rbac.assertPermission(UserPermissions.View);
@@ -1,11 +1,11 @@
1
1
  import UserServiceFactory from "../../factory/UserServiceFactory.js";
2
2
  import { GraphQLError } from "graphql";
3
3
  import { ValidationErrorToGraphQLError, ValidationError, StoreManager, DraxConfig, CommonConfig } from "@drax/common-back";
4
- import { IdentityPermissions } from "../../permissions/IdentityPermissions.js";
5
4
  import { UnauthorizedError } from "@drax/common-back";
6
5
  import BadCredentialsError from "../../errors/BadCredentialsError.js";
7
6
  import { join } from "path";
8
7
  import IdentityConfig from "../../config/IdentityConfig.js";
8
+ import UserPermissions from "../../permissions/UserPermissions.js";
9
9
  export default {
10
10
  Query: {
11
11
  me: async (_, {}, { authUser }) => {
@@ -25,7 +25,7 @@ export default {
25
25
  },
26
26
  findUserById: async (_, { id }, { rbac }) => {
27
27
  try {
28
- rbac.assertPermission(IdentityPermissions.ViewUser);
28
+ rbac.assertPermission(UserPermissions.View);
29
29
  let userService = UserServiceFactory();
30
30
  let user = await userService.findById(id);
31
31
  user.password = undefined;
@@ -40,7 +40,7 @@ export default {
40
40
  },
41
41
  paginateUser: async (_, { options = { page: 1, limit: 5, orderBy: "", order: "asc", search: "", filters: [] } }, { rbac }) => {
42
42
  try {
43
- rbac.assertPermission(IdentityPermissions.ViewUser);
43
+ rbac.assertPermission(UserPermissions.View);
44
44
  let userService = UserServiceFactory();
45
45
  if (!options.filters) {
46
46
  options.filters = [];
@@ -72,9 +72,26 @@ export default {
72
72
  throw new GraphQLError('error.server');
73
73
  }
74
74
  },
75
+ switchTenant: async (_, { input }, { rbac, authUser, token }) => {
76
+ try {
77
+ rbac.assertPermission(UserPermissions.SwitchTenant);
78
+ if (authUser && token) {
79
+ const tenantId = input.tenantId;
80
+ const userService = UserServiceFactory();
81
+ let { accessToken } = await userService.switchTenant(token, tenantId);
82
+ return { accessToken };
83
+ }
84
+ else {
85
+ throw new UnauthorizedError();
86
+ }
87
+ }
88
+ catch (e) {
89
+ throw new GraphQLError('error.server');
90
+ }
91
+ },
75
92
  createUser: async (_, { input }, { rbac }) => {
76
93
  try {
77
- rbac.assertPermission(IdentityPermissions.CreateUser);
94
+ rbac.assertPermission(UserPermissions.Create);
78
95
  let userService = UserServiceFactory();
79
96
  if (rbac.getAuthUser.tenantId) {
80
97
  input.tenant = rbac.getAuthUser.tenantId;
@@ -95,7 +112,7 @@ export default {
95
112
  },
96
113
  updateUser: async (_, { id, input }, { rbac }) => {
97
114
  try {
98
- rbac.assertPermission(IdentityPermissions.UpdateUser);
115
+ rbac.assertPermission(UserPermissions.Update);
99
116
  let userService = UserServiceFactory();
100
117
  if (rbac.getAuthUser.tenantId) {
101
118
  input.tenant = rbac.getAuthUser.tenantId;
@@ -115,7 +132,7 @@ export default {
115
132
  },
116
133
  deleteUser: async (_, { id }, { rbac }) => {
117
134
  try {
118
- rbac.assertPermission(IdentityPermissions.DeleteUser);
135
+ rbac.assertPermission(UserPermissions.Delete);
119
136
  let userService = UserServiceFactory();
120
137
  return await userService.delete(id);
121
138
  }
@@ -151,7 +168,7 @@ export default {
151
168
  },
152
169
  changeUserPassword: async (_, { userId, newPassword }, { rbac }) => {
153
170
  try {
154
- rbac.assertPermission(IdentityPermissions.UpdateUser);
171
+ rbac.assertPermission(UserPermissions.Update);
155
172
  let userService = UserServiceFactory();
156
173
  return await userService.changeUserPassword(userId, newPassword);
157
174
  }
@@ -56,9 +56,13 @@ input AuthInput{
56
56
  password: String!
57
57
  }
58
58
 
59
+ input SwitchTenantInput{
60
+ tenantId: String!
61
+ }
59
62
 
60
63
  type Mutation{
61
64
  auth(input: AuthInput): Auth
65
+ switchTenant(input: SwitchTenantInput): Auth
62
66
  createUser(input: UserCreateInput): User
63
67
  updateUser(id: ID!, input: UserUpdateInput): User
64
68
  deleteUser(id: ID!): Boolean
@@ -10,6 +10,7 @@ function jwtMiddleware(request, reply, done) {
10
10
  const authUser = AuthUtils.verifyToken(token);
11
11
  if (authUser) {
12
12
  request.authUser = authUser;
13
+ request.token = token;
13
14
  }
14
15
  }
15
16
  done();
@@ -5,6 +5,7 @@ var UserPermissions;
5
5
  UserPermissions["Delete"] = "user:delete";
6
6
  UserPermissions["View"] = "user:view";
7
7
  UserPermissions["Manage"] = "user:manage";
8
+ UserPermissions["SwitchTenant"] = "user:switchTenant";
8
9
  })(UserPermissions || (UserPermissions = {}));
9
10
  export default UserPermissions;
10
11
  export { UserPermissions };
@@ -5,6 +5,7 @@ import { LoginBodyRequestSchema, LoginBodyResponseSchema } from "../schemas/Logi
5
5
  import { UserSchema, UserCreateSchema, UserUpdateSchema } from "../schemas/UserSchema.js";
6
6
  import { RegisterBodyRequestSchema, RegisterBodyResponseSchema } from "../schemas/RegisterSchema.js";
7
7
  import { MyPasswordBodyRequestSchema, PasswordBodyRequestSchema, PasswordBodyResponseSchema } from "../schemas/PasswordSchema.js";
8
+ import { SwitchTenantBodyRequestSchema, SwitchTenantBodyResponseSchema } from "../schemas/SwitchTenantSchema.js";
8
9
  async function UserRoutes(fastify, options) {
9
10
  const controller = new UserController();
10
11
  const schemas = new CrudSchemaBuilder(UserSchema, UserCreateSchema, UserUpdateSchema, 'tenant', 'openApi3', ['Identity']);
@@ -34,6 +35,16 @@ async function UserRoutes(fastify, options) {
34
35
  },
35
36
  },
36
37
  }, (req, rep) => controller.me(req, rep));
38
+ fastify.post('/api/auth/switch-tenant', {
39
+ schema: {
40
+ tags: ['Auth'],
41
+ body: zodToJsonSchema(SwitchTenantBodyRequestSchema),
42
+ response: {
43
+ 200: zodToJsonSchema(SwitchTenantBodyResponseSchema),
44
+ 400: schemas.jsonErrorBodyResponse,
45
+ },
46
+ },
47
+ }, (req, rep) => controller.switchTenant(req, rep));
37
48
  fastify.post('/api/users/register', {
38
49
  schema: {
39
50
  tags: ['Auth'],
@@ -0,0 +1,8 @@
1
+ import z from "zod";
2
+ const SwitchTenantBodyRequestSchema = z.object({
3
+ tenantId: z.string(),
4
+ });
5
+ const SwitchTenantBodyResponseSchema = z.object({
6
+ accessToken: z.string()
7
+ });
8
+ export { SwitchTenantBodyRequestSchema, SwitchTenantBodyResponseSchema };
@@ -25,6 +25,10 @@ class UserService extends AbstractService {
25
25
  throw new BadCredentialsError();
26
26
  }
27
27
  }
28
+ async switchTenant(accessToken, tenantId) {
29
+ const newAccessToken = AuthUtils.switchTenant(accessToken, tenantId);
30
+ return { accessToken: newAccessToken };
31
+ }
28
32
  async authByEmail(email, createIfNotFound = false, userData) {
29
33
  let user = null;
30
34
  console.log("auth email", email);
@@ -60,6 +60,34 @@ class AuthUtils {
60
60
  // Generar el hash en formato hexadecimal
61
61
  return hmac.digest('hex');
62
62
  }
63
+ static switchTenant(accessToken, newTenantId) {
64
+ // Verificar que el token actual sea válido
65
+ const decodedToken = AuthUtils.verifyToken(accessToken);
66
+ if (!decodedToken) {
67
+ throw new Error("Invalid access token");
68
+ }
69
+ // Crear el nuevo payload manteniendo todos los datos excepto tenantId
70
+ const newPayload = {
71
+ id: decodedToken.id,
72
+ username: decodedToken.username,
73
+ roleId: decodedToken.roleId,
74
+ tenantId: newTenantId,
75
+ session: decodedToken.session,
76
+ exp: decodedToken.exp
77
+ };
78
+ const JWT_SECRET = DraxConfig.getOrLoad(IdentityConfig.JwtSecret);
79
+ if (!JWT_SECRET) {
80
+ throw new Error("JWT_SECRET ENV must be provided");
81
+ }
82
+ const JWT_ISSUER = DraxConfig.getOrLoad(IdentityConfig.JwtIssuer) || 'DRAX';
83
+ const options = {
84
+ jwtid: decodedToken.id,
85
+ algorithm: 'HS256',
86
+ audience: decodedToken.username,
87
+ issuer: JWT_ISSUER
88
+ };
89
+ return jsonwebtoken.sign(newPayload, JWT_SECRET, options);
90
+ }
63
91
  }
64
92
  export default AuthUtils;
65
93
  export { AuthUtils };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.29.0",
6
+ "version": "0.30.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.29.0",
32
- "@drax/crud-back": "^0.29.0",
33
- "@drax/crud-share": "^0.29.0",
34
- "@drax/email-back": "^0.29.0",
35
- "@drax/identity-share": "^0.29.0",
31
+ "@drax/common-back": "^0.30.0",
32
+ "@drax/crud-back": "^0.30.0",
33
+ "@drax/crud-share": "^0.30.0",
34
+ "@drax/email-back": "^0.30.0",
35
+ "@drax/identity-share": "^0.30.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": "0a3f69beb23eb8768ebe7a3d41d61d4a6d40b104"
66
+ "gitHead": "f7f06578327be29f20dcb7e2c8a2eac9e9145cab"
67
67
  }
@@ -5,6 +5,7 @@ import {ValidationError, UnauthorizedError} from "@drax/common-back";
5
5
  import TenantServiceFactory from "../factory/TenantServiceFactory.js";
6
6
  import TenantService from "../services/TenantService.js";
7
7
  import TenantPermissions from "../permissions/TenantPermissions.js";
8
+ import UserPermissions from "../permissions/UserPermissions.js";
8
9
 
9
10
  class TenantController extends AbstractFastifyController<ITenant, ITenantBase, ITenantBase> {
10
11
 
@@ -14,13 +15,11 @@ class TenantController extends AbstractFastifyController<ITenant, ITenantBase, I
14
15
  super(TenantServiceFactory(), TenantPermissions)
15
16
  }
16
17
 
17
-
18
-
19
18
  async all(request, reply) {
20
19
  try {
21
20
  request.rbac.assertPermission(this.permission.View)
22
21
  let tenants = await this.service.fetchAll()
23
- if(request.rbac.getAuthUser.tenantId){
22
+ if(request.rbac.getAuthUser.tenantId && !request.rbac.hasPermission(UserPermissions.SwitchTenant)){
24
23
  return tenants.filter(t => t._id === request.rbac.getAuthUser.tenantId)
25
24
  }else{
26
25
  return tenants
@@ -18,6 +18,7 @@ import {join} from "path";
18
18
  import {IdentityConfig} from "../config/IdentityConfig.js";
19
19
  import UserEmailService from "../services/UserEmailService.js";
20
20
  import {IDraxFieldFilter} from "@drax/crud-share";
21
+ import TenantServiceFactory from "../factory/TenantServiceFactory.js";
21
22
 
22
23
  const BASE_FILE_DIR = DraxConfig.getOrLoad(CommonConfig.FileDir) || 'files';
23
24
  const AVATAR_DIR = DraxConfig.getOrLoad(IdentityConfig.AvatarDir) || 'avatar';
@@ -56,7 +57,18 @@ class UserController extends AbstractFastifyController<IUser, IUserCreate, IUser
56
57
  let user = await userService.findById(request.rbac.userId)
57
58
  user.password = undefined
58
59
  delete user.password
60
+
61
+ //handle SwitchTenant setted in accessToken
62
+ if(request.authUser.tenantId != user?.tenant?._id){
63
+ const tenantService = TenantServiceFactory()
64
+ const tenant = await tenantService.findById(request.authUser.tenantId)
65
+ if(tenant){
66
+ user.tenant = tenant
67
+ }
68
+ }
69
+
59
70
  return user
71
+
60
72
  } else {
61
73
  throw new UnauthorizedError()
62
74
 
@@ -66,6 +78,22 @@ class UserController extends AbstractFastifyController<IUser, IUserCreate, IUser
66
78
  }
67
79
  }
68
80
 
81
+ async switchTenant(request, reply) {
82
+ try {
83
+ request.rbac.assertPermission(UserPermissions.SwitchTenant)
84
+ if (request.authUser && request.token) {
85
+ const tenantId = request.body.tenantId
86
+ const userService = UserServiceFactory()
87
+ let {accessToken} = await userService.switchTenant(request.token, tenantId)
88
+ return {accessToken}
89
+ } else {
90
+ throw new UnauthorizedError()
91
+ }
92
+ } catch (e) {
93
+ this.handleError(e,reply)
94
+ }
95
+ }
96
+
69
97
  async paginate(request, reply) {
70
98
  try {
71
99
  request.rbac.assertPermission(UserPermissions.View)
@@ -7,12 +7,12 @@ import {
7
7
  DraxConfig,
8
8
  CommonConfig
9
9
  } from "@drax/common-back";
10
- import {IdentityPermissions} from "../../permissions/IdentityPermissions.js";
11
10
  import {UnauthorizedError} from "@drax/common-back";
12
11
  import BadCredentialsError from "../../errors/BadCredentialsError.js";
13
12
  import {join} from "path";
14
13
  import IdentityConfig from "../../config/IdentityConfig.js";
15
14
  import {IDraxPaginateOptions} from "@drax/crud-share";
15
+ import UserPermissions from "../../permissions/UserPermissions.js";
16
16
 
17
17
  export default {
18
18
  Query: {
@@ -33,7 +33,7 @@ export default {
33
33
  },
34
34
  findUserById: async (_, {id}, {rbac}) => {
35
35
  try {
36
- rbac.assertPermission(IdentityPermissions.ViewUser)
36
+ rbac.assertPermission(UserPermissions.View)
37
37
  let userService = UserServiceFactory()
38
38
  let user = await userService.findById(id)
39
39
  user.password = undefined
@@ -48,7 +48,7 @@ export default {
48
48
  },
49
49
  paginateUser: async (_, { options= {page:1, limit:5, orderBy:"", order:"asc", search:"", filters: []} as IDraxPaginateOptions }, {rbac}) => {
50
50
  try {
51
- rbac.assertPermission(IdentityPermissions.ViewUser)
51
+ rbac.assertPermission(UserPermissions.View)
52
52
  let userService = UserServiceFactory()
53
53
 
54
54
  if(!options.filters){
@@ -80,10 +80,26 @@ export default {
80
80
  throw new GraphQLError('error.server')
81
81
  }
82
82
 
83
+ },
84
+ switchTenant: async (_, {input},{rbac, authUser, token}) => {
85
+ try {
86
+ rbac.assertPermission(UserPermissions.SwitchTenant)
87
+ if (authUser && token) {
88
+ const tenantId = input.tenantId
89
+ const userService = UserServiceFactory()
90
+ let {accessToken} = await userService.switchTenant(token, tenantId)
91
+ return {accessToken}
92
+ } else {
93
+ throw new UnauthorizedError()
94
+ }
95
+ } catch (e) {
96
+ throw new GraphQLError('error.server')
97
+ }
98
+
83
99
  },
84
100
  createUser: async (_, {input}, {rbac}) => {
85
101
  try {
86
- rbac.assertPermission(IdentityPermissions.CreateUser)
102
+ rbac.assertPermission(UserPermissions.Create)
87
103
  let userService = UserServiceFactory()
88
104
  if (rbac.getAuthUser.tenantId) {
89
105
  input.tenant = rbac.getAuthUser.tenantId
@@ -103,7 +119,7 @@ export default {
103
119
  },
104
120
  updateUser: async (_, {id, input}, {rbac}) => {
105
121
  try {
106
- rbac.assertPermission(IdentityPermissions.UpdateUser)
122
+ rbac.assertPermission(UserPermissions.Update)
107
123
  let userService = UserServiceFactory()
108
124
  if (rbac.getAuthUser.tenantId) {
109
125
  input.tenant = rbac.getAuthUser.tenantId
@@ -121,7 +137,7 @@ export default {
121
137
  },
122
138
  deleteUser: async (_, {id}, {rbac}) => {
123
139
  try {
124
- rbac.assertPermission(IdentityPermissions.DeleteUser)
140
+ rbac.assertPermission(UserPermissions.Delete)
125
141
  let userService = UserServiceFactory()
126
142
  return await userService.delete(id)
127
143
  } catch (e) {
@@ -153,7 +169,7 @@ export default {
153
169
  },
154
170
  changeUserPassword: async (_, {userId, newPassword}, {rbac}) => {
155
171
  try {
156
- rbac.assertPermission(IdentityPermissions.UpdateUser)
172
+ rbac.assertPermission(UserPermissions.Update)
157
173
  let userService = UserServiceFactory()
158
174
  return await userService.changeUserPassword(userId, newPassword)
159
175
  } catch (e) {
@@ -56,9 +56,13 @@ input AuthInput{
56
56
  password: String!
57
57
  }
58
58
 
59
+ input SwitchTenantInput{
60
+ tenantId: String!
61
+ }
59
62
 
60
63
  type Mutation{
61
64
  auth(input: AuthInput): Auth
65
+ switchTenant(input: SwitchTenantInput): Auth
62
66
  createUser(input: UserCreateInput): User
63
67
  updateUser(id: ID!, input: UserUpdateInput): User
64
68
  deleteUser(id: ID!): Boolean
@@ -15,6 +15,7 @@ function jwtMiddleware (request, reply, done) {
15
15
  const authUser = AuthUtils.verifyToken(token) as IJwtUser
16
16
  if(authUser){
17
17
  request.authUser = authUser
18
+ request.token = token
18
19
  }
19
20
  }
20
21
  done()
@@ -4,7 +4,7 @@ enum UserPermissions {
4
4
  Delete = "user:delete",
5
5
  View = "user:view",
6
6
  Manage = "user:manage",
7
-
7
+ SwitchTenant = "user:switchTenant",
8
8
  }
9
9
 
10
10
  export default UserPermissions;
@@ -9,6 +9,7 @@ import {
9
9
  PasswordBodyRequestSchema,
10
10
  PasswordBodyResponseSchema
11
11
  } from "../schemas/PasswordSchema.js";
12
+ import {SwitchTenantBodyRequestSchema, SwitchTenantBodyResponseSchema} from "../schemas/SwitchTenantSchema.js";
12
13
 
13
14
  async function UserRoutes(fastify, options) {
14
15
 
@@ -52,6 +53,19 @@ async function UserRoutes(fastify, options) {
52
53
  },
53
54
  }, (req, rep) => controller.me(req, rep))
54
55
 
56
+ fastify.post('/api/auth/switch-tenant',
57
+ {
58
+ schema: {
59
+ tags: ['Auth'],
60
+ body: zodToJsonSchema(SwitchTenantBodyRequestSchema),
61
+ response: {
62
+ 200: zodToJsonSchema(SwitchTenantBodyResponseSchema),
63
+ 400: schemas.jsonErrorBodyResponse,
64
+ },
65
+ },
66
+ },
67
+ (req, rep) => controller.switchTenant(req, rep))
68
+
55
69
 
56
70
  fastify.post('/api/users/register', {
57
71
  schema: {
@@ -0,0 +1,11 @@
1
+ import z from "zod"
2
+
3
+ const SwitchTenantBodyRequestSchema = z.object({
4
+ tenantId: z.string(),
5
+ });
6
+
7
+ const SwitchTenantBodyResponseSchema = z.object({
8
+ accessToken: z.string()
9
+ });
10
+
11
+ export {SwitchTenantBodyRequestSchema, SwitchTenantBodyResponseSchema}
@@ -34,6 +34,11 @@ class UserService extends AbstractService<IUser, IUserCreate, IUserUpdate> {
34
34
  }
35
35
  }
36
36
 
37
+ async switchTenant(accessToken: string, tenantId: string) {
38
+ const newAccessToken = AuthUtils.switchTenant(accessToken, tenantId)
39
+ return {accessToken: newAccessToken}
40
+ }
41
+
37
42
  async authByEmail(email: string, createIfNotFound: boolean = false, userData: IUserCreate) {
38
43
  let user = null
39
44
  console.log("auth email", email)
@@ -78,6 +78,42 @@ class AuthUtils{
78
78
  // Generar el hash en formato hexadecimal
79
79
  return hmac.digest('hex');
80
80
  }
81
+
82
+ static switchTenant(accessToken: string, newTenantId: string): string {
83
+ // Verificar que el token actual sea válido
84
+ const decodedToken = AuthUtils.verifyToken(accessToken) as IJwtUser & { exp?: number };
85
+
86
+ if (!decodedToken) {
87
+ throw new Error("Invalid access token");
88
+ }
89
+
90
+ // Crear el nuevo payload manteniendo todos los datos excepto tenantId
91
+ const newPayload: IJwtUser = {
92
+ id: decodedToken.id,
93
+ username: decodedToken.username,
94
+ roleId: decodedToken.roleId,
95
+ tenantId: newTenantId,
96
+ session: decodedToken.session,
97
+ exp: decodedToken.exp
98
+ };
99
+
100
+ const JWT_SECRET = DraxConfig.getOrLoad(IdentityConfig.JwtSecret);
101
+ if (!JWT_SECRET) {
102
+ throw new Error("JWT_SECRET ENV must be provided");
103
+ }
104
+
105
+ const JWT_ISSUER = DraxConfig.getOrLoad(IdentityConfig.JwtIssuer) || 'DRAX';
106
+
107
+ const options: SignOptions = {
108
+ jwtid: decodedToken.id,
109
+ algorithm: 'HS256',
110
+ audience: decodedToken.username,
111
+ issuer: JWT_ISSUER
112
+ };
113
+
114
+
115
+ return jsonwebtoken.sign(newPayload, JWT_SECRET, options);
116
+ }
81
117
  }
82
118
 
83
119
  export default AuthUtils