# RF-AUTH-003: Refresh Token y Renovacion de Sesion

## Identificacion

| Campo | Valor |
|-------|-------|
| **ID** | RF-AUTH-003 |
| **Modulo** | MGN-001 |
| **Nombre Modulo** | Auth - Autenticacion |
| **Prioridad** | P0 |
| **Complejidad** | Media |
| **Estado** | Aprobado |
| **Autor** | System |
| **Fecha** | 2025-12-05 |

---

## Descripcion

El sistema debe permitir renovar el access token utilizando un refresh token valido, sin requerir que el usuario vuelva a ingresar sus credenciales. Esto permite mantener sesiones de larga duracion de forma segura, mientras los access tokens tienen vida corta.

### Contexto de Negocio

Los access tokens tienen vida corta (15 minutos) por seguridad. Sin un mecanismo de refresh, los usuarios tendrian que re-autenticarse constantemente, lo cual afecta negativamente la experiencia de usuario. El refresh token permite mantener la sesion activa hasta 7 dias sin comprometer la seguridad.

---

## Criterios de Aceptacion

- [x] **CA-001:** El sistema debe aceptar un refresh token valido y generar nuevos tokens
- [x] **CA-002:** El nuevo access token debe tener los mismos claims que el original
- [x] **CA-003:** El refresh token usado debe invalidarse (rotacion de tokens)
- [x] **CA-004:** Se debe generar un nuevo refresh token con cada renovacion
- [x] **CA-005:** El sistema debe rechazar refresh tokens expirados con error 401
- [x] **CA-006:** El sistema debe rechazar refresh tokens revocados con error 401
- [x] **CA-007:** El sistema debe detectar y prevenir reuso de refresh tokens (token replay)
- [x] **CA-008:** El frontend debe renovar automaticamente antes de que expire el access token

### Ejemplos de Verificacion

```gherkin
Scenario: Renovacion exitosa de tokens
  Given un usuario con refresh token valido
  When el usuario envia el refresh token a /api/v1/auth/refresh
  Then el sistema invalida el refresh token anterior
  And genera un nuevo par de tokens (access + refresh)
  And responde con status 200 y los nuevos tokens

Scenario: Refresh token expirado
  Given un usuario con refresh token expirado
  When el usuario intenta renovar tokens
  Then el sistema responde con status 401
  And el mensaje es "Refresh token expirado"
  And el usuario debe hacer login nuevamente

Scenario: Deteccion de token replay (reuso)
  Given un refresh token que ya fue usado para renovar
  When alguien intenta usar ese mismo refresh token
  Then el sistema detecta el reuso
  And invalida TODOS los tokens del usuario (seguridad)
  And responde con status 401
  And el mensaje es "Sesion comprometida, por favor inicie sesion"
```

---

## Reglas de Negocio

| ID | Regla | Validacion |
|----|-------|------------|
| RN-001 | Refresh token valido por 7 dias | JWT exp claim |
| RN-002 | Cada refresh genera nuevo refresh token (rotacion) | Token replacement |
| RN-003 | Refresh token usado se invalida inmediatamente | Marcar como usado en BD |
| RN-004 | Reuso de refresh token invalida toda la familia | Revocar todos tokens del usuario |
| RN-005 | Maximo 5 sesiones activas por usuario | Contador de sesiones |
| RN-006 | El refresh se hace 1 minuto antes de expiracion | Frontend timer |

### Token Family (Familia de Tokens)

Cada refresh token pertenece a una "familia" que se origina en un login. Si se detecta reuso de un token de esa familia, toda la familia se invalida.

```
Login -> RT1 -> RT2 -> RT3 (familia activa)
                  ↳ RT2 reusado? -> Invalida RT1, RT2, RT3
```

---

## Impacto en Capas

### Database

| Elemento | Accion | Descripcion |
|----------|--------|-------------|
| Tabla | modificar | `refresh_tokens` - agregar campos |
| Columna | agregar | `family_id` UUID - familia del token |
| Columna | agregar | `is_used` BOOLEAN - si ya fue usado |
| Columna | agregar | `used_at` TIMESTAMPTZ - cuando se uso |
| Columna | agregar | `replaced_by` UUID - token que lo reemplazo |
| Indice | crear | `idx_refresh_tokens_family` |

### Backend

