@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.
Files changed (43) hide show
  1. package/dist/application/dtos/request/index.d.ts +2 -0
  2. package/dist/application/dtos/request/index.js +2 -0
  3. package/dist/application/dtos/request/me.request.d.ts +3 -0
  4. package/dist/application/dtos/request/me.request.js +2 -0
  5. package/dist/application/dtos/request/verify-email.request.d.ts +3 -0
  6. package/dist/application/dtos/request/verify-email.request.js +2 -0
  7. package/dist/application/dtos/response/index.d.ts +2 -0
  8. package/dist/application/dtos/response/index.js +2 -0
  9. package/dist/application/dtos/response/me.response.d.ts +11 -0
  10. package/dist/application/dtos/response/me.response.js +2 -0
  11. package/dist/application/dtos/response/register-user.response.d.ts +9 -0
  12. package/dist/application/dtos/response/verify-email.response.d.ts +4 -0
  13. package/dist/application/dtos/response/verify-email.response.js +2 -0
  14. package/dist/application/facades/auth.facade.d.ts +4 -2
  15. package/dist/application/facades/auth.facade.js +6 -0
  16. package/dist/application/facades/create-auth-facade.d.ts +2 -1
  17. package/dist/application/facades/create-auth-facade.js +1 -1
  18. package/dist/application/factories/auth-service.factory.d.ts +2 -2
  19. package/dist/application/factories/auth-service.factory.js +7 -2
  20. package/dist/application/types/auth-service-factory-options.type.d.ts +1 -0
  21. package/dist/application/use-cases/index.d.ts +2 -0
  22. package/dist/application/use-cases/index.js +2 -0
  23. package/dist/application/use-cases/login-with-password.use-case.js +4 -1
  24. package/dist/application/use-cases/me.use-case.d.ts +7 -0
  25. package/dist/application/use-cases/me.use-case.js +28 -0
  26. package/dist/application/use-cases/register-user.use-case.d.ts +6 -1
  27. package/dist/application/use-cases/register-user.use-case.js +12 -1
  28. package/dist/application/use-cases/verify-email.use-case.d.ts +8 -0
  29. package/dist/application/use-cases/verify-email.use-case.js +26 -0
  30. package/dist/domain/entities/user.entity.d.ts +12 -0
  31. package/dist/domain/entities/user.entity.js +19 -2
  32. package/dist/domain/errors/auth-error-code.d.ts +1 -1
  33. package/dist/domain/errors/auth.errors.d.ts +3 -0
  34. package/dist/domain/errors/auth.errors.js +7 -1
  35. package/dist/domain/ports/auth/email-verification-token.port.d.ts +19 -0
  36. package/dist/domain/ports/auth/email-verification-token.port.js +2 -0
  37. package/dist/domain/ports/auth/index.d.ts +1 -0
  38. package/dist/domain/ports/auth/index.js +1 -0
  39. package/dist/domain/props/entities/user.props.d.ts +1 -0
  40. package/dist/domain/props/jwt/jwt-user.d.ts +11 -2
  41. package/dist/infrastructure/services/token-session.service.js +7 -20
  42. package/dist/infrastructure/types/auth-service-container.d.ts +12 -2
  43. package/package.json +1 -1
@@ -5,3 +5,5 @@ export * from "./register-user.request";
5
5
  export * from "./request-password-reset.request";
6
6
  export * from "./reset-password.request";
7
7
  export * from "./change-password.request";
8
+ export * from "./verify-email.request";
9
+ export * from "./me.request";
@@ -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);
@@ -0,0 +1,3 @@
1
+ export interface MeRequest {
2
+ userId: string;
3
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,3 @@
1
+ export interface VerifyEmailRequest {
2
+ token: string;
3
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -5,3 +5,5 @@ export * from "./register-user.response";
5
5
  export * from "./reset-password.response";
6
6
  export * from "./request-password-reset.response";
7
7
  export * from "./change-password.response";
8
+ export * from "./verify-email.response";
9
+ export * from "./me.response";
@@ -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);
@@ -0,0 +1,11 @@
1
+ export interface MeResponse {
2
+ id: string;
3
+ email: string;
4
+ isActive: boolean;
5
+ isEmailVerified: boolean;
6
+ roles: {
7
+ role: string;
8
+ }[];
9
+ createdAt: string;
10
+ updatedAt: string;
11
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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
  }
@@ -0,0 +1,4 @@
1
+ export interface VerifyEmailResponse {
2
+ success: true;
3
+ message: string;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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
  }
@@ -45,4 +45,5 @@ export interface AuthServiceFactoryOptions {
45
45
  * - Más largo ⇒ mejor conveniencia, pero incrementa exposición.
46
46
  */
47
47
  passwordResetTokenTtl?: string;
48
+ emailVerificationTokenTtl?: string;
48
49
  }
@@ -5,3 +5,5 @@ export * from "./register-user.use-case";
5
5
  export * from "./change-password.use-case";
6
6
  export * from "./request-password-reset.use-case";
7
7
  export * from "./reset-password.use-case";
8
+ export * from "./verify-email.use-case";
9
+ export * from "./me.use-case";
@@ -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.canLogin()) {
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
- constructor(userRepository: IUserRepositoryPort, passwordHasher: IPasswordHasherPort, passwordPolicy: IPasswordPolicyPort);
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(); // canónico (incluye permissions)
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
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,3 +1,4 @@
1
1
  export * from "./password-hasher.port";
2
2
  export * from "./password-policy.port";
3
3
  export * from "./password-reset-token.port";
4
+ export * from "./email-verification-token.port";
@@ -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);
@@ -5,6 +5,7 @@ export interface IUserProps {
5
5
  roles: Role[];
6
6
  password: HashedPassword;
7
7
  isActive: boolean;
8
+ isEmailVerified: boolean;
8
9
  createdAt: Date;
9
10
  updatedAt: Date;
10
11
  }
@@ -1,7 +1,16 @@
1
1
  export interface JwtUser {
2
+ /**
3
+ * Identificador único del usuario (claim `sub`).
4
+ */
2
5
  id: string;
3
- email: string;
4
- roles: {
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
- * Custom claims mínimos que necesita el API para requirePermissions().
103
- *
104
- * Decisión:
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: user.email.getValue(),
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
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jmlq/auth",
3
3
  "description": "JWT authentication package with clean architecture",
4
- "version": "0.0.1-alpha.29",
4
+ "version": "0.0.1-alpha.30",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {