@drax/identity-back 0.37.5 → 0.38.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/middleware/apiKeyMiddleware.js +5 -1
- package/dist/rbac/Rbac.js +21 -1
- package/dist/schemas/TokenPayloadSchema.js +14 -0
- package/dist/services/UserService.js +34 -14
- package/dist/utils/AuthUtils.js +14 -28
- package/package.json +7 -7
- package/src/middleware/apiKeyMiddleware.ts +6 -2
- package/src/middleware/jwtMiddleware.ts +2 -2
- package/src/middleware/rbacMiddleware.ts +6 -6
- package/src/rbac/Rbac.ts +28 -4
- package/src/schemas/TokenPayloadSchema.ts +23 -0
- package/src/services/UserService.ts +42 -15
- package/src/utils/AuthUtils.ts +17 -31
- package/tsconfig.tsbuildinfo +1 -1
- package/types/middleware/apiKeyMiddleware.d.ts.map +1 -1
- package/types/rbac/Rbac.d.ts +7 -3
- package/types/rbac/Rbac.d.ts.map +1 -1
- package/types/schemas/TokenPayloadSchema.d.ts +35 -0
- package/types/schemas/TokenPayloadSchema.d.ts.map +1 -0
- package/types/services/UserService.d.ts +5 -1
- package/types/services/UserService.d.ts.map +1 -1
- package/types/utils/AuthUtils.d.ts +3 -4
- package/types/utils/AuthUtils.d.ts.map +1 -1
|
@@ -21,7 +21,7 @@ async function apiKeyMiddleware(request, reply) {
|
|
|
21
21
|
apiKey = request.headers?.authorization?.replace(/ApiKey /i, "");
|
|
22
22
|
}
|
|
23
23
|
//Por authorization '<uuid-key>'
|
|
24
|
-
const uuidRegex = /^[0-9a-
|
|
24
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
25
25
|
if (request.headers['authorization'] && uuidRegex.test(request.headers['authorization'])) {
|
|
26
26
|
apiKey = request.headers['authorization'];
|
|
27
27
|
}
|
|
@@ -32,7 +32,11 @@ async function apiKeyMiddleware(request, reply) {
|
|
|
32
32
|
id: userApiKey.user._id.toString(),
|
|
33
33
|
username: userApiKey.user.username,
|
|
34
34
|
roleId: userApiKey.user.role?._id?.toString(),
|
|
35
|
+
roleName: userApiKey.user.role?.name,
|
|
35
36
|
tenantId: userApiKey.user?.tenant?._id?.toString(),
|
|
37
|
+
tenantName: userApiKey.user?.tenant?.name,
|
|
38
|
+
apiKeyId: userApiKey?._id?.toString(),
|
|
39
|
+
apiKeyName: userApiKey?.name
|
|
36
40
|
};
|
|
37
41
|
request.authUser = authUser;
|
|
38
42
|
}
|
package/dist/rbac/Rbac.js
CHANGED
|
@@ -11,8 +11,13 @@ class Rbac {
|
|
|
11
11
|
return {
|
|
12
12
|
id: this.userId,
|
|
13
13
|
username: this.username,
|
|
14
|
+
session: this.session,
|
|
14
15
|
roleId: this.roleId,
|
|
15
|
-
|
|
16
|
+
roleName: this.roleName,
|
|
17
|
+
tenantId: this.tenantId,
|
|
18
|
+
tenantName: this.tenantName,
|
|
19
|
+
apiKeyId: this.apiKeyId,
|
|
20
|
+
apiKeyName: this.apiKeyName
|
|
16
21
|
};
|
|
17
22
|
}
|
|
18
23
|
get username() {
|
|
@@ -21,12 +26,27 @@ class Rbac {
|
|
|
21
26
|
get userId() {
|
|
22
27
|
return this.authUser?.id.toString();
|
|
23
28
|
}
|
|
29
|
+
get session() {
|
|
30
|
+
return this.authUser?.session;
|
|
31
|
+
}
|
|
32
|
+
get apiKeyId() {
|
|
33
|
+
return this.authUser?.apiKeyId.toString();
|
|
34
|
+
}
|
|
35
|
+
get apiKeyName() {
|
|
36
|
+
return this.authUser?.apiKeyName;
|
|
37
|
+
}
|
|
24
38
|
get roleId() {
|
|
25
39
|
return this.authUser?.roleId.toString();
|
|
26
40
|
}
|
|
41
|
+
get roleName() {
|
|
42
|
+
return this.authUser?.roleName;
|
|
43
|
+
}
|
|
27
44
|
get tenantId() {
|
|
28
45
|
return this.authUser?.tenantId?.toString();
|
|
29
46
|
}
|
|
47
|
+
get tenantName() {
|
|
48
|
+
return this.authUser?.tenantName;
|
|
49
|
+
}
|
|
30
50
|
assertAuthenticated() {
|
|
31
51
|
if (!this.authUser) {
|
|
32
52
|
throw new UnauthorizedError();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const TokenPayloadSchema = z.object({
|
|
3
|
+
id: z.string(),
|
|
4
|
+
username: z.string(),
|
|
5
|
+
session: z.string(),
|
|
6
|
+
roleId: z.string(),
|
|
7
|
+
roleName: z.string().optional().nullable(),
|
|
8
|
+
tenantId: z.string().optional().nullable(),
|
|
9
|
+
tenantName: z.string().optional().nullable(),
|
|
10
|
+
apiKeyId: z.string().optional().nullable(),
|
|
11
|
+
apiKeyName: z.string().optional().nullable(),
|
|
12
|
+
});
|
|
13
|
+
export default TokenPayloadSchema;
|
|
14
|
+
export { TokenPayloadSchema };
|
|
@@ -15,19 +15,19 @@ class UserService extends AbstractService {
|
|
|
15
15
|
}
|
|
16
16
|
async auth(username, password, { userAgent, ip }) {
|
|
17
17
|
let user = null;
|
|
18
|
-
console.log("auth username", username);
|
|
19
18
|
user = await this.findByUsernameWithPassword(username);
|
|
20
19
|
if (user && user.active && AuthUtils.checkPassword(password, user.password)) {
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
const sessionUUID = await this.generateSession(user, userAgent, ip);
|
|
21
|
+
const tokenPayload = {
|
|
22
|
+
id: user._id.toString(),
|
|
23
|
+
username: user.username,
|
|
24
|
+
roleId: user.role?._id?.toString(),
|
|
25
|
+
roleName: user.role?.name,
|
|
26
|
+
tenantId: user.tenant ? user.tenant?._id?.toString() : null,
|
|
27
|
+
tenantName: user.tenant ? user.tenant?.name : null,
|
|
28
|
+
session: sessionUUID
|
|
29
|
+
};
|
|
30
|
+
const accessToken = AuthUtils.generateToken(tokenPayload);
|
|
31
31
|
return { accessToken: accessToken };
|
|
32
32
|
}
|
|
33
33
|
else {
|
|
@@ -40,11 +40,22 @@ class UserService extends AbstractService {
|
|
|
40
40
|
throw new BadCredentialsError();
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
+
async generateSession(user, userAgent, ip) {
|
|
44
|
+
const sessionUUID = randomUUID();
|
|
45
|
+
const sessionService = UserSessionServiceFactory();
|
|
46
|
+
await sessionService.create({
|
|
47
|
+
user: user._id.toString(),
|
|
48
|
+
uuid: sessionUUID,
|
|
49
|
+
userAgent: userAgent,
|
|
50
|
+
ip: ip
|
|
51
|
+
});
|
|
52
|
+
return sessionUUID;
|
|
53
|
+
}
|
|
43
54
|
async switchTenant(accessToken, tenantId) {
|
|
44
55
|
const newAccessToken = AuthUtils.switchTenant(accessToken, tenantId);
|
|
45
56
|
return { accessToken: newAccessToken };
|
|
46
57
|
}
|
|
47
|
-
async authByEmail(email, createIfNotFound = false, userData) {
|
|
58
|
+
async authByEmail(email, createIfNotFound = false, userData, { userAgent, ip }) {
|
|
48
59
|
let user = null;
|
|
49
60
|
console.log("auth email", email);
|
|
50
61
|
user = await this.findByEmail(email);
|
|
@@ -54,8 +65,17 @@ class UserService extends AbstractService {
|
|
|
54
65
|
user = await this.create(userData);
|
|
55
66
|
}
|
|
56
67
|
if (user && user.active) {
|
|
57
|
-
const
|
|
58
|
-
const
|
|
68
|
+
const sessionUUID = await this.generateSession(user, userAgent, ip);
|
|
69
|
+
const tokenPayload = {
|
|
70
|
+
id: user._id.toString(),
|
|
71
|
+
username: user.username,
|
|
72
|
+
roleId: user.role?._id?.toString(),
|
|
73
|
+
roleName: user.role?.name,
|
|
74
|
+
tenantId: user.tenant ? user.tenant?._id?.toString() : null,
|
|
75
|
+
tenantName: user.tenant ? user.tenant?.name : null,
|
|
76
|
+
session: sessionUUID
|
|
77
|
+
};
|
|
78
|
+
const accessToken = AuthUtils.generateToken(tokenPayload);
|
|
59
79
|
return { accessToken: accessToken };
|
|
60
80
|
}
|
|
61
81
|
else {
|
package/dist/utils/AuthUtils.js
CHANGED
|
@@ -3,6 +3,7 @@ import jsonwebtoken from "jsonwebtoken";
|
|
|
3
3
|
import { DraxConfig } from "@drax/common-back";
|
|
4
4
|
import IdentityConfig from "../config/IdentityConfig.js";
|
|
5
5
|
import crypto from "crypto";
|
|
6
|
+
import { TokenPayloadSchema } from "../schemas/TokenPayloadSchema.js";
|
|
6
7
|
class AuthUtils {
|
|
7
8
|
static verifyToken(token) {
|
|
8
9
|
const JWT_SECRET = DraxConfig.getOrLoad(IdentityConfig.JwtSecret);
|
|
@@ -12,7 +13,9 @@ class AuthUtils {
|
|
|
12
13
|
const options = {
|
|
13
14
|
algorithms: ['HS256'],
|
|
14
15
|
};
|
|
15
|
-
|
|
16
|
+
const tokenPayload = jsonwebtoken.verify(token, JWT_SECRET, options);
|
|
17
|
+
TokenPayloadSchema.parse(tokenPayload);
|
|
18
|
+
return tokenPayload;
|
|
16
19
|
}
|
|
17
20
|
static hashPassword(password) {
|
|
18
21
|
if (!password) {
|
|
@@ -25,17 +28,8 @@ class AuthUtils {
|
|
|
25
28
|
static checkPassword(password, hashPassword) {
|
|
26
29
|
return bcryptjs.compareSync(password, hashPassword);
|
|
27
30
|
}
|
|
28
|
-
static
|
|
29
|
-
|
|
30
|
-
id: userId,
|
|
31
|
-
username: username,
|
|
32
|
-
roleId: roleId,
|
|
33
|
-
tenantId: tenantId,
|
|
34
|
-
session: session
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
static generateToken(userId, username, roleId, tenantId, session) {
|
|
38
|
-
const payload = AuthUtils.tokenSignPayload(userId, username, roleId, tenantId, session);
|
|
31
|
+
static generateToken(payload) {
|
|
32
|
+
TokenPayloadSchema.parse(payload);
|
|
39
33
|
const JWT_SECRET = DraxConfig.getOrLoad(IdentityConfig.JwtSecret);
|
|
40
34
|
if (!JWT_SECRET) {
|
|
41
35
|
throw new Error("JWT_SECRET ENV must be provided");
|
|
@@ -44,9 +38,9 @@ class AuthUtils {
|
|
|
44
38
|
const JWT_ISSUER = DraxConfig.getOrLoad(IdentityConfig.JwtIssuer) || 'DRAX';
|
|
45
39
|
const options = {
|
|
46
40
|
expiresIn: JWT_EXPIRATION,
|
|
47
|
-
jwtid:
|
|
41
|
+
jwtid: payload.id,
|
|
48
42
|
algorithm: 'HS256',
|
|
49
|
-
audience: username,
|
|
43
|
+
audience: payload.username,
|
|
50
44
|
issuer: JWT_ISSUER
|
|
51
45
|
};
|
|
52
46
|
let token = jsonwebtoken.sign(payload, JWT_SECRET, options);
|
|
@@ -62,31 +56,23 @@ class AuthUtils {
|
|
|
62
56
|
}
|
|
63
57
|
static switchTenant(accessToken, newTenantId) {
|
|
64
58
|
// Verificar que el token actual sea válido
|
|
65
|
-
const
|
|
66
|
-
if (!
|
|
59
|
+
const tokenPayload = AuthUtils.verifyToken(accessToken);
|
|
60
|
+
if (!tokenPayload) {
|
|
67
61
|
throw new Error("Invalid access token");
|
|
68
62
|
}
|
|
69
|
-
|
|
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
|
-
};
|
|
63
|
+
tokenPayload.tenantId = newTenantId;
|
|
78
64
|
const JWT_SECRET = DraxConfig.getOrLoad(IdentityConfig.JwtSecret);
|
|
79
65
|
if (!JWT_SECRET) {
|
|
80
66
|
throw new Error("JWT_SECRET ENV must be provided");
|
|
81
67
|
}
|
|
82
68
|
const JWT_ISSUER = DraxConfig.getOrLoad(IdentityConfig.JwtIssuer) || 'DRAX';
|
|
83
69
|
const options = {
|
|
84
|
-
jwtid:
|
|
70
|
+
jwtid: tokenPayload.id,
|
|
85
71
|
algorithm: 'HS256',
|
|
86
|
-
audience:
|
|
72
|
+
audience: tokenPayload.username,
|
|
87
73
|
issuer: JWT_ISSUER
|
|
88
74
|
};
|
|
89
|
-
return jsonwebtoken.sign(
|
|
75
|
+
return jsonwebtoken.sign(tokenPayload, JWT_SECRET, options);
|
|
90
76
|
}
|
|
91
77
|
}
|
|
92
78
|
export default AuthUtils;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.38.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.38.0",
|
|
32
|
+
"@drax/crud-back": "^0.38.0",
|
|
33
|
+
"@drax/crud-share": "^0.38.0",
|
|
34
|
+
"@drax/email-back": "^0.38.0",
|
|
35
|
+
"@drax/identity-share": "^0.38.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": "43c90f3c12165e7527edefbc80dd327a59236dd5"
|
|
67
67
|
}
|
|
@@ -29,19 +29,23 @@ async function apiKeyMiddleware (request, reply) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
//Por authorization '<uuid-key>'
|
|
32
|
-
const uuidRegex =
|
|
32
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
33
33
|
if(request.headers['authorization'] && uuidRegex.test(request.headers['authorization'])){
|
|
34
34
|
apiKey = request.headers['authorization']
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
if(apiKey){
|
|
38
|
-
const userApiKey = await draxCache.getOrLoad(apiKey, userApiKeyLoader)
|
|
38
|
+
const userApiKey : IUserApiKey = await draxCache.getOrLoad(apiKey, userApiKeyLoader)
|
|
39
39
|
if(userApiKey && userApiKey.user){
|
|
40
40
|
const authUser: IAuthUser = {
|
|
41
41
|
id: userApiKey.user._id.toString(),
|
|
42
42
|
username: userApiKey.user.username,
|
|
43
43
|
roleId: userApiKey.user.role?._id?.toString(),
|
|
44
|
+
roleName: userApiKey.user.role?.name,
|
|
44
45
|
tenantId: userApiKey.user?.tenant?._id?.toString(),
|
|
46
|
+
tenantName: userApiKey.user?.tenant?.name,
|
|
47
|
+
apiKeyId: userApiKey?._id?.toString(),
|
|
48
|
+
apiKeyName: userApiKey?.name
|
|
45
49
|
}
|
|
46
50
|
request.authUser = authUser
|
|
47
51
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import AuthUtils from "../utils/AuthUtils.js";
|
|
2
|
-
import {
|
|
2
|
+
import {IAuthUser} from "@drax/identity-share";
|
|
3
3
|
|
|
4
4
|
function jwtMiddleware (request, reply, done) {
|
|
5
5
|
try{
|
|
@@ -14,7 +14,7 @@ function jwtMiddleware (request, reply, done) {
|
|
|
14
14
|
const routerPath = request.url
|
|
15
15
|
|
|
16
16
|
if(routerPath != '/api/auth/login' && token){
|
|
17
|
-
const authUser = AuthUtils.verifyToken(token)
|
|
17
|
+
const authUser: IAuthUser = AuthUtils.verifyToken(token)
|
|
18
18
|
if(authUser){
|
|
19
19
|
request.authUser = authUser
|
|
20
20
|
request.token = token
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {IAuthUser, IRbac, IRole} from "@drax/identity-share";
|
|
2
2
|
import {DraxCache, DraxConfig} from "@drax/common-back";
|
|
3
3
|
import RoleServiceFactory from "../factory/RoleServiceFactory.js";
|
|
4
4
|
import Rbac from "../rbac/Rbac.js";
|
|
@@ -16,14 +16,14 @@ async function roleLoader(k):Promise<IRole | null> {
|
|
|
16
16
|
|
|
17
17
|
async function rbacMiddleware (request, reply) {
|
|
18
18
|
try{
|
|
19
|
-
if(request.authUser as
|
|
20
|
-
const authUser = request.authUser
|
|
19
|
+
if(request.authUser as IAuthUser){
|
|
20
|
+
const authUser: IAuthUser = request.authUser
|
|
21
21
|
const cacheKey = authUser.roleId
|
|
22
|
-
const role = await draxCache.getOrLoad(cacheKey, roleLoader)
|
|
23
|
-
const rbac = new Rbac(authUser,role)
|
|
22
|
+
const role : IRole = await draxCache.getOrLoad(cacheKey, roleLoader)
|
|
23
|
+
const rbac: IRbac = new Rbac(authUser,role)
|
|
24
24
|
request.rbac = rbac
|
|
25
25
|
}else{
|
|
26
|
-
const rbac = new Rbac(null,null)
|
|
26
|
+
const rbac: IRbac = new Rbac(null,null)
|
|
27
27
|
request.rbac = rbac
|
|
28
28
|
}
|
|
29
29
|
return
|
package/src/rbac/Rbac.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { IAuthUser, IRole, IRbac} from "@drax/identity-share";
|
|
2
2
|
import {UnauthorizedError, ForbiddenError} from "@drax/common-back";
|
|
3
|
-
import {IAuthUser} from "@drax/identity-share/dist";
|
|
4
3
|
|
|
5
4
|
class Rbac implements IRbac{
|
|
6
5
|
private role: IRole;
|
|
7
6
|
private authUser: IAuthUser;
|
|
8
7
|
|
|
9
|
-
constructor(authUser:
|
|
8
|
+
constructor(authUser: IAuthUser, role: IRole) {
|
|
10
9
|
this.authUser = authUser;
|
|
11
10
|
this.role = role;
|
|
12
11
|
}
|
|
@@ -19,8 +18,13 @@ class Rbac implements IRbac{
|
|
|
19
18
|
return {
|
|
20
19
|
id: this.userId,
|
|
21
20
|
username: this.username,
|
|
21
|
+
session: this.session,
|
|
22
22
|
roleId: this.roleId,
|
|
23
|
-
|
|
23
|
+
roleName: this.roleName,
|
|
24
|
+
tenantId: this.tenantId,
|
|
25
|
+
tenantName: this.tenantName,
|
|
26
|
+
apiKeyId: this.apiKeyId,
|
|
27
|
+
apiKeyName: this.apiKeyName
|
|
24
28
|
}
|
|
25
29
|
}
|
|
26
30
|
|
|
@@ -32,14 +36,34 @@ class Rbac implements IRbac{
|
|
|
32
36
|
return this.authUser?.id.toString()
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
get session(): string {
|
|
40
|
+
return this.authUser?.session
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get apiKeyId(): string {
|
|
44
|
+
return this.authUser?.apiKeyId.toString()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get apiKeyName(): string {
|
|
48
|
+
return this.authUser?.apiKeyName
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
get roleId(): string {
|
|
36
52
|
return this.authUser?.roleId.toString()
|
|
37
53
|
}
|
|
38
54
|
|
|
55
|
+
get roleName(): string {
|
|
56
|
+
return this.authUser?.roleName
|
|
57
|
+
}
|
|
58
|
+
|
|
39
59
|
get tenantId(): string | undefined {
|
|
40
60
|
return this.authUser?.tenantId?.toString();
|
|
41
61
|
}
|
|
42
62
|
|
|
63
|
+
get tenantName(): string | undefined {
|
|
64
|
+
return this.authUser?.tenantName;
|
|
65
|
+
}
|
|
66
|
+
|
|
43
67
|
assertAuthenticated() {
|
|
44
68
|
if (!this.authUser) {
|
|
45
69
|
throw new UnauthorizedError()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {z} from 'zod';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const TokenPayloadSchema = z.object({
|
|
5
|
+
id: z.string(),
|
|
6
|
+
username: z.string(),
|
|
7
|
+
|
|
8
|
+
session: z.string(),
|
|
9
|
+
|
|
10
|
+
roleId: z.string(),
|
|
11
|
+
roleName: z.string().optional().nullable(),
|
|
12
|
+
|
|
13
|
+
tenantId: z.string().optional().nullable(),
|
|
14
|
+
tenantName: z.string().optional().nullable(),
|
|
15
|
+
|
|
16
|
+
apiKeyId: z.string().optional().nullable(),
|
|
17
|
+
apiKeyName: z.string().optional().nullable(),
|
|
18
|
+
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
export default TokenPayloadSchema;
|
|
23
|
+
export {TokenPayloadSchema}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {IUser, IUserCreate, IUserUpdate} from "@drax/identity-share";
|
|
1
|
+
import type {IAuthUser, IUser, IUserCreate, IUserUpdate} from "@drax/identity-share";
|
|
2
2
|
import type {IUserRepository} from "../interfaces/IUserRepository";
|
|
3
3
|
|
|
4
4
|
import {ZodError} from "zod";
|
|
@@ -24,19 +24,22 @@ class UserService extends AbstractService<IUser, IUserCreate, IUserUpdate> {
|
|
|
24
24
|
|
|
25
25
|
async auth(username: string, password: string, {userAgent, ip}) {
|
|
26
26
|
let user = null
|
|
27
|
-
console.log("auth username", username)
|
|
28
27
|
user = await this.findByUsernameWithPassword(username)
|
|
29
28
|
if (user && user.active && AuthUtils.checkPassword(password, user.password)) {
|
|
30
|
-
|
|
31
|
-
const sessionUUID =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
|
|
30
|
+
const sessionUUID = await this.generateSession(user, userAgent, ip);
|
|
31
|
+
|
|
32
|
+
const tokenPayload: IAuthUser = {
|
|
33
|
+
id: user._id.toString(),
|
|
34
|
+
username: user.username,
|
|
35
|
+
roleId: user.role?._id?.toString(),
|
|
36
|
+
roleName: user.role?.name,
|
|
37
|
+
tenantId: user.tenant ? user.tenant?._id?.toString() : null,
|
|
38
|
+
tenantName: user.tenant ? user.tenant?.name : null,
|
|
39
|
+
session: sessionUUID
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const accessToken = AuthUtils.generateToken(tokenPayload)
|
|
40
43
|
return {accessToken: accessToken}
|
|
41
44
|
} else {
|
|
42
45
|
const userLoginFailService = UserLoginFailServiceFactory()
|
|
@@ -49,12 +52,24 @@ class UserService extends AbstractService<IUser, IUserCreate, IUserUpdate> {
|
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
54
|
|
|
55
|
+
private async generateSession(user, userAgent, ip) {
|
|
56
|
+
const sessionUUID = randomUUID()
|
|
57
|
+
const sessionService = UserSessionServiceFactory()
|
|
58
|
+
await sessionService.create({
|
|
59
|
+
user: user._id.toString(),
|
|
60
|
+
uuid: sessionUUID,
|
|
61
|
+
userAgent: userAgent,
|
|
62
|
+
ip: ip
|
|
63
|
+
})
|
|
64
|
+
return sessionUUID;
|
|
65
|
+
}
|
|
66
|
+
|
|
52
67
|
async switchTenant(accessToken: string, tenantId: string) {
|
|
53
68
|
const newAccessToken = AuthUtils.switchTenant(accessToken, tenantId)
|
|
54
69
|
return {accessToken: newAccessToken}
|
|
55
70
|
}
|
|
56
71
|
|
|
57
|
-
async authByEmail(email: string, createIfNotFound: boolean = false, userData: IUserCreate) {
|
|
72
|
+
async authByEmail(email: string, createIfNotFound: boolean = false, userData: IUserCreate, {userAgent, ip}) {
|
|
58
73
|
let user = null
|
|
59
74
|
console.log("auth email", email)
|
|
60
75
|
user = await this.findByEmail(email)
|
|
@@ -66,8 +81,20 @@ class UserService extends AbstractService<IUser, IUserCreate, IUserUpdate> {
|
|
|
66
81
|
}
|
|
67
82
|
|
|
68
83
|
if (user && user.active) {
|
|
69
|
-
const
|
|
70
|
-
|
|
84
|
+
const sessionUUID = await this.generateSession(user, userAgent, ip);
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
const tokenPayload: IAuthUser = {
|
|
88
|
+
id: user._id.toString(),
|
|
89
|
+
username: user.username,
|
|
90
|
+
roleId: user.role?._id?.toString(),
|
|
91
|
+
roleName: user.role?.name,
|
|
92
|
+
tenantId: user.tenant ? user.tenant?._id?.toString() : null,
|
|
93
|
+
tenantName: user.tenant ? user.tenant?.name : null,
|
|
94
|
+
session: sessionUUID
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const accessToken = AuthUtils.generateToken(tokenPayload)
|
|
71
98
|
return {accessToken: accessToken}
|
|
72
99
|
} else {
|
|
73
100
|
throw new BadCredentialsError()
|
package/src/utils/AuthUtils.ts
CHANGED
|
@@ -3,11 +3,12 @@ import jsonwebtoken, {SignOptions, VerifyOptions} from "jsonwebtoken";
|
|
|
3
3
|
import {DraxConfig} from "@drax/common-back";
|
|
4
4
|
import IdentityConfig from "../config/IdentityConfig.js";
|
|
5
5
|
import crypto from "crypto";
|
|
6
|
-
import type {
|
|
6
|
+
import type {IAuthUser} from "@drax/identity-share";
|
|
7
|
+
import {TokenPayloadSchema} from "../schemas/TokenPayloadSchema.js";
|
|
7
8
|
|
|
8
9
|
class AuthUtils{
|
|
9
10
|
|
|
10
|
-
static verifyToken(token : string) {
|
|
11
|
+
static verifyToken(token : string): IAuthUser {
|
|
11
12
|
const JWT_SECRET = DraxConfig.getOrLoad(IdentityConfig.JwtSecret)
|
|
12
13
|
if(!JWT_SECRET){
|
|
13
14
|
throw new Error("DraxConfig.JWT_SECRET must be provided")
|
|
@@ -15,7 +16,9 @@ class AuthUtils{
|
|
|
15
16
|
const options : VerifyOptions = {
|
|
16
17
|
algorithms: ['HS256'],
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
+
const tokenPayload = jsonwebtoken.verify(token, JWT_SECRET, options)
|
|
20
|
+
TokenPayloadSchema.parse(tokenPayload)
|
|
21
|
+
return tokenPayload as IAuthUser;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
static hashPassword(password : string) :string {
|
|
@@ -32,18 +35,10 @@ class AuthUtils{
|
|
|
32
35
|
return bcryptjs.compareSync(password, hashPassword);
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
static tokenSignPayload(userId : string, username: string, roleId: string, tenantId: string, session : string): IJwtUser {
|
|
36
|
-
return {
|
|
37
|
-
id: userId,
|
|
38
|
-
username: username,
|
|
39
|
-
roleId: roleId,
|
|
40
|
-
tenantId: tenantId,
|
|
41
|
-
session: session
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
38
|
|
|
45
|
-
static generateToken(
|
|
46
|
-
|
|
39
|
+
static generateToken(payload: IAuthUser) {
|
|
40
|
+
|
|
41
|
+
TokenPayloadSchema.parse(payload)
|
|
47
42
|
|
|
48
43
|
const JWT_SECRET = DraxConfig.getOrLoad(IdentityConfig.JwtSecret)
|
|
49
44
|
if(!JWT_SECRET){
|
|
@@ -55,9 +50,9 @@ class AuthUtils{
|
|
|
55
50
|
|
|
56
51
|
const options : SignOptions = {
|
|
57
52
|
expiresIn: JWT_EXPIRATION,
|
|
58
|
-
jwtid:
|
|
53
|
+
jwtid: payload.id,
|
|
59
54
|
algorithm: 'HS256',
|
|
60
|
-
audience: username,
|
|
55
|
+
audience: payload.username,
|
|
61
56
|
issuer: JWT_ISSUER
|
|
62
57
|
}
|
|
63
58
|
|
|
@@ -81,21 +76,13 @@ class AuthUtils{
|
|
|
81
76
|
|
|
82
77
|
static switchTenant(accessToken: string, newTenantId: string): string {
|
|
83
78
|
// Verificar que el token actual sea válido
|
|
84
|
-
const
|
|
79
|
+
const tokenPayload = AuthUtils.verifyToken(accessToken) as IAuthUser & { exp?: number };
|
|
85
80
|
|
|
86
|
-
if (!
|
|
81
|
+
if (!tokenPayload) {
|
|
87
82
|
throw new Error("Invalid access token");
|
|
88
83
|
}
|
|
89
84
|
|
|
90
|
-
|
|
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
|
-
};
|
|
85
|
+
tokenPayload.tenantId = newTenantId;
|
|
99
86
|
|
|
100
87
|
const JWT_SECRET = DraxConfig.getOrLoad(IdentityConfig.JwtSecret);
|
|
101
88
|
if (!JWT_SECRET) {
|
|
@@ -105,14 +92,13 @@ class AuthUtils{
|
|
|
105
92
|
const JWT_ISSUER = DraxConfig.getOrLoad(IdentityConfig.JwtIssuer) || 'DRAX';
|
|
106
93
|
|
|
107
94
|
const options: SignOptions = {
|
|
108
|
-
jwtid:
|
|
95
|
+
jwtid: tokenPayload.id,
|
|
109
96
|
algorithm: 'HS256',
|
|
110
|
-
audience:
|
|
97
|
+
audience: tokenPayload.username,
|
|
111
98
|
issuer: JWT_ISSUER
|
|
112
99
|
};
|
|
113
100
|
|
|
114
|
-
|
|
115
|
-
return jsonwebtoken.sign(newPayload, JWT_SECRET, options);
|
|
101
|
+
return jsonwebtoken.sign(tokenPayload, JWT_SECRET, options);
|
|
116
102
|
}
|
|
117
103
|
}
|
|
118
104
|
|