---
id: "RF-AUTH-004"
title: "Two-Factor Authentication (2FA)"
type: "Requirement"
status: "Done"
priority: "Alta"
module: "auth"
epic: "OQI-001"
version: "1.0"
created_date: "2025-12-05"
updated_date: "2026-01-04"
---

# RF-AUTH-004: Two-Factor Authentication (2FA)

**Version:** 1.0.0
**Fecha:** 2025-12-05
**Estado:** ✅ Implementado
**Prioridad:** P1 (Alta)
**Épica:** [OQI-001](../_MAP.md)

---

## Descripción

El sistema debe permitir a los usuarios habilitar autenticación de dos factores (2FA) usando TOTP (Time-based One-Time Password), compatible con aplicaciones como Google Authenticator, Authy, y 1Password.

---

## Objetivo de Negocio

- Aumentar seguridad de cuentas con fondos
- Cumplir con estándares de seguridad financiera
- Reducir riesgo de acceso no autorizado
- Generar confianza en la plataforma

---

## Requisitos Funcionales

### RF-AUTH-004.1: Configuración de 2FA

**DEBE:**
1. Generar secreto TOTP único por usuario
2. Generar código QR para escanear
3. Mostrar secreto manual como alternativa
4. Requerir verificación antes de activar
5. Generar backup codes (10 códigos)

### RF-AUTH-004.2: Verificación 2FA

**DEBE:**
1. Solicitar código 2FA después de login exitoso
2. Validar código TOTP (ventana de ±1)
3. Aceptar backup codes como alternativa
4. Invalidar backup code después de uso
5. Bloquear después de 5 intentos fallidos

### RF-AUTH-004.3: Desactivación de 2FA

**DEBE:**
1. Requerir código 2FA válido para desactivar
2. Alternativa: backup code para desactivar
3. Invalidar todos los backup codes
4. Notificar por email al desactivar

### RF-AUTH-004.4: Recuperación de 2FA

**DEBE:**
1. Permitir uso de backup codes si pierde dispositivo
2. Proceso de recuperación con verificación de identidad
3. Notificar intentos de recuperación sospechosos

---

## Especificación TOTP

### Parámetros

| Parámetro | Valor |
|-----------|-------|
| Algoritmo | SHA1 |
| Dígitos | 6 |
| Período | 30 segundos |
| Ventana | ±1 período (tolerancia) |

### Generación de Secreto

```typescript
import speakeasy from 'speakeasy';

const secret = speakeasy.generateSecret({
  length: 20,
  name: 'Trading Platform',
  issuer: 'Trading Platform',
});

// secret.base32 → almacenar encriptado
// secret.otpauth_url → para QR code
```

### Verificación de Código

```typescript
const verified = speakeasy.totp.verify({
  secret: userSecret,
  encoding: 'base32',
  token: userCode,
  window: 1, // ±30 segundos
});
```

---

## Flujo de Configuración

```
Usuario                    Frontend                   Backend                    DB
   │                          │                          │                        │
   │─── "Activar 2FA" ───────▶│                          │                        │
   │                          │                          │                        │
   │                          │─── POST /auth/2fa/setup ▶│                        │
   │                          │                          │                        │
   │                          │                          │─── Generate secret ────│
   │                          │                          │─── Generate QR code ───│
   │                          │                          │─── Store temp secret ──│
   │                          │                          │                        │
   │                          │◀── 200 OK ───────────────│                        │
   │                          │    { qrCode, secret }    │                        │
   │                          │                          │                        │
   │◀── Muestra QR code ──────│                          │                        │
   │                          │                          │                        │
   │─── Escanea con App ──────│                          │                        │
   │                          │                          │                        │
   │─── Ingresa código ──────▶│                          │                        │
   │    "123456"              │                          │                        │
   │                          │                          │                        │
   │                          │─── POST /auth/2fa/enable▶│                        │
   │                          │    { code: "123456" }    │                        │
   │                          │                          │                        │
   │                          │                          │─── Verify TOTP ────────│
   │                          │                          │                        │
   │                          │                          │─── Generate backup ────│
   │                          │                          │    codes (10)          │
   │                          │                          │                        │
   │                          │                          │─── UPDATE user ───────▶│
   │                          │                          │    2fa_enabled = true  │
   │                          │                          │    2fa_secret = xxx    │
   │                          │                          │                        │
   │                          │◀── 200 OK ───────────────│                        │
   │                          │    { backupCodes }       │                        │
   │                          │                          │                        │
   │◀── Muestra backup codes ─│                          │                        │
   │    "Guarda estos códigos"│                          │                        │
```

---

## Flujo de Login con 2FA

```
Usuario                    Frontend                   Backend                    DB
   │                          │                          │                        │
   │─── Login email/pass ────▶│                          │                        │
   │                          │                          │                        │
   │                          │─── POST /auth/login ────▶│                        │
   │                          │                          │                        │
   │                          │                          │─── Validate creds ─────│
   │                          │                          │─── Check 2FA enabled ──│
   │                          │                          │                        │
   │                          │◀── 200 OK ───────────────│                        │
   │                          │    { requires2FA: true,  │                        │
   │                          │      tempToken }         │                        │
   │                          │                          │                        │
   │◀── Muestra input 2FA ────│                          │                        │
   │                          │                          │                        │
   │─── Ingresa código ──────▶│                          │                        │
   │    "123456"              │                          │                        │
   │                          │                          │                        │
   │                          │─── POST /auth/2fa/verify▶│                        │
   │                          │    { tempToken, code }   │                        │
   │                          │                          │                        │
   │                          │                          │─── Verify tempToken ───│
   │                          │                          │─── Verify TOTP ────────│
   │                          │                          │─── Generate JWT ───────│
   │                          │                          │─── Create session ────▶│
   │                          │                          │                        │
   │                          │◀── 200 OK ───────────────│                        │
   │                          │    { tokens, user }      │                        │
   │                          │                          │                        │
   │◀── Dashboard ────────────│                          │                        │
```

