erp-core/docs/03-requerimientos/RF-auth/RF-AUTH-005.md

15 KiB

RF-AUTH-005: Recuperacion de Password

Identificacion

Campo Valor
ID RF-AUTH-005
Modulo MGN-001
Nombre Modulo Auth - Autenticacion
Prioridad P1
Complejidad Media
Estado Aprobado
Autor System
Fecha 2025-12-05

Descripcion

El sistema debe permitir a los usuarios recuperar el acceso a su cuenta cuando olvidan su contraseña. El proceso incluye solicitar un enlace de recuperacion por email, validar el token de recuperacion, y establecer una nueva contraseña de forma segura.

Contexto de Negocio

La recuperacion de password es un proceso critico que debe balancear:

  • Usabilidad: El usuario debe poder recuperar acceso facilmente
  • Seguridad: El proceso no debe permitir acceso no autorizado
  • Cumplimiento: Debe registrarse para auditoria y prevencion de abuso

Criterios de Aceptacion

  • CA-001: El sistema debe generar un token unico de recuperacion con expiracion de 1 hora
  • CA-002: El sistema debe enviar email con enlace de recuperacion
  • CA-003: El sistema debe validar el token antes de permitir cambio de password
  • CA-004: El sistema debe invalidar el token despues de un uso exitoso
  • CA-005: El sistema debe invalidar el token despues de 3 intentos fallidos
  • CA-006: El sistema debe aplicar las mismas reglas de complejidad al nuevo password
  • CA-007: El sistema debe forzar logout de todas las sesiones al cambiar password
  • CA-008: El sistema debe notificar al usuario via email que su password fue cambiado
  • CA-009: El sistema NO debe revelar si el email existe o no (seguridad)

Ejemplos de Verificacion

Scenario: Solicitud de recuperacion exitosa
  Given un usuario registrado con email "user@example.com"
  When el usuario solicita recuperacion de password
  Then el sistema genera un token de recuperacion
  And envia email con enlace de recuperacion
  And responde con mensaje generico "Si el email existe, recibiras instrucciones"
  And el token expira en 1 hora

Scenario: Cambio de password exitoso
  Given un usuario con token de recuperacion valido
  When el usuario envia nuevo password cumpliendo requisitos
  Then el sistema actualiza el password hasheado
  And invalida el token de recuperacion
  And cierra todas las sesiones activas del usuario
  And envia email confirmando el cambio
  And responde con status 200

Scenario: Token de recuperacion expirado
  Given un token de recuperacion emitido hace mas de 1 hora
  When el usuario intenta usarlo para cambiar password
  Then el sistema responde con status 400
  And el mensaje es "Token de recuperacion expirado"

Scenario: Email no registrado (seguridad)
  Given un email que NO existe en el sistema
  When alguien solicita recuperacion para ese email
  Then el sistema responde con el mismo mensaje generico
  And NO envia ningun email
  And NO revela que el email no existe

Reglas de Negocio

ID Regla Validacion
RN-001 El token de recuperacion expira en 1 hora Campo expires_at
RN-002 Solo un token activo por usuario Invalida tokens anteriores
RN-003 Maximo 3 solicitudes por hora por email Rate limiting
RN-004 El nuevo password no puede ser igual al anterior Comparar hashes
RN-005 El nuevo password debe cumplir politica de complejidad Min 8 chars, mayus, minus, numero
RN-006 Cambio de password fuerza logout-all Seguridad
RN-007 Respuesta generica para solicitud (no revelar existencia) Mensaje fijo
RN-008 Token de uso unico Invalida inmediatamente despues de uso

Politica de Complejidad de Password

