trading-platform/docs/02-definicion-modulos/OQI-001-fundamentos-auth/historias-usuario/US-AUTH-013-logout-global.md
Adrian Flores Cortes cdec253b02 [TASK-2026-01-25-FRONTEND-ANALYSIS] docs: Add frontend specifications and user stories
- Add 5 frontend specification documents (ET-*-frontend.md):
  - ET-AUTH-006: Authentication module frontend spec
  - ET-ML-008: ML Signals module frontend spec
  - ET-LLM-007: LLM Agent module frontend spec
  - ET-PFM-008: Portfolio Manager frontend spec (design)
  - ET-MKT-003: Marketplace frontend spec (design)

- Add 8 new user stories:
  - US-AUTH-013: Global logout
  - US-AUTH-014: Device management
  - US-ML-008: Ensemble signal view
  - US-ML-009: ICT analysis view
  - US-ML-010: Multi-symbol scan
  - US-LLM-011: Execute trade from chat
  - US-PFM-013: Rebalance alerts
  - US-PFM-014: PDF report generation

- Update task index with completed analysis

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 01:47:27 -06:00

515 lines
19 KiB
Markdown

---
id: "US-AUTH-013"
title: "Logout Global"
type: "User Story"
status: "To Do"
priority: "Alta"
epic: "OQI-001"
story_points: 3
created_date: "2026-01-25"
updated_date: "2026-01-25"
---
# US-AUTH-013: Logout Global
**Version:** 1.0.0
**Fecha:** 2026-01-25
**Estado:** Pendiente
**Story Points:** 3
**Prioridad:** P1 (Alta)
**Épica:** [OQI-001](../_MAP.md)
---
## Historia de Usuario
**Como** usuario de Trading Platform
**Quiero** poder cerrar sesión en todos mis dispositivos simultáneamente
**Para** asegurarme de que mi cuenta está completamente segura, especialmente si creo que ha sido comprometida o si pierdo un dispositivo
---
## Criterios de Aceptación
### AC-001: Opción de logout global en perfil
**Dado** que estoy autenticado y en mi perfil
**Cuando** accedo a Configuración > Seguridad > Sesiones
**Entonces** debería ver un botón "Cerrar sesión en todos los dispositivos" o similar
### AC-002: Confirmación de logout global
**Dado** que quiero cerrar sesión en todos mis dispositivos
**Cuando** hago click en el botón de logout global
**Entonces** debería ver un modal de confirmación que indique:
- "Esto cerrará sesión en TODOS tus dispositivos"
- "Incluida esta sesión actual"
- "Tendrás que volver a iniciar sesión"
- Botón "Cancelar" y "Cerrar todas las sesiones"
### AC-003: Invalidación de todos los tokens
**Dado** que confirmo el logout global
**Cuando** se ejecuta la acción
**Entonces** debería:
1. Invalidar TODOS los tokens JWT de todas las sesiones
2. Invalidar TODOS los refresh tokens
3. Marcar todas las sesiones como "closed" en la DB
4. No permitir que ningún token anterior funcione
### AC-004: Redirección a login
**Dado** que logout global se completó exitosamente
**Cuando** se cierre la sesión
**Entonces** debería:
1. Mostrar mensaje "Sesión cerrada en todos los dispositivos"
2. Redirigir a la página de login después de 2 segundos
3. Limpiar localStorage y cookies
4. No permitir acceso a rutas protegidas
### AC-005: Email de notificación
**Dado** que ejecuté logout global
**Cuando** se cierre la sesión
**Entonces** debería recibir email en la dirección registrada indicando:
- "Se cerró sesión en todos tus dispositivos"
- Fecha y hora
- Si no fuiste tú, link para cambiar contraseña
### AC-006: Impossibilidad de continuar con tokens antiguos
**Dado** que hice logout global
**Cuando** intento usar un token anterior en cualquier dispositivo
**Entonces** debería recibir:
- Código 401 Unauthorized
- Mensaje "Sesión inválida"
- Redirección a login
### AC-007: Operación atómica
**Dado** que inicio logout global
**Cuando** hay una falla de red durante el proceso
**Entonces** debería:
- Reintentar automáticamente 3 veces
- Si continúa fallando, mostrar error al usuario
- Los tokens deben estar invalidados (garantizar atomicidad)
### AC-008: Auditoria y registro
**Dado** que ejecuté logout global
**Cuando** se complete la acción
**Entonces** debería:
- Quedar registrado en logs de auditoria
- Incluir timestamp, user_id, tipo de acción
- Ser visible en historial de sesiones como "Cerrado por logout global"
---
## Mockup
```
┌─────────────────────────────────────────────────────────────┐
│ Configuración > Seguridad > Sesiones │
├─────────────────────────────────────────────────────────────┤
│ │
│ Sesiones activas │
│ Gestiona dónde está abierta tu cuenta │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 💻 Chrome en macOS 🟢 Esta sesión │ │
│ │ San Francisco, Estados Unidos • 201.45.67.89 │ │
│ │ Última actividad: Hace 2 minutos │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 📱 Safari en iPhone [Cerrar sesión]│ │
│ │ Ciudad de México, México • 189.203.45.12 │ │
│ │ Última actividad: Hace 3 horas │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 🔒 CERRAR SESIÓN EN TODOS LOS DISPOSITIVOS │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ⚠️ Esto cerrará sesión en esta y todas las otras │
│ sesiones. Tendrás que volver a iniciar sesión. │
│ │
└─────────────────────────────────────────────────────────────┘
Modal de confirmación:
┌─────────────────────────────────────────────────────────────┐
│ │
│ ⚠️ Cerrar sesión en todos los dispositivos │
│ │
│ Esto cerrará sesión INMEDIATAMENTE en: │
│ │
│ • Esta sesión (Chrome en macOS) │
│ • Safari en iPhone │
│ • Firefox en Windows │
│ │
│ Se cerrarán 3 sesiones en total. │
│ │
│ Si no fuiste tú, considera cambiar tu contraseña después. │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ Cancelar │ │ Cerrar todo │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Email de notificación:
┌─────────────────────────────────────────────────────────────┐
│ De: Trading Platform Security <security@trading.com> │
│ Asunto: Se cerró sesión en todos tus dispositivos │
│ │
│ Hola Juan, │
│ │
│ Se cerró sesión en TODOS tus dispositivos. │
│ │
│ 🕐 25 de Enero, 2026 a las 14:30 CST │
│ 📍 Desde: San Francisco, Estados Unidos │
│ │
│ ¿Fuiste tú? │
│ Si reconoces esta actividad, puedes ignorar este email. │
│ │
│ ¿No fuiste tú? │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Cambiar contraseña inmediatamente │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Para seguridad adicional, considera: │
│ • Verificar tu email de recuperación │
│ • Revisar actividades recientes │
│ • Habilitar autenticación de dos factores │
│ │
└─────────────────────────────────────────────────────────────┘
Mensaje después de logout:
┌─────────────────────────────────────────────────────────────┐
│ │
│ ✓ Sesión cerrada en todos los dispositivos │
│ │
│ Se cerrará sesión automáticamente en 2 segundos... │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## Tareas Técnicas
### Database (DB)
- [ ] Tabla `user_sessions` (ya existe en US-AUTH-012)
- Verificar columnas: `expires_at`, `status`
- Agregar índice: `idx_user_active` (user_id, WHERE status = 'active')
- [ ] Tabla `session_audit_log`:
```sql
CREATE TABLE session_audit_log (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id) NOT NULL,
action VARCHAR(100) NOT NULL, -- 'logout_global', 'logout_single', etc.
session_id UUID REFERENCES user_sessions(id),
ip_address VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMP DEFAULT NOW(),
INDEX idx_user_created (user_id, created_at),
INDEX idx_action_created (action, created_at)
);
```
### Backend (BE)
- [ ] Endpoint `POST /api/v1/auth/logout-all`
- Autenticación requerida
- Parámetro: `confirmation: boolean`
- Respuesta: `{ success: boolean, message: string, redirectUrl: string }`
- Rate limit: 1 request/min por usuario
- [ ] Service `LogoutService`
- `logoutGlobal(userId: string)`
- Buscar todas las sesiones activas del usuario
- Invalidar todos los tokens
- Marcar sesiones como 'closed'
- Registrar en auditoria
- Retornar conteo de sesiones cerradas
- [ ] Invalidación de tokens:
- Crear tabla/cache de tokens invalidados (blacklist)
- Middleware: verificar si token está en blacklist
- Usar Redis para expiración automática
- [ ] Email notification:
- Template: `logout-all.html`
- Enviar a dirección registrada
- Incluir: fecha, hora, ubicación, link de seguridad
- Service: `EmailService.sendLogoutAllNotification()`
- [ ] Logging y auditoria:
- Registrar en `session_audit_log`
- Incluir: timestamp, user_id, acción, IP, user_agent
- Service: `AuditService.logLogoutAll()`
- [ ] Tests unitarios (8 casos)
- Logout exitoso
- Logout con error de DB
- Email enviado correctamente
- Tokens invalidados
- Auditoria registrada
- Rate limiting
- Concurrencia (múltiples requests simultáneos)
- Validación de autenticación
- [ ] Tests de integración (5 escenarios)
- Logout global desde múltiples dispositivos
- Verificar 401 con tokens anteriores
- Email recibido
- Auditoria registrada
- Redirección correcta
### Frontend (FE)
- [ ] Componente `LogoutAllButton.tsx`
- Botón de logout global
- En Settings > Security > Sessions
- [ ] Modal `LogoutAllConfirmationModal.tsx`
- Mostrar lista de sesiones que se cerrarán
- Advertencia clara
- Botones Cancelar/Confirmar
- [ ] Integración en `Sessions.tsx`
- Agregar botón y modal
- Manejar respuesta de API
- Mostrar mensaje de éxito
- Redirigir a login
- [ ] Servicio `AuthService`
- Método `logoutAll(): Promise<{ success: boolean }>`
- Llamar a POST /api/v1/auth/logout-all
- Limpiar tokens locales
- Redirigir a login
- [ ] Tests con React Testing Library
- Renderizar componente
- Click en botón
- Mostrar modal
- Enviar confirmación
- Verificar redirección
### Testing (QA)
- [ ] E2E: Logout global exitoso
- [ ] E2E: Modal de confirmación
- [ ] E2E: Email recibido
- [ ] E2E: Tokens invalidados en todos los dispositivos
- [ ] E2E: Redirección a login
- [ ] E2E: Auditoria registrada
- [ ] Test de concurrencia: Múltiples logouts globales simultáneos
- [ ] Test de seguridad: Tokens antiguos no funcionan
- [ ] Performance: Logout global < 500ms
---
## Dependencias
- **Bloqueantes:**
- US-AUTH-012: Gestión de Sesiones (para tablas y flujos)
- Servicio de email configurado
- **Relacionadas:**
- US-AUTH-002: Login genera tokens
- US-AUTH-014: Cambio de contraseña por seguridad
---
## Definition of Ready (DoR)
- [ ] Mockups aprobados
- [ ] API contract definido
- [ ] Estrategia de invalidación de tokens definida
- [ ] Template de email diseñado
- [ ] Esquema de auditoria revisado
---
## Definition of Done (DoD)
- [ ] Código implementado y revisado
- [ ] Tests unitarios con 80%+ cobertura
- [ ] Tests de integración pasando
- [ ] Tests E2E implementados
- [ ] Email de notificación funcional
- [ ] Auditoria registrada correctamente
- [ ] Rate limiting configurado
- [ ] Tokens invalidados en todos los dispositivos
- [ ] Redirección a login funcional
- [ ] Documentación actualizada
- [ ] QA aprobado en staging
- [ ] Deploy a producción exitoso
---
## Notas Técnicas
### Estrategia de Invalidación de Tokens
**Opción 1: Token Blacklist (Recomendado)**
```typescript
// En Redis, crear entrada para cada token invalidado
await redis.setex(
`token_blacklist:${jti}`,
tokenExpirationTime,
'revoked'
);
// En middleware, verificar si token está en blacklist
const isBlacklisted = await redis.exists(`token_blacklist:${jti}`);
if (isBlacklisted) {
throw new UnauthorizedException('Token revoked');
}
```
**Opción 2: Token Version (Alternativa)**
```typescript
// En tabla users, guardar version_token
// Al hacer logout global, incrementar version
UPDATE users SET token_version = token_version + 1 WHERE id = ?
// En JWT, incluir token_version
const token = generateJWT({
sub: user.id,
token_version: user.token_version,
...
});
// En middleware, validar que token_version coincida
const user = await findUser(jti.sub);
if (token.token_version !== user.token_version) {
throw new UnauthorizedException('Token revoked');
}
```
Recomendamos Opción 1 (Redis) por precisión inmediata.
### Flujo de Logout Global
```
1. Usuario hace click en "Cerrar sesión en todos los dispositivos"
2. Frontend muestra modal de confirmación
3. Usuario confirma
4. Frontend envía POST /api/v1/auth/logout-all
5. Backend:
a. Verifica autenticación
b. Busca todas las sesiones del usuario
c. Marca todas como 'closed'
d. Invalida todos los tokens en Redis/Cache
e. Registra acción en auditoria
f. Envía email de notificación
6. Frontend recibe respuesta exitosa
7. Frontend limpia tokens locales
8. Frontend muestra mensaje "Sesión cerrada"
9. Frontend redirige a /login después de 2 segundos
```
### Rate Limiting
```typescript
// Usar express-rate-limit o similar
const logoutAllLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minuto
max: 1, // 1 request por minuto
message: 'Solo puedes hacer logout global 1 vez por minuto'
});
app.post('/api/v1/auth/logout-all', logoutAllLimiter, logoutAllHandler);
```
### Transacción en DB
```typescript
async function logoutGlobal(userId: string) {
return await db.$transaction(async (tx) => {
// 1. Buscar sesiones
const sessions = await tx.userSessions.findMany({
where: { user_id: userId, status: 'active' }
});
// 2. Marcar como closed
await tx.userSessions.updateMany({
where: { user_id: userId, status: 'active' },
data: { status: 'closed', expires_at: new Date() }
});
// 3. Registrar auditoria
await tx.sessionAuditLog.create({
data: {
user_id: userId,
action: 'logout_global',
created_at: new Date()
}
});
return sessions.length;
});
}
```
### Email Template
```html
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; }
.container { max-width: 600px; margin: 0 auto; }
.header { background-color: #f44336; color: white; padding: 20px; }
.content { padding: 20px; }
.warning { background-color: #fff3cd; padding: 10px; border-left: 4px solid #ffc107; }
.button { background-color: #2196f3; color: white; padding: 10px 20px; text-decoration: none; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Sesión cerrada en todos tus dispositivos</h1>
</div>
<div class="content">
<p>Hola {{ user_name }},</p>
<p>Se cerró sesión en TODOS tus dispositivos.</p>
<div class="warning">
<strong>Detalles:</strong><br>
Fecha: {{ date }} a las {{ time }} {{ timezone }}<br>
Ubicación: {{ location }}<br>
</div>
<p>¿Fuiste tú? Si reconoces esta actividad, puedes ignorar este email.</p>
<p>¿No fuiste tú? Considera cambiar tu contraseña inmediatamente:</p>
<a href="{{ password_reset_url }}" class="button">Cambiar contraseña</a>
</div>
</div>
</body>
</html>
```
---
## Requerimientos Relacionados
- [RF-AUTH-007: Logout Global](../requerimientos/RF-AUTH-007-logout-global.md)
## Especificaciones Relacionadas
- [ET-AUTH-002: JWT Tokens](../especificaciones/ET-AUTH-002-jwt.md)
- [ET-AUTH-003: Database](../especificaciones/ET-AUTH-003-database.md)
- [ET-AUTH-006: Session Management](../especificaciones/ET-AUTH-006-sessions.md)