ML Engine Updates: - Updated BTCUSD with Polygon API data (2024-2025): 215,699 new records - Re-trained all ML models: Attention (R²: 0.223), Base, Metamodel (87.3% confidence) - Backtest results: +176.71R profit with aggressive_filter strategy Documentation Consolidation: - Created docs/99-analisis/_MAP.md index with 13 new analysis documents - Consolidated inventories: removed duplicates from orchestration/inventarios/ - Updated ML_INVENTORY.yml with BTCUSD metrics and training results - Added execution reports: FASE11-BTCUSD, correction issues, alignment validation Architecture & Integration: - Updated all module documentation with NEXUS v3.4 frontmatter - Fixed _MAP.md indexes across all folders - Updated orchestration plans and traces Files: 229 changed, 5064 insertions(+), 1872 deletions(-) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
453 lines
15 KiB
Markdown
453 lines
15 KiB
Markdown
---
|
|
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)
|