@drax/identity-back 0.29.0 → 0.31.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/TenantController.js +2 -1
- package/dist/controllers/UserController.js +26 -0
- package/dist/graphql/resolvers/user.resolvers.js +24 -7
- package/dist/graphql/types/user.graphql +4 -0
- package/dist/middleware/jwtMiddleware.js +1 -0
- package/dist/permissions/UserPermissions.js +1 -0
- package/dist/routes/UserRoutes.js +11 -0
- package/dist/schemas/SwitchTenantSchema.js +8 -0
- package/dist/services/UserService.js +4 -0
- package/dist/utils/AuthUtils.js +28 -0
- package/package.json +7 -7
- package/src/controllers/TenantController.ts +2 -3
- package/src/controllers/UserController.ts +28 -0
- package/src/graphql/resolvers/user.resolvers.ts +23 -7
- package/src/graphql/types/user.graphql +4 -0
- package/src/middleware/jwtMiddleware.ts +1 -0
- package/src/permissions/UserPermissions.ts +1 -1
- package/src/routes/UserRoutes.ts +14 -0
- package/src/schemas/SwitchTenantSchema.ts +11 -0
- package/src/services/UserService.ts +5 -0
- package/src/utils/AuthUtils.ts +36 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/types/controllers/TenantController.d.ts.map +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 +9 -0
- package/types/graphql/resolvers/user.resolvers.d.ts.map +1 -1
- package/types/middleware/jwtMiddleware.d.ts.map +1 -1
- package/types/permissions/UserPermissions.d.ts +2 -1
- package/types/permissions/UserPermissions.d.ts.map +1 -1
- package/types/permissions/index.d.ts +2 -0
- package/types/permissions/index.d.ts.map +1 -1
- package/types/routes/UserRoutes.d.ts.map +1 -1
- package/types/schemas/SwitchTenantSchema.d.ts +17 -0
- package/types/schemas/SwitchTenantSchema.d.ts.map +1 -0
- package/types/services/UserService.d.ts +3 -0
- package/types/services/UserService.d.ts.map +1 -1
- package/types/utils/AuthUtils.d.ts +1 -0
- 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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
@@ -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'],
|
|
@@ -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);
|
package/dist/utils/AuthUtils.js
CHANGED
|
@@ -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.
|
|
6
|
+
"version": "0.31.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.31.0",
|
|
32
|
+
"@drax/crud-back": "^0.31.0",
|
|
33
|
+
"@drax/crud-share": "^0.31.0",
|
|
34
|
+
"@drax/email-back": "^0.31.0",
|
|
35
|
+
"@drax/identity-share": "^0.31.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": "01589f8197801c0b8ad13a10d398e41d6cb2a25a"
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
package/src/routes/UserRoutes.ts
CHANGED
|
@@ -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)
|
package/src/utils/AuthUtils.ts
CHANGED
|
@@ -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
|