--- id: "RF-AUTH-005" title: "Gestion de Sesiones" 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-005: Gestión de Sesiones **Version:** 1.0.0 **Fecha:** 2025-12-05 **Estado:** ✅ Implementado **Prioridad:** P0 (Crítica) **Épica:** [OQI-001](../_MAP.md) --- ## Descripción El sistema debe gestionar sesiones de usuario de forma segura, permitiendo múltiples dispositivos simultáneos, visualización de sesiones activas, y capacidad de revocar acceso de forma individual o global. --- ## Objetivo de Negocio - Permitir acceso desde múltiples dispositivos - Dar control al usuario sobre sus sesiones - Detectar y prevenir accesos no autorizados - Cumplir con regulaciones de seguridad --- ## Requisitos Funcionales ### RF-AUTH-005.1: Creación de Sesión **DEBE:** 1. Crear sesión al login exitoso 2. Registrar información del dispositivo 3. Registrar IP y ubicación aproximada 4. Generar refresh token único 5. Establecer tiempo de expiración (7 días) ### RF-AUTH-005.2: Tokens JWT **DEBE:** 1. Access token con TTL corto (15 min) 2. Refresh token con TTL largo (7 días) 3. Refresh token rotativo (nuevo en cada refresh) 4. Invalidar refresh token anterior al rotar 5. Almacenar hash del refresh token en DB ### RF-AUTH-005.3: Visualización de Sesiones **DEBE:** 1. Listar todas las sesiones activas 2. Mostrar dispositivo, navegador, SO 3. Mostrar IP y ubicación 4. Mostrar fecha de último acceso 5. Identificar sesión actual ### RF-AUTH-005.4: Revocación de Sesiones **DEBE:** 1. Permitir cerrar sesión individual 2. Permitir cerrar todas las sesiones 3. Cerrar sesiones al cambiar contraseña 4. Cerrar sesiones al desactivar cuenta ### RF-AUTH-005.5: Detección de Anomalías **DEBE:** 1. Detectar login desde nueva ubicación 2. Detectar login desde nuevo dispositivo 3. Notificar al usuario de accesos sospechosos 4. Registrar en audit log --- ## Estructura de Sesión ### Modelo de Datos ```sql CREATE TABLE sessions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, refresh_token_hash VARCHAR(255) NOT NULL, device_info JSONB NOT NULL, ip_address INET, location JSONB, user_agent TEXT, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMPTZ DEFAULT NOW(), last_activity TIMESTAMPTZ DEFAULT NOW(), expires_at TIMESTAMPTZ NOT NULL, revoked_at TIMESTAMPTZ, revoked_reason VARCHAR(100) ); CREATE INDEX idx_sessions_user_id ON sessions(user_id); CREATE INDEX idx_sessions_token_hash ON sessions(refresh_token_hash); CREATE INDEX idx_sessions_expires ON sessions(expires_at) WHERE is_active = TRUE; ``` ### Device Info Structure ```typescript interface DeviceInfo { type: 'desktop' | 'mobile' | 'tablet'; browser: string; browserVersion: string; os: string; osVersion: string; isMobile: boolean; deviceId?: string; // Fingerprint opcional } ``` ### Location Structure ```typescript interface LocationInfo { country: string; countryCode: string; region: string; city: string; timezone: string; // Aproximado por IP, no GPS } ``` --- ## Flujo de Tokens ### Login y Generación ``` Usuario Frontend Backend DB │ │ │ │ │─── Login ───────────────▶│ │ │ │ │ │ │ │ │─── POST /auth/login ────▶│ │ │ │ + User-Agent │ │ │ │ + IP │ │ │ │ │ │ │ │ │─── Validate creds ─────│ │ │ │ │ │ │ │─── Parse device info ──│ │ │ │─── Get location by IP ─│ │ │ │ │ │ │ │─── Generate tokens ────│ │ │ │ accessToken (15m) │ │ │ │ refreshToken (7d) │ │ │ │ │ │ │ │─── INSERT session ────▶│ │ │ │ refresh_token_hash │ │ │ │ device_info │ │ │ │ ip, location │ │ │ │ │ │ │◀── 200 OK ───────────────│ │ │ │ { accessToken, │ │ │ │ refreshToken, │ │ │ │ expiresIn } │ │ │ │ │ │ │◀── Store tokens ─────────│ │ │ ``` ### Refresh Token Rotation ``` Frontend Backend DB │ │ │ │─── POST /auth/refresh ──▶│ │ │ { refreshToken } │ │ │ │ │ │ │─── Hash token ─────────│ │ │ │ │ │─── Find session ──────▶│ │ │◀── session data ───────│ │ │ │ │ │─── Validate: │ │ │ - Token matches │ │ │ - Not expired │ │ │ - Session active │ │ │ │ │ │─── Generate NEW tokens │ │ │ newAccessToken │ │ │ newRefreshToken │ │ │ │ │ │─── UPDATE session ────▶│ │ │ new token_hash │ │ │ last_activity │ │ │ │ │◀── 200 OK ───────────────│ │ │ { accessToken, │ │ │ refreshToken } │ │ ``` --- ## Gestión de Sesiones UI ### Lista de Sesiones ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ Sesiones Activas │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 🖥️ Windows · Chrome 120 [Sesión actual] │ │ Ciudad de México, México │ │ Último acceso: Ahora │ │ IP: 189.xxx.xxx.xxx │ │ │ │ ───────────────────────────────────────────────────────────────────── │ │ │ │ 📱 iPhone · Safari 17 [Revocar] │ │ Guadalajara, México │ │ Último acceso: Hace 2 horas │ │ IP: 187.xxx.xxx.xxx │ │ │ │ ───────────────────────────────────────────────────────────────────── │ │ │ │ 💻 MacOS · Firefox 121 [Revocar] │ │ Monterrey, México │ │ Último acceso: Hace 3 días │ │ IP: 201.xxx.xxx.xxx │ │ │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ⚠️ ¿No reconoces alguna sesión? │ │ [Cerrar todas las demás sesiones] │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ``` --- ## API Endpoints ### Listar Sesiones ```http GET /auth/sessions Authorization: Bearer {accessToken} Response 200: { "sessions": [ { "id": "uuid", "device": { "type": "desktop", "browser": "Chrome", "os": "Windows" }, "location": { "city": "Ciudad de México", "country": "México" }, "ipAddress": "189.xxx.xxx.xxx", "lastActivity": "2025-12-05T10:00:00Z", "createdAt": "2025-12-01T08:00:00Z", "isCurrent": true } ] } ``` ### Revocar Sesión ```http DELETE /auth/sessions/{sessionId} Authorization: Bearer {accessToken} Response 200: { "message": "Sesión cerrada exitosamente" } ``` ### Revocar Todas las Sesiones ```http DELETE /auth/sessions Authorization: Bearer {accessToken} Response 200: { "message": "Todas las demás sesiones han sido cerradas", "revokedCount": 3 } ``` --- ## Detección de Anomalías ### Triggers de Alerta | Evento | Acción | |--------|--------| | Nuevo dispositivo | Email de notificación | | Nueva ubicación (país) | Email + notificación in-app | | Login desde IP en blacklist | Bloquear + notificar | | Múltiples IPs en poco tiempo | Notificar + audit log | | Login en horario inusual | Audit log | ### Email de Nuevo Dispositivo ``` Asunto: Nuevo inicio de sesión en tu cuenta Trading Platform Hola {{firstName}}, Detectamos un nuevo inicio de sesión en tu cuenta: Dispositivo: {{device}} Ubicación: {{location}} Fecha: {{date}} IP: {{ip}} Si fuiste tú, puedes ignorar este mensaje. Si NO fuiste tú: 1. Cambia tu contraseña inmediatamente 2. Activa 2FA si no lo tienes 3. Revisa tus sesiones activas [Asegurar mi cuenta] Saludos, Equipo Trading Platform ``` --- ## Limpieza de Sesiones ### Job Programado ```typescript // Ejecutar cada hora async function cleanupExpiredSessions() { await db.query(` UPDATE sessions SET is_active = FALSE, revoked_reason = 'expired' WHERE expires_at < NOW() AND is_active = TRUE `); } ``` ### Retención de Datos | Tipo de Sesión | Retención | |----------------|-----------| | Activa | Hasta expiración o revocación | | Revocada | 30 días (audit) | | Expirada | 30 días (audit) | --- ## Configuración ```typescript const sessionConfig = { accessToken: { expiresIn: '15m', algorithm: 'RS256', }, refreshToken: { expiresIn: '7d', algorithm: 'RS256', }, session: { maxPerUser: 10, // Máximo sesiones simultáneas inactivityTimeout: '30d', // Auto-revoke si inactiva }, }; ``` --- ## Manejo de Errores | Error | Código | Mensaje Usuario | |-------|--------|-----------------| | Token expirado | 401 | Sesión expirada, inicia sesión | | Token inválido | 401 | Token inválido | | Sesión revocada | 401 | Sesión cerrada, inicia sesión | | Sesión no encontrada | 404 | Sesión no encontrada | | No autorizado | 403 | No puedes cerrar esta sesión | | Límite de sesiones | 400 | Máximo de sesiones alcanzado | --- ## Seguridad ### Almacenamiento de Tokens 1. **Access token**: Solo en memoria (no localStorage) 2. **Refresh token**: httpOnly cookie o secure storage 3. **Hash del refresh token** en DB (no el token en sí) ### Protección de Refresh 1. **Rotación** en cada uso 2. **Detección de reuso** (posible robo) 3. **Binding** a device fingerprint (opcional) ### Reuse Detection Si un refresh token ya usado se presenta: 1. Invalidar TODAS las sesiones del usuario 2. Notificar al usuario 3. Registrar en security log 4. Posible cuenta comprometida --- ## Criterios de Aceptación - [ ] Sesión se crea al login exitoso - [ ] Device info se detecta correctamente - [ ] Ubicación por IP funciona - [ ] Access token expira en 15 minutos - [ ] Refresh token renueva correctamente - [ ] Usuario puede ver sesiones activas - [ ] Usuario puede revocar sesión individual - [ ] Usuario puede revocar todas las sesiones - [ ] Notificación de nuevo dispositivo funciona - [ ] Sesiones se limpian al expirar --- ## Especificación Técnica Relacionada - [ET-AUTH-002: JWT Tokens](../especificaciones/ET-AUTH-002-jwt.md) ## Historias de Usuario Relacionadas - [US-AUTH-012: Gestión de Sesiones](../historias-usuario/US-AUTH-012-session-management.md)