- Minimo 8 caracteres
- Al menos 1 letra mayuscula
- Al menos 1 letra minuscula
- Al menos 1 numero
- Al menos 1 caracter especial (!@#$%^&*)
- No puede contener el email del usuario
- No puede ser igual a los ultimos 5 passwords

Impacto en Capas

Database

Elemento Accion Descripcion
Tabla crear password_reset_tokens
Columna - id UUID PK
Columna - user_id UUID FK → users
Columna - token_hash VARCHAR(255)
Columna - expires_at TIMESTAMPTZ
Columna - used_at TIMESTAMPTZ NULL
Columna - attempts INTEGER DEFAULT 0
Columna - created_at TIMESTAMPTZ
Tabla crear password_history - historial de passwords
Indice crear idx_password_reset_tokens_user
Indice crear idx_password_reset_tokens_expires

Backend

Elemento Accion Descripcion
Controller crear AuthController.requestPasswordReset()
Controller crear AuthController.resetPassword()
Method crear PasswordService.generateResetToken()
Method crear PasswordService.validateResetToken()
Method crear PasswordService.resetPassword()
Method crear PasswordService.validatePasswordPolicy()
DTO crear RequestPasswordResetDto
DTO crear ResetPasswordDto
Service usar EmailService.sendPasswordResetEmail()
Endpoint crear POST /api/v1/auth/password/request-reset
Endpoint crear POST /api/v1/auth/password/reset
Endpoint crear GET /api/v1/auth/password/validate-token/:token

Frontend

Elemento Accion Descripcion
Pagina crear ForgotPasswordPage
Pagina crear ResetPasswordPage
Componente crear ForgotPasswordForm
Componente crear ResetPasswordForm
Componente crear PasswordStrengthIndicator
Service crear passwordService.requestReset()
Service crear passwordService.resetPassword()

Dependencias

Depende de (Bloqueantes)

ID Requerimiento Estado
RF-AUTH-001 Login Estructura de usuarios
RF-AUTH-004 Logout Logout-all despues de cambio

Dependencias Externas

Servicio Descripcion
Email Service Envio de emails transaccionales
Template Engine Templates de email

Especificaciones Tecnicas

Flujo de Recuperacion

┌─────────────────────────────────────────────────────────────────┐
│ FASE 1: SOLICITUD DE RECUPERACION                                │
├─────────────────────────────────────────────────────────────────┤
│ 1. Usuario ingresa email en formulario                          │
│ 2. Frontend POST /api/v1/auth/password/request-reset            │
│ 3. Backend busca usuario por email                              │
│    - Si existe: genera token, envia email                       │
│    - Si no existe: no hace nada (seguridad)                     │
│ 4. Backend responde con mensaje generico                        │
│    "Si el email esta registrado, recibiras instrucciones"       │
└─────────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────────┐
│ FASE 2: EMAIL DE RECUPERACION                                    │
├─────────────────────────────────────────────────────────────────┤
│ 1. Usuario recibe email con enlace                              │
│    https://app.erp.com/reset-password?token={token}             │
│ 2. El enlace incluye token de uso unico (NO el hash)            │
│ 3. El token tiene validez de 1 hora                             │
└─────────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────────┐
│ FASE 3: VALIDACION DE TOKEN                                      │
├─────────────────────────────────────────────────────────────────┤
│ 1. Usuario hace clic en enlace                                  │
│ 2. Frontend GET /api/v1/auth/password/validate-token/:token     │
│ 3. Backend valida:                                               │
│    - Token existe                                                │
│    - Token no expirado                                           │
│    - Token no usado                                              │
│    - Intentos < 3                                                │
│ 4. Si valido: muestra formulario de nuevo password              │
│    Si invalido: muestra error apropiado                         │
└─────────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────────┐
│ FASE 4: CAMBIO DE PASSWORD                                       │
├─────────────────────────────────────────────────────────────────┤
│ 1. Usuario ingresa nuevo password (2 veces)                     │
│ 2. Frontend POST /api/v1/auth/password/reset                    │
│    Body: { token, newPassword }                                 │
│ 3. Backend:                                                      │
│    a. Valida token nuevamente                                    │
│    b. Valida politica de password                               │
│    c. Verifica no sea igual a anteriores                        │
│    d. Hashea nuevo password                                      │
│    e. Actualiza users.password_hash                             │
│    f. Marca token como usado                                     │
│    g. Guarda en password_history                                │
│    h. Ejecuta logout-all                                         │
│    i. Envia email de confirmacion                               │
│ 4. Responde 200 OK                                               │
└─────────────────────────────────────────────────────────────────┘

Generacion de Token Seguro

async generateResetToken(email: string): Promise<void> {
  const user = await this.userRepo.findByEmail(email);

  // IMPORTANTE: No revelar si el usuario existe
  if (!user) {
    // Log para auditoria pero no revelar al cliente
    this.logger.warn(`Password reset requested for non-existent email: ${email}`);
    return; // Respuesta identica a caso exitoso
  }

  // Invalida tokens anteriores
  await this.passwordResetRepo.invalidateUserTokens(user.id);

  // Genera token seguro (32 bytes = 256 bits)
  const token = crypto.randomBytes(32).toString('hex');
  const tokenHash = await bcrypt.hash(token, 10);

  // Guarda en BD
  await this.passwordResetRepo.create({
    userId: user.id,
    tokenHash,
    expiresAt: addHours(new Date(), 1),
    attempts: 0,
  });

  // Envia email (async, no bloquea respuesta)
  await this.emailService.sendPasswordResetEmail(user.email, token);
}

Template de Email

<h2>Recuperacion de Contraseña</h2>
<p>Hola {{userName}},</p>
<p>Recibimos una solicitud para restablecer tu contraseña.</p>
<p>Haz clic en el siguiente enlace para crear una nueva contraseña:</p>
<a href="{{resetUrl}}">Restablecer Contraseña</a>
<p>Este enlace expira en 1 hora.</p>
<p>Si no solicitaste este cambio, ignora este email.</p>
<p><strong>Por seguridad, nunca compartas este enlace.</strong></p>

Datos de Prueba

Escenario Entrada Resultado
Solicitud email existente email: "test@erp.com" 200, email enviado
Solicitud email no existe email: "noexiste@erp.com" 200, mensaje generico (no revela)
Token valido Token < 1 hora 200, permite cambio
Token expirado Token > 1 hora 400, "Token expirado"
Token usado Token ya utilizado 400, "Token ya utilizado"
Password debil "123456" 400, "Password no cumple requisitos"
Password igual anterior Mismo que actual 400, "No puede ser igual al anterior"
Demasiados intentos 3+ intentos fallidos 400, "Token invalidado"

Estimacion

Capa Story Points Notas
Database 2 Tablas password_reset_tokens, password_history
Backend 5 Services, validaciones, email
Frontend 3 Formularios, validacion, UX
Total 10

Notas Adicionales

  • Usar crypto.randomBytes para generacion de tokens (no UUID)
  • Almacenar solo el HASH del token, no el token plano
  • Implementar rate limiting estricto para prevenir enumeracion de emails
  • Los enlaces de reset deben ser HTTPS obligatoriamente
  • Considerar CAPTCHA para solicitudes de recuperacion
  • Implementar honeypot para detectar bots
  • El email de confirmacion de cambio debe incluir IP y timestamp

Consideraciones de Seguridad

Amenaza Mitigacion
Enumeracion de emails Respuesta identica para email existe/no existe
Fuerza bruta en token Token de 256 bits, max 3 intentos
Intercepcion de email HTTPS, token de uso unico
Session fixation Logout-all despues de cambio
Password spraying Rate limiting, CAPTCHA

Historial de Cambios

Version Fecha Autor Cambios
1.0 2025-12-05 System Creacion inicial

Aprobaciones

Rol Nombre Fecha Firma
Analista System 2025-12-05 [x]
Tech Lead - - [ ]
Product Owner - - [ ]