@jmlq/auth 0.0.1-alpha.29 → 0.0.1-alpha.30
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/application/dtos/request/index.d.ts +2 -0
- package/dist/application/dtos/request/index.js +2 -0
- package/dist/application/dtos/request/me.request.d.ts +3 -0
- package/dist/application/dtos/request/me.request.js +2 -0
- package/dist/application/dtos/request/verify-email.request.d.ts +3 -0
- package/dist/application/dtos/request/verify-email.request.js +2 -0
- package/dist/application/dtos/response/index.d.ts +2 -0
- package/dist/application/dtos/response/index.js +2 -0
- package/dist/application/dtos/response/me.response.d.ts +11 -0
- package/dist/application/dtos/response/me.response.js +2 -0
- package/dist/application/dtos/response/register-user.response.d.ts +9 -0
- package/dist/application/dtos/response/verify-email.response.d.ts +4 -0
- package/dist/application/dtos/response/verify-email.response.js +2 -0
- package/dist/application/facades/auth.facade.d.ts +4 -2
- package/dist/application/facades/auth.facade.js +6 -0
- package/dist/application/facades/create-auth-facade.d.ts +2 -1
- package/dist/application/facades/create-auth-facade.js +1 -1
- package/dist/application/factories/auth-service.factory.d.ts +2 -2
- package/dist/application/factories/auth-service.factory.js +7 -2
- package/dist/application/types/auth-service-factory-options.type.d.ts +1 -0
- package/dist/application/use-cases/index.d.ts +2 -0
- package/dist/application/use-cases/index.js +2 -0
- package/dist/application/use-cases/login-with-password.use-case.js +4 -1
- package/dist/application/use-cases/me.use-case.d.ts +7 -0
- package/dist/application/use-cases/me.use-case.js +28 -0
- package/dist/application/use-cases/register-user.use-case.d.ts +6 -1
- package/dist/application/use-cases/register-user.use-case.js +12 -1
- package/dist/application/use-cases/verify-email.use-case.d.ts +8 -0
- package/dist/application/use-cases/verify-email.use-case.js +26 -0
- package/dist/domain/entities/user.entity.d.ts +12 -0
- package/dist/domain/entities/user.entity.js +19 -2
- package/dist/domain/errors/auth-error-code.d.ts +1 -1
- package/dist/domain/errors/auth.errors.d.ts +3 -0
- package/dist/domain/errors/auth.errors.js +7 -1
- package/dist/domain/ports/auth/email-verification-token.port.d.ts +19 -0
- package/dist/domain/ports/auth/email-verification-token.port.js +2 -0
- package/dist/domain/ports/auth/index.d.ts +1 -0
- package/dist/domain/ports/auth/index.js +1 -0
- package/dist/domain/props/entities/user.props.d.ts +1 -0
- package/dist/domain/props/jwt/jwt-user.d.ts +11 -2
- package/dist/infrastructure/services/token-session.service.js +7 -20
- package/dist/infrastructure/types/auth-service-container.d.ts +12 -2
- package/package.json +1 -1
|
@@ -21,3 +21,5 @@ __exportStar(require("./register-user.request"), exports);
|
|
|
21
21
|
__exportStar(require("./request-password-reset.request"), exports);
|
|
22
22
|
__exportStar(require("./reset-password.request"), exports);
|
|
23
23
|
__exportStar(require("./change-password.request"), exports);
|
|
24
|
+
__exportStar(require("./verify-email.request"), exports);
|
|
25
|
+
__exportStar(require("./me.request"), exports);
|
|
@@ -21,3 +21,5 @@ __exportStar(require("./register-user.response"), exports);
|
|
|
21
21
|
__exportStar(require("./reset-password.response"), exports);
|
|
22
22
|
__exportStar(require("./request-password-reset.response"), exports);
|
|
23
23
|
__exportStar(require("./change-password.response"), exports);
|
|
24
|
+
__exportStar(require("./verify-email.response"), exports);
|
|
25
|
+
__exportStar(require("./me.response"), exports);
|
|
@@ -4,4 +4,13 @@ export interface RegisterUserResponse {
|
|
|
4
4
|
role: string;
|
|
5
5
|
}[];
|
|
6
6
|
isActive: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Delivery interno para el host (API) para enviar email.
|
|
9
|
+
* NO recomendado devolver al cliente.
|
|
10
|
+
*/
|
|
11
|
+
delivery?: {
|
|
12
|
+
email: string;
|
|
13
|
+
token: string;
|
|
14
|
+
expiresAt: string;
|
|
15
|
+
};
|
|
7
16
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IAuthServiceContainer } from "../../infrastructure";
|
|
2
|
-
import type { ChangePasswordRequest, LoginRequest, LogoutRequest, RefreshTokenRequest, RegisterUserRequest, RequestPasswordResetRequest, ResetPasswordRequest } from "../dtos/request";
|
|
3
|
-
import type { ChangePasswordResponse, LoginResponse, LogoutResponse, RefreshTokenResponse, RegisterUserResponse, RequestPasswordResetResponse, ResetPasswordResponse } from "../dtos/response";
|
|
2
|
+
import type { ChangePasswordRequest, LoginRequest, LogoutRequest, MeRequest, RefreshTokenRequest, RegisterUserRequest, RequestPasswordResetRequest, ResetPasswordRequest, VerifyEmailRequest } from "../dtos/request";
|
|
3
|
+
import type { ChangePasswordResponse, LoginResponse, LogoutResponse, MeResponse, RefreshTokenResponse, RegisterUserResponse, RequestPasswordResetResponse, ResetPasswordResponse, VerifyEmailResponse } from "../dtos/response";
|
|
4
4
|
/**
|
|
5
5
|
* Facade delgada para integrar @jmlq/auth en hosts (APIs externas).
|
|
6
6
|
*
|
|
@@ -28,4 +28,6 @@ export declare class AuthFacade {
|
|
|
28
28
|
changePassword(request: ChangePasswordRequest): Promise<ChangePasswordResponse>;
|
|
29
29
|
requestPasswordReset(request: RequestPasswordResetRequest): Promise<RequestPasswordResetResponse>;
|
|
30
30
|
resetPassword(request: ResetPasswordRequest): Promise<ResetPasswordResponse>;
|
|
31
|
+
verifyEmail(request: VerifyEmailRequest): Promise<VerifyEmailResponse>;
|
|
32
|
+
me(request: MeRequest): Promise<MeResponse>;
|
|
31
33
|
}
|
|
@@ -50,5 +50,11 @@ class AuthFacade {
|
|
|
50
50
|
resetPassword(request) {
|
|
51
51
|
return this.container.resetPasswordUseCase.execute(request);
|
|
52
52
|
}
|
|
53
|
+
verifyEmail(request) {
|
|
54
|
+
return this.container.verifyEmailUseCase.execute(request);
|
|
55
|
+
}
|
|
56
|
+
me(request) {
|
|
57
|
+
return this.container.meUseCase.execute(request);
|
|
58
|
+
}
|
|
53
59
|
}
|
|
54
60
|
exports.AuthFacade = AuthFacade;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AuthFacade } from "./auth.facade";
|
|
2
|
-
import type { IUserRepositoryPort, ICredentialRepositoryPort, ITokenServicePort, IPasswordResetTokenPort } from "../../domain";
|
|
2
|
+
import type { IUserRepositoryPort, ICredentialRepositoryPort, ITokenServicePort, IPasswordResetTokenPort, IEmailVerificationTokenPort } from "../../domain";
|
|
3
3
|
import type { AuthServiceFactoryOptions } from "../types";
|
|
4
4
|
/**
|
|
5
5
|
* Helper de integración para hosts (APIs externas).
|
|
@@ -16,6 +16,7 @@ export type CreateAuthFacadeDeps = Readonly<{
|
|
|
16
16
|
credentialRepository: ICredentialRepositoryPort;
|
|
17
17
|
tokenService: ITokenServicePort;
|
|
18
18
|
passwordResetToken: IPasswordResetTokenPort;
|
|
19
|
+
emailVerificationToken: IEmailVerificationTokenPort;
|
|
19
20
|
options?: AuthServiceFactoryOptions;
|
|
20
21
|
}>;
|
|
21
22
|
export declare function createAuthFacade(deps: CreateAuthFacadeDeps): AuthFacade;
|
|
@@ -4,6 +4,6 @@ exports.createAuthFacade = createAuthFacade;
|
|
|
4
4
|
const auth_facade_1 = require("./auth.facade");
|
|
5
5
|
const factories_1 = require("../factories");
|
|
6
6
|
function createAuthFacade(deps) {
|
|
7
|
-
const container = factories_1.AuthServiceFactory.create(deps.userRepository, deps.credentialRepository, deps.tokenService, deps.passwordResetToken, deps.options);
|
|
7
|
+
const container = factories_1.AuthServiceFactory.create(deps.userRepository, deps.credentialRepository, deps.tokenService, deps.passwordResetToken, deps.emailVerificationToken, deps.options);
|
|
8
8
|
return new auth_facade_1.AuthFacade(container);
|
|
9
9
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ICredentialRepositoryPort, ITokenServicePort, IUserRepositoryPort, IPasswordResetTokenPort } from "../../domain";
|
|
1
|
+
import { ICredentialRepositoryPort, ITokenServicePort, IUserRepositoryPort, IPasswordResetTokenPort, IEmailVerificationTokenPort } from "../../domain";
|
|
2
2
|
import type { IAuthServiceContainer } from "../../infrastructure/types";
|
|
3
3
|
import type { AuthServiceFactoryOptions } from "../types";
|
|
4
4
|
/**
|
|
@@ -7,5 +7,5 @@ import type { AuthServiceFactoryOptions } from "../types";
|
|
|
7
7
|
* - encapsula configuración para que NO se repita en cada API externa
|
|
8
8
|
*/
|
|
9
9
|
export declare class AuthServiceFactory {
|
|
10
|
-
static create(userRepository: IUserRepositoryPort, credentialRepository: ICredentialRepositoryPort, tokenService: ITokenServicePort, passwordResetToken: IPasswordResetTokenPort, options?: AuthServiceFactoryOptions): IAuthServiceContainer;
|
|
10
|
+
static create(userRepository: IUserRepositoryPort, credentialRepository: ICredentialRepositoryPort, tokenService: ITokenServicePort, passwordResetToken: IPasswordResetTokenPort, emailVerificationToken: IEmailVerificationTokenPort, options?: AuthServiceFactoryOptions): IAuthServiceContainer;
|
|
11
11
|
}
|
|
@@ -11,15 +11,17 @@ const services_2 = require("../../infrastructure/services");
|
|
|
11
11
|
* - encapsula configuración para que NO se repita en cada API externa
|
|
12
12
|
*/
|
|
13
13
|
class AuthServiceFactory {
|
|
14
|
-
static create(userRepository, credentialRepository, tokenService, passwordResetToken, options) {
|
|
14
|
+
static create(userRepository, credentialRepository, tokenService, passwordResetToken, emailVerificationToken, options) {
|
|
15
15
|
// 1) Policy + hasher
|
|
16
16
|
const passwordPolicy = options?.passwordPolicy ?? new services_1.DefaultPasswordPolicy();
|
|
17
17
|
const passwordHasher = new security_1.BcryptPasswordHasher(options?.bcryptSaltRounds);
|
|
18
18
|
// 2) Session service (rotación/revocación)
|
|
19
19
|
const tokenSession = new services_2.TokenSessionService(tokenService, userRepository, credentialRepository, options?.accessTokenTtl ?? "15m", options?.refreshTokenTtl ?? "7d");
|
|
20
20
|
// 3) Use cases
|
|
21
|
-
const registerUserUseCase = new use_cases_1.RegisterUserUseCase(userRepository, passwordHasher, passwordPolicy);
|
|
21
|
+
const registerUserUseCase = new use_cases_1.RegisterUserUseCase(userRepository, passwordHasher, passwordPolicy, emailVerificationToken, { verifyTokenTtl: options?.emailVerificationTokenTtl });
|
|
22
|
+
const verifyEmailUseCase = new use_cases_1.VerifyEmailUseCase(userRepository, emailVerificationToken);
|
|
22
23
|
const loginWithPasswordUseCase = new use_cases_1.LoginWithPasswordUseCase(userRepository, passwordHasher, tokenSession);
|
|
24
|
+
const meUseCase = new use_cases_1.MeUseCase(userRepository);
|
|
23
25
|
const refreshTokenUseCase = new use_cases_1.RefreshTokenUseCase(tokenSession);
|
|
24
26
|
const logoutUseCase = new use_cases_1.LogoutUseCase(tokenSession);
|
|
25
27
|
// 4) Use cases nuevos (password flows)
|
|
@@ -41,6 +43,9 @@ class AuthServiceFactory {
|
|
|
41
43
|
requestPasswordResetUseCase,
|
|
42
44
|
resetPasswordUseCase,
|
|
43
45
|
changePasswordUseCase,
|
|
46
|
+
verifyEmailUseCase,
|
|
47
|
+
emailVerificationToken,
|
|
48
|
+
meUseCase,
|
|
44
49
|
};
|
|
45
50
|
}
|
|
46
51
|
}
|
|
@@ -21,3 +21,5 @@ __exportStar(require("./register-user.use-case"), exports);
|
|
|
21
21
|
__exportStar(require("./change-password.use-case"), exports);
|
|
22
22
|
__exportStar(require("./request-password-reset.use-case"), exports);
|
|
23
23
|
__exportStar(require("./reset-password.use-case"), exports);
|
|
24
|
+
__exportStar(require("./verify-email.use-case"), exports);
|
|
25
|
+
__exportStar(require("./me.use-case"), exports);
|
|
@@ -16,8 +16,11 @@ class LoginWithPasswordUseCase {
|
|
|
16
16
|
if (!user) {
|
|
17
17
|
throw new errors_1.UserNotFoundError("Invalid credentials");
|
|
18
18
|
}
|
|
19
|
+
if (!user.isEmailVerified) {
|
|
20
|
+
throw new errors_1.EmailNotVerifiedError();
|
|
21
|
+
}
|
|
19
22
|
// Verificar que el usuario esté activo
|
|
20
|
-
if (!user.
|
|
23
|
+
if (!user.isActive) {
|
|
21
24
|
throw new errors_1.UserDisabledError("User account is not active");
|
|
22
25
|
}
|
|
23
26
|
// Verificar contraseña
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { IUserRepositoryPort } from "../../domain/ports";
|
|
2
|
+
import { MeRequest, MeResponse } from "../dtos";
|
|
3
|
+
export declare class MeUseCase {
|
|
4
|
+
private readonly userRepository;
|
|
5
|
+
constructor(userRepository: IUserRepositoryPort);
|
|
6
|
+
execute(request: MeRequest): Promise<MeResponse>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MeUseCase = void 0;
|
|
4
|
+
const object_values_1 = require("../../domain/object-values");
|
|
5
|
+
const domain_1 = require("../../domain");
|
|
6
|
+
class MeUseCase {
|
|
7
|
+
constructor(userRepository) {
|
|
8
|
+
this.userRepository = userRepository;
|
|
9
|
+
}
|
|
10
|
+
async execute(request) {
|
|
11
|
+
const userId = String(request.userId ?? "").trim();
|
|
12
|
+
if (!userId)
|
|
13
|
+
throw new Error("userId is required");
|
|
14
|
+
const user = await this.userRepository.findById(new object_values_1.Id(userId));
|
|
15
|
+
if (!user)
|
|
16
|
+
throw new domain_1.UserNotFoundError();
|
|
17
|
+
return {
|
|
18
|
+
id: user.id.getValue(),
|
|
19
|
+
email: user.email.getValue(),
|
|
20
|
+
isActive: user.isActive,
|
|
21
|
+
isEmailVerified: user.isEmailVerified,
|
|
22
|
+
roles: user.roles.map((r) => r.getValuePublic()),
|
|
23
|
+
createdAt: user.createdAt.toISOString(),
|
|
24
|
+
updatedAt: user.updatedAt.toISOString(),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.MeUseCase = MeUseCase;
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { IPasswordHasherPort, IPasswordPolicyPort } from "../../domain";
|
|
2
2
|
import { IUserRepositoryPort } from "../../domain/ports/repository";
|
|
3
3
|
import { RegisterUserRequest, RegisterUserResponse } from "../dtos";
|
|
4
|
+
import type { IEmailVerificationTokenPort } from "../../domain/ports/auth";
|
|
4
5
|
export declare class RegisterUserUseCase {
|
|
5
6
|
private readonly userRepository;
|
|
6
7
|
private readonly passwordHasher;
|
|
7
8
|
private readonly passwordPolicy;
|
|
9
|
+
private readonly emailVerificationToken;
|
|
8
10
|
private static readonly DEFAULT_ROLE;
|
|
9
|
-
|
|
11
|
+
private readonly verifyTokenTtl;
|
|
12
|
+
constructor(userRepository: IUserRepositoryPort, passwordHasher: IPasswordHasherPort, passwordPolicy: IPasswordPolicyPort, emailVerificationToken: IEmailVerificationTokenPort, opts?: {
|
|
13
|
+
verifyTokenTtl?: string;
|
|
14
|
+
});
|
|
10
15
|
private static normalizeRoles;
|
|
11
16
|
execute(request: RegisterUserRequest): Promise<RegisterUserResponse>;
|
|
12
17
|
}
|
|
@@ -3,11 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RegisterUserUseCase = void 0;
|
|
4
4
|
const domain_1 = require("../../domain");
|
|
5
5
|
const internal_1 = require("./internal");
|
|
6
|
+
const utils_1 = require("../../shared/utils");
|
|
6
7
|
class RegisterUserUseCase {
|
|
7
|
-
constructor(userRepository, passwordHasher, passwordPolicy) {
|
|
8
|
+
constructor(userRepository, passwordHasher, passwordPolicy, emailVerificationToken, opts) {
|
|
8
9
|
this.userRepository = userRepository;
|
|
9
10
|
this.passwordHasher = passwordHasher;
|
|
10
11
|
this.passwordPolicy = passwordPolicy;
|
|
12
|
+
this.emailVerificationToken = emailVerificationToken;
|
|
13
|
+
this.verifyTokenTtl = opts?.verifyTokenTtl ?? "30m";
|
|
11
14
|
}
|
|
12
15
|
// ---------------------------------------------------------------------------
|
|
13
16
|
// Helpers
|
|
@@ -49,11 +52,19 @@ class RegisterUserUseCase {
|
|
|
49
52
|
const user = domain_1.User.create(request.email, roles, hashedPassword);
|
|
50
53
|
// Guardar en repositorio
|
|
51
54
|
await this.userRepository.save(user);
|
|
55
|
+
// Emitir token de verificación (delivery interno)
|
|
56
|
+
const ttlMs = utils_1.TimeParser.parseToMilliseconds(this.verifyTokenTtl);
|
|
57
|
+
const issued = await this.emailVerificationToken.issue(user.id, ttlMs);
|
|
52
58
|
// Retornar respuesta
|
|
53
59
|
return {
|
|
54
60
|
id: user.id.getValue(),
|
|
55
61
|
roles: user.roles.map((role) => role.getValuePublic()),
|
|
56
62
|
isActive: user.isActive,
|
|
63
|
+
delivery: {
|
|
64
|
+
email: user.email.getValue(),
|
|
65
|
+
token: issued.token,
|
|
66
|
+
expiresAt: issued.expiresAt.toISOString(),
|
|
67
|
+
},
|
|
57
68
|
};
|
|
58
69
|
}
|
|
59
70
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { VerifyEmailRequest, VerifyEmailResponse } from "../dtos";
|
|
2
|
+
import { IEmailVerificationTokenPort, IUserRepositoryPort } from "../../domain/ports";
|
|
3
|
+
export declare class VerifyEmailUseCase {
|
|
4
|
+
private readonly userRepository;
|
|
5
|
+
private readonly emailVerificationToken;
|
|
6
|
+
constructor(userRepository: IUserRepositoryPort, emailVerificationToken: IEmailVerificationTokenPort);
|
|
7
|
+
execute(request: VerifyEmailRequest): Promise<VerifyEmailResponse>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VerifyEmailUseCase = void 0;
|
|
4
|
+
const object_values_1 = require("../../domain/object-values");
|
|
5
|
+
const errors_1 = require("../../domain/errors");
|
|
6
|
+
class VerifyEmailUseCase {
|
|
7
|
+
constructor(userRepository, emailVerificationToken) {
|
|
8
|
+
this.userRepository = userRepository;
|
|
9
|
+
this.emailVerificationToken = emailVerificationToken;
|
|
10
|
+
}
|
|
11
|
+
async execute(request) {
|
|
12
|
+
const token = String(request.token ?? "").trim();
|
|
13
|
+
if (!token) {
|
|
14
|
+
// Conservador: si prefieres un error de dominio, puedes crear uno específico.
|
|
15
|
+
throw new Error("Email verification token is required");
|
|
16
|
+
}
|
|
17
|
+
const consumed = await this.emailVerificationToken.consume(token);
|
|
18
|
+
const user = await this.userRepository.findById(new object_values_1.Id(consumed.userId.getValue()));
|
|
19
|
+
if (!user)
|
|
20
|
+
throw new errors_1.UserNotFoundError();
|
|
21
|
+
user.verifyEmail();
|
|
22
|
+
await this.userRepository.update(user);
|
|
23
|
+
return { success: true, message: "Email verified successfully" };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.VerifyEmailUseCase = VerifyEmailUseCase;
|
|
@@ -25,6 +25,10 @@ export declare class User {
|
|
|
25
25
|
* Indica si el usuario está activo o inactivo
|
|
26
26
|
*/
|
|
27
27
|
private _isActive;
|
|
28
|
+
/**
|
|
29
|
+
* Nuevo: estado de verificación de email.
|
|
30
|
+
*/
|
|
31
|
+
private _isEmailVerified;
|
|
28
32
|
/**
|
|
29
33
|
* Fecha de creación del usuario
|
|
30
34
|
*/
|
|
@@ -58,6 +62,10 @@ export declare class User {
|
|
|
58
62
|
* Indica si el usuario está activo o inactivo
|
|
59
63
|
*/
|
|
60
64
|
get isActive(): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Indica si el correo fue verificado.
|
|
67
|
+
*/
|
|
68
|
+
get isEmailVerified(): boolean;
|
|
61
69
|
/**
|
|
62
70
|
* Obtiene la fecha de creación del usuario
|
|
63
71
|
*/
|
|
@@ -76,9 +84,13 @@ export declare class User {
|
|
|
76
84
|
deactivate(): void;
|
|
77
85
|
/**
|
|
78
86
|
* Evalúa si el usuario puede iniciar sesión
|
|
87
|
+
* Política de login:
|
|
88
|
+
* - activo
|
|
89
|
+
* - email verificado
|
|
79
90
|
* @returns Verdadero si el usuario puede iniciar sesión, falso en caso contrario
|
|
80
91
|
*/
|
|
81
92
|
canLogin(): boolean;
|
|
93
|
+
verifyEmail(): void;
|
|
82
94
|
/**
|
|
83
95
|
* Cambia la contraseña del usuario.
|
|
84
96
|
* Responsabilidad: el agregado actualiza su estado de forma consistente.
|
|
@@ -16,6 +16,7 @@ class User {
|
|
|
16
16
|
this._roles = props.roles;
|
|
17
17
|
this._password = props.password;
|
|
18
18
|
this._isActive = props.isActive;
|
|
19
|
+
this._isEmailVerified = props.isEmailVerified;
|
|
19
20
|
this._createdAt = props.createdAt;
|
|
20
21
|
this._updatedAt = props.updatedAt;
|
|
21
22
|
}
|
|
@@ -50,6 +51,12 @@ class User {
|
|
|
50
51
|
get isActive() {
|
|
51
52
|
return this._isActive;
|
|
52
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Indica si el correo fue verificado.
|
|
56
|
+
*/
|
|
57
|
+
get isEmailVerified() {
|
|
58
|
+
return this._isEmailVerified;
|
|
59
|
+
}
|
|
53
60
|
/**
|
|
54
61
|
* Obtiene la fecha de creación del usuario
|
|
55
62
|
*/
|
|
@@ -79,10 +86,19 @@ class User {
|
|
|
79
86
|
}
|
|
80
87
|
/**
|
|
81
88
|
* Evalúa si el usuario puede iniciar sesión
|
|
89
|
+
* Política de login:
|
|
90
|
+
* - activo
|
|
91
|
+
* - email verificado
|
|
82
92
|
* @returns Verdadero si el usuario puede iniciar sesión, falso en caso contrario
|
|
83
93
|
*/
|
|
84
94
|
canLogin() {
|
|
85
|
-
return this._isActive;
|
|
95
|
+
return this._isActive && this._isEmailVerified;
|
|
96
|
+
}
|
|
97
|
+
verifyEmail() {
|
|
98
|
+
if (this._isEmailVerified)
|
|
99
|
+
return;
|
|
100
|
+
this._isEmailVerified = true;
|
|
101
|
+
this._updatedAt = new Date();
|
|
86
102
|
}
|
|
87
103
|
/**
|
|
88
104
|
* Cambia la contraseña del usuario.
|
|
@@ -107,6 +123,7 @@ class User {
|
|
|
107
123
|
roles: roles,
|
|
108
124
|
password: new object_values_1.HashedPassword(hashedPassword),
|
|
109
125
|
isActive: true,
|
|
126
|
+
isEmailVerified: false,
|
|
110
127
|
createdAt: new Date(),
|
|
111
128
|
updatedAt: new Date(),
|
|
112
129
|
});
|
|
@@ -136,7 +153,7 @@ class User {
|
|
|
136
153
|
const roles = [];
|
|
137
154
|
const permissions = [];
|
|
138
155
|
for (const role of this.roles) {
|
|
139
|
-
const value = role.getValue(); //
|
|
156
|
+
const value = role.getValue(); // canónico (incluye permissions)
|
|
140
157
|
roles.push(value.role);
|
|
141
158
|
permissions.push(...value.permissions);
|
|
142
159
|
}
|
|
@@ -9,4 +9,4 @@
|
|
|
9
9
|
* - Mantén este catálogo pequeño y estable.
|
|
10
10
|
* - Si agregas un error nuevo, agrega aquí su código.
|
|
11
11
|
*/
|
|
12
|
-
export type AuthErrorCode = "TOKEN_INVALID" | "TOKEN_EXPIRED" | "TOKEN_MALFORMED" | "SIGNATURE_INVALID" | "AUTHENTICATION_FAILED" | "JWT_ERROR" | "KEY_MISMATCH" | "KEY_NOT_FOUND" | "KEY_MISMATCH" | "CLAIMS_VALIDATION_ERROR" | "JWT_PAYLOAD_INVALID" | "TOKEN_NOT_YET_VALID" | "ALGORITHM_UNSUPPORTED" | "KEY_MISMATCH" | "KEY_NOT_FOUND" | "INVALID_EMAIL" | "INVALID_HASHED_PASSWORD" | "PASSWORD_POLICY_VIOLATION" | "PASSWORD_MISMATCH" | "USER_NOT_FOUND" | "USER_DISABLED" | "EMAIL_ALREADY_IN_USE" | "INVALID_PERMISSION" | "INVALID_ROLE" | "INVALID_ID" | "LOGOUT_FAILED" | "PASSWORD_RESET_TOKEN_INVALID" | "PASSWORD_RESET_TOKEN_EXPIRED" | "PASSWORD_RESET_TOKEN_ALREADY_USED";
|
|
12
|
+
export type AuthErrorCode = "TOKEN_INVALID" | "TOKEN_EXPIRED" | "TOKEN_MALFORMED" | "SIGNATURE_INVALID" | "AUTHENTICATION_FAILED" | "JWT_ERROR" | "KEY_MISMATCH" | "KEY_NOT_FOUND" | "KEY_MISMATCH" | "CLAIMS_VALIDATION_ERROR" | "JWT_PAYLOAD_INVALID" | "TOKEN_NOT_YET_VALID" | "ALGORITHM_UNSUPPORTED" | "KEY_MISMATCH" | "KEY_NOT_FOUND" | "INVALID_EMAIL" | "INVALID_HASHED_PASSWORD" | "PASSWORD_POLICY_VIOLATION" | "PASSWORD_MISMATCH" | "USER_NOT_FOUND" | "USER_DISABLED" | "EMAIL_ALREADY_IN_USE" | "INVALID_PERMISSION" | "INVALID_ROLE" | "INVALID_ID" | "LOGOUT_FAILED" | "EMAIL_NOT_VERIFIED" | "PASSWORD_RESET_TOKEN_INVALID" | "PASSWORD_RESET_TOKEN_EXPIRED" | "PASSWORD_RESET_TOKEN_ALREADY_USED";
|
|
@@ -37,3 +37,6 @@ export declare class AuthenticationError extends AuthDomainError {
|
|
|
37
37
|
export declare class SessionAuthError extends AuthDomainError {
|
|
38
38
|
constructor(message?: string, details?: unknown);
|
|
39
39
|
}
|
|
40
|
+
export declare class EmailNotVerifiedError extends AuthDomainError {
|
|
41
|
+
constructor(message?: string, details?: unknown);
|
|
42
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SessionAuthError = exports.AuthenticationError = exports.InvalidSignatureError = exports.InvalidTokenFormatError = exports.TokenExpiredError = exports.AuthDomainError = void 0;
|
|
3
|
+
exports.EmailNotVerifiedError = exports.SessionAuthError = exports.AuthenticationError = exports.InvalidSignatureError = exports.InvalidTokenFormatError = exports.TokenExpiredError = exports.AuthDomainError = void 0;
|
|
4
4
|
class AuthDomainError extends Error {
|
|
5
5
|
constructor(message, code, details) {
|
|
6
6
|
super(message);
|
|
@@ -59,3 +59,9 @@ class SessionAuthError extends AuthDomainError {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
exports.SessionAuthError = SessionAuthError;
|
|
62
|
+
class EmailNotVerifiedError extends AuthDomainError {
|
|
63
|
+
constructor(message = "Email is not verified", details) {
|
|
64
|
+
super(message, "EMAIL_NOT_VERIFIED", details);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.EmailNotVerifiedError = EmailNotVerifiedError;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Id } from "../../object-values";
|
|
2
|
+
/**
|
|
3
|
+
* Token de verificación de email (single-use, TTL).
|
|
4
|
+
* Implementación fuera del core (DB/Redis).
|
|
5
|
+
*/
|
|
6
|
+
export interface IEmailVerificationTokenPort {
|
|
7
|
+
issue(userId: Id, ttlMs: number): Promise<{
|
|
8
|
+
token: string;
|
|
9
|
+
expiresAt: Date;
|
|
10
|
+
}>;
|
|
11
|
+
verify(token: string): Promise<{
|
|
12
|
+
userId: Id;
|
|
13
|
+
expiresAt: Date;
|
|
14
|
+
}>;
|
|
15
|
+
consume(token: string): Promise<{
|
|
16
|
+
userId: Id;
|
|
17
|
+
expiresAt: Date;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
@@ -17,3 +17,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./password-hasher.port"), exports);
|
|
18
18
|
__exportStar(require("./password-policy.port"), exports);
|
|
19
19
|
__exportStar(require("./password-reset-token.port"), exports);
|
|
20
|
+
__exportStar(require("./email-verification-token.port"), exports);
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
export interface JwtUser {
|
|
2
|
+
/**
|
|
3
|
+
* Identificador único del usuario (claim `sub`).
|
|
4
|
+
*/
|
|
2
5
|
id: string;
|
|
3
|
-
|
|
4
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Opcional: algunas apps pueden incluirlo, pero no es requerido por el core.
|
|
8
|
+
*/
|
|
9
|
+
email?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Opcional: JWT delgado (RBAC en BDD).
|
|
12
|
+
*/
|
|
13
|
+
roles?: {
|
|
5
14
|
role: string;
|
|
6
15
|
}[];
|
|
7
16
|
}
|
|
@@ -97,31 +97,18 @@ class TokenSessionService {
|
|
|
97
97
|
* @param sessionId Identificador de sesión/dispositivo.
|
|
98
98
|
*/
|
|
99
99
|
async issueTokens(user, sessionId) {
|
|
100
|
-
const snapshot = user.getAccessSnapshot();
|
|
101
100
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* - Si no hay permissions => customClaims vacío (JWT más limpio).
|
|
106
|
-
* - Si hay permissions => { permissions: string[] }.
|
|
107
|
-
*
|
|
108
|
-
* Esto hace que requirePermissions() pueda operar solo con JWT,
|
|
109
|
-
* sin requerir llamadas extra al resolver en cada request.
|
|
110
|
-
*/
|
|
111
|
-
const customClaims = snapshot.permissions.length > 0
|
|
112
|
-
? { permissions: snapshot.permissions }
|
|
113
|
-
: {};
|
|
114
|
-
/**
|
|
115
|
-
* userClaims:
|
|
116
|
-
* - Shape mínimo de usuario que el tokenService necesita
|
|
117
|
-
* - Evita pasar la entidad User completa (no serializable y con lógica)
|
|
101
|
+
* Política:
|
|
102
|
+
* - JWT delgado: NO roles, NO permissions.
|
|
103
|
+
* - Authorization se resuelve en el host (BDD) por middleware/servicio.
|
|
118
104
|
*/
|
|
119
105
|
const userClaims = {
|
|
120
106
|
id: user.id.getValue(),
|
|
121
|
-
email
|
|
122
|
-
// Mantienes el shape actual requerido por IJWTPayload.roles: Array<{role:string}>
|
|
123
|
-
roles: snapshot.roles.map((role) => ({ role })),
|
|
107
|
+
// email/roles son opcionales y NO se incluyen por defecto.
|
|
124
108
|
};
|
|
109
|
+
// Custom claims: no forzamos nada desde el core.
|
|
110
|
+
// Si el host quiere customClaims, debe hacerlo en su propio flujo/política.
|
|
111
|
+
const customClaims = {};
|
|
125
112
|
/**
|
|
126
113
|
* Generar access token:
|
|
127
114
|
* - sessionId se coloca en "sid"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ICredentialRepositoryPort, IPasswordHasherPort, IPasswordPolicyPort, ITokenServicePort, ITokenSessionPort, IUserRepositoryPort, IPasswordResetTokenPort } from "../../domain/ports";
|
|
2
|
-
import { ChangePasswordUseCase, LoginWithPasswordUseCase, LogoutUseCase, RefreshTokenUseCase, RegisterUserUseCase, RequestPasswordResetUseCase, ResetPasswordUseCase } from "../../application/use-cases";
|
|
1
|
+
import { ICredentialRepositoryPort, IPasswordHasherPort, IPasswordPolicyPort, ITokenServicePort, ITokenSessionPort, IUserRepositoryPort, IPasswordResetTokenPort, IEmailVerificationTokenPort } from "../../domain/ports";
|
|
2
|
+
import { ChangePasswordUseCase, LoginWithPasswordUseCase, LogoutUseCase, MeUseCase, RefreshTokenUseCase, RegisterUserUseCase, RequestPasswordResetUseCase, ResetPasswordUseCase, VerifyEmailUseCase } from "../../application/use-cases";
|
|
3
3
|
/**
|
|
4
4
|
* IAuthServiceContainer es el punto de composición del módulo de autenticación
|
|
5
5
|
* Es un contrato que agrupa todas las dependencias y casos de uso de Auth ya construidos,
|
|
@@ -13,6 +13,14 @@ export interface IAuthServiceContainer {
|
|
|
13
13
|
passwordPolicy: IPasswordPolicyPort;
|
|
14
14
|
tokenSession: ITokenSessionPort;
|
|
15
15
|
passwordResetToken: IPasswordResetTokenPort;
|
|
16
|
+
/**
|
|
17
|
+
* Puerto para la gestión de tokens de verificación de email.
|
|
18
|
+
*
|
|
19
|
+
* Responsabilidad:
|
|
20
|
+
* - Emitir tokens temporales (single-use) asociados a un usuario.
|
|
21
|
+
* - Validar y consumir el token cuando el usuario accede al enlace de verificación.
|
|
22
|
+
*/
|
|
23
|
+
emailVerificationToken: IEmailVerificationTokenPort;
|
|
16
24
|
registerUserUseCase: RegisterUserUseCase;
|
|
17
25
|
loginWithPasswordUseCase: LoginWithPasswordUseCase;
|
|
18
26
|
refreshTokenUseCase: RefreshTokenUseCase;
|
|
@@ -20,4 +28,6 @@ export interface IAuthServiceContainer {
|
|
|
20
28
|
requestPasswordResetUseCase: RequestPasswordResetUseCase;
|
|
21
29
|
resetPasswordUseCase: ResetPasswordUseCase;
|
|
22
30
|
changePasswordUseCase: ChangePasswordUseCase;
|
|
31
|
+
verifyEmailUseCase: VerifyEmailUseCase;
|
|
32
|
+
meUseCase: MeUseCase;
|
|
23
33
|
}
|