| Elemento | Accion | Descripcion |
|----------|--------|-------------|
| Controller | crear | `AuthController.refresh()` |
| Method | crear | `TokenService.refreshTokens()` |
| Method | crear | `TokenService.detectTokenReuse()` |
| Method | crear | `TokenService.revokeTokenFamily()` |
| DTO | crear | `RefreshTokenDto` |
| DTO | crear | `TokenResponseDto` |
| Endpoint | crear | `POST /api/v1/auth/refresh` |

### Frontend

| Elemento | Accion | Descripcion |
|----------|--------|-------------|
| Service | modificar | `tokenService.refreshTokens()` |
| Interceptor | crear | Auto-refresh interceptor |
| Timer | crear | Refresh timer (1 min antes de exp) |
| Handler | crear | Token expiration handler |

---

## Dependencias

### Depende de (Bloqueantes)

| ID | Requerimiento | Estado |
|----|---------------|--------|
| RF-AUTH-001 | Login | Genera tokens iniciales |
| RF-AUTH-002 | JWT Tokens | Estructura de tokens |

### Dependencias Relacionadas

| ID | Requerimiento | Relacion |
|----|---------------|----------|
| RF-AUTH-004 | Logout | Revoca refresh tokens |

---

## Especificaciones Tecnicas

### Flujo de Refresh

```
┌─────────────────────────────────────────────────────────────────┐
│ 1. Frontend detecta que access token expira pronto              │
│    (1 minuto antes de exp)                                      │
└───────────────────────────┬─────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────────┐
│ 2. Frontend envia refresh token a POST /api/v1/auth/refresh     │
└───────────────────────────┬─────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────────┐
│ 3. Backend valida refresh token                                  │
│    - Verifica firma                                              │
│    - Verifica expiracion                                         │
│    - Verifica que no este usado (is_used = false)               │
│    - Verifica que no este revocado                              │
└───────────────────────────┬─────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────────┐
│ 4. Si es valido:                                                 │
│    - Marca token actual como usado (is_used = true)             │
│    - Genera nuevo access token                                   │
│    - Genera nuevo refresh token (misma family_id)               │
│    - Actualiza replaced_by del token anterior                   │
│    - Retorna nuevos tokens                                       │
└───────────────────────────┬─────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────────┐
│ 5. Frontend almacena nuevos tokens                               │
│    - Actualiza access token en memoria/storage                  │
│    - Actualiza refresh token en httpOnly cookie                 │
│    - Reinicia timer de refresh                                   │
└─────────────────────────────────────────────────────────────────┘
```

### Deteccion de Token Replay

```typescript
async refreshTokens(refreshToken: string): Promise<TokenPair> {
  const decoded = this.decodeToken(refreshToken);
  const storedToken = await this.refreshTokenRepo.findOne({
    where: { jti: decoded.jti }
  });

  // Detectar reuso
  if (storedToken.isUsed) {
    // ALERTA: Token replay detectado
    await this.revokeTokenFamily(storedToken.familyId);
    throw new UnauthorizedException('Sesion comprometida');
  }

  // Marcar como usado
  storedToken.isUsed = true;
  storedToken.usedAt = new Date();

  // Generar nuevos tokens
  const newTokens = await this.generateTokenPair(decoded.sub, decoded.tid);

  // Vincular tokens
  storedToken.replacedBy = newTokens.refreshTokenId;
  await this.refreshTokenRepo.save(storedToken);

  return newTokens;
}
```

---

## Datos de Prueba

| Escenario | Entrada | Resultado |
|-----------|---------|-----------|
| Refresh exitoso | Refresh token valido | 200, nuevos tokens |
| Token expirado | RT con exp < now | 401, "Token expirado" |
| Token ya usado | RT con is_used = true | 401, "Sesion comprometida" |
| Token revocado | RT en revoked_tokens | 401, "Token revocado" |
| Sin refresh token | Body vacio | 400, "Refresh token requerido" |

---

## Estimacion

| Capa | Story Points | Notas |
|------|--------------|-------|
| Database | 2 | Columnas adicionales en refresh_tokens |
| Backend | 5 | Logica de rotacion y deteccion reuso |
| Frontend | 3 | Auto-refresh interceptor y timer |
| **Total** | **10** | |

---

## Notas Adicionales

- El refresh token debe enviarse en httpOnly cookie, no en body (previene XSS)
- Considerar sliding window: extender expiracion si hay actividad
- Implementar rate limiting en endpoint de refresh (max 1 req/segundo)
- Loguear todos los refreshes para auditoria
- En caso de breach, proporcionar endpoint para revocar todas las sesiones

---

## 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 | - | - | [ ] |