---

## Backup Codes

### Generación

```typescript
function generateBackupCodes(count: number = 10): string[] {
  const codes: string[] = [];
  for (let i = 0; i < count; i++) {
    // 8 caracteres alfanuméricos en mayúsculas
    const code = crypto.randomBytes(4).toString('hex').toUpperCase();
    codes.push(code);
  }
  return codes;
}

// Ejemplo: ["A1B2C3D4", "E5F6G7H8", ...]
```

### Almacenamiento

```typescript
// Hashear cada código antes de almacenar
const hashedCodes = backupCodes.map(code =>
  bcrypt.hashSync(code, 10)
);

// Almacenar en DB
await db.query(`
  UPDATE users
  SET backup_codes = $1
  WHERE id = $2
`, [JSON.stringify(hashedCodes), userId]);
```

### Uso de Backup Code

1. Usuario ingresa backup code en lugar de TOTP
2. Sistema busca en lista de códigos hasheados
3. Si coincide, invalida ese código (elimina de lista)
4. Permite acceso
5. Notifica por email que se usó backup code

---

## Interfaz de Usuario

### Pantalla de Configuración

```
┌─────────────────────────────────────────────────────────────┐
│                    Configurar 2FA                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. Escanea este código QR con tu aplicación               │
│                                                             │
│     ┌─────────────────┐                                    │
│     │  ▄▄▄▄▄  ▄▄▄▄▄  │                                    │
│     │  █   █  █   █  │    Google Authenticator            │
│     │  █████  █████  │    Microsoft Authenticator          │
│     │  █   █  █   █  │    Authy                            │
│     │  ▀▀▀▀▀  ▀▀▀▀▀  │    1Password                        │
│     └─────────────────┘                                    │
│                                                             │
│  2. O ingresa este código manualmente:                     │
│     JBSWY3DPEHPK3PXP                                       │
│                                                             │
│  3. Ingresa el código de 6 dígitos:                        │
│     ┌──────────────────────────┐                           │
│     │  _ _ _ _ _ _             │                           │
│     └──────────────────────────┘                           │
│                                                             │
│     [Cancelar]              [Verificar y Activar]          │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### Pantalla de Backup Codes

```
┌─────────────────────────────────────────────────────────────┐
│                    Códigos de Respaldo                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ⚠️  Guarda estos códigos en un lugar seguro.               │
│      Cada código solo puede usarse una vez.                 │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  A1B2C3D4    E5F6G7H8    I9J0K1L2    M3N4O5P6      │   │
│  │  Q7R8S9T0    U1V2W3X4    Y5Z6A7B8    C9D0E1F2      │   │
│  │  G3H4I5J6    K7L8M9N0                              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  [Descargar como archivo]  [Copiar al portapapeles]        │
│                                                             │
│  ☑ He guardado mis códigos de respaldo                     │
│                                                             │
│                                         [Continuar]         │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

---

## Manejo de Errores

| Error | Código | Mensaje Usuario |
|-------|--------|-----------------|
| Código inválido | 401 | Código incorrecto, intenta de nuevo |
| Código expirado | 401 | Código expirado, usa el actual |
| Muchos intentos | 429 | Demasiados intentos, espera X minutos |
| 2FA no configurado | 400 | 2FA no está configurado |
| Backup code inválido | 401 | Código de respaldo inválido |
| Sin backup codes | 400 | No tienes códigos de respaldo |

---

## Notificaciones por Email

### 2FA Activado

```
Asunto: 2FA activado en tu cuenta Trading Platform

Hola {{firstName}},

La autenticación de dos factores ha sido activada en tu cuenta.

Fecha: {{date}}
Dispositivo: {{device}}
IP: {{ip}}

Si no realizaste esta acción, contacta soporte inmediatamente.

Saludos,
Equipo Trading Platform
```

### Backup Code Usado

```
Asunto: ⚠️ Código de respaldo usado en tu cuenta

Hola {{firstName}},

Se usó un código de respaldo para acceder a tu cuenta.

Fecha: {{date}}
IP: {{ip}}

Códigos restantes: {{remainingCodes}}

Si no fuiste tú, cambia tu contraseña y contacta soporte.

Saludos,
Equipo Trading Platform
```

---

## Seguridad

### Almacenamiento del Secreto

1. **Encriptar** secreto TOTP antes de almacenar
2. Usar **AES-256-GCM** para encriptación
3. Clave de encriptación en variables de entorno
4. **No** loguear secretos TOTP

### Protección contra Replay

1. **Tracking** de códigos usados (últimos 60 segundos)
2. **Rechazar** códigos ya usados en la misma ventana
3. **Limpiar** tracking después de cada ventana

---

## Criterios de Aceptación

- [ ] Usuario puede generar secreto TOTP
- [ ] Código QR se muestra correctamente
- [ ] Código manual se puede copiar
- [ ] Usuario puede verificar y activar 2FA
- [ ] Se generan 10 backup codes
- [ ] Login requiere 2FA si está activado
- [ ] Códigos TOTP válidos permiten acceso
- [ ] Backup codes funcionan como alternativa
- [ ] Backup codes se invalidan después de uso
- [ ] Usuario puede desactivar 2FA
- [ ] Notificaciones se envían correctamente

---

## Especificación Técnica Relacionada

- [ET-AUTH-005: Seguridad](../especificaciones/ET-AUTH-005-security.md)

## Historias de Usuario Relacionadas

- [US-AUTH-010: Configurar 2FA](../historias-usuario/US-AUTH-010-2fa-setup.md)
