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>
640 lines
23 KiB
Markdown
640 lines
23 KiB
Markdown
---
|
|
id: "US-AUTH-012"
|
|
title: "Gestion de Sesiones"
|
|
type: "User Story"
|
|
status: "To Do"
|
|
priority: "Alta"
|
|
epic: "OQI-001"
|
|
story_points: 5
|
|
created_date: "2025-12-05"
|
|
updated_date: "2026-01-04"
|
|
---
|
|
|
|
# US-AUTH-012: Gestión de Sesiones
|
|
|
|
**Version:** 1.0.0
|
|
**Fecha:** 2025-12-05
|
|
**Estado:** Pendiente
|
|
**Story Points:** 5
|
|
**Prioridad:** P1 (Alta)
|
|
**Épica:** [OQI-001](../_MAP.md)
|
|
|
|
---
|
|
|
|
## Historia de Usuario
|
|
|
|
**Como** usuario de Trading Platform
|
|
**Quiero** poder ver y gestionar mis sesiones activas en diferentes dispositivos
|
|
**Para** tener control sobre dónde está abierta mi cuenta y poder cerrar sesiones remotamente por seguridad
|
|
|
|
---
|
|
|
|
## Criterios de Aceptación
|
|
|
|
### AC-001: Página de sesiones
|
|
|
|
**Dado** que estoy autenticado
|
|
**Cuando** accedo a Configuración > Seguridad > Sesiones
|
|
**Entonces** debería ver una lista de todas mis sesiones activas
|
|
|
|
### AC-002: Información de cada sesión
|
|
|
|
**Dado** que estoy viendo mis sesiones activas
|
|
**Cuando** veo una sesión en la lista
|
|
**Entonces** debería ver:
|
|
- Tipo de dispositivo (Computadora, Móvil, Tablet)
|
|
- Sistema operativo (Windows, macOS, iOS, Android, Linux)
|
|
- Navegador (Chrome, Safari, Firefox, etc.)
|
|
- Ubicación aproximada (Ciudad, País)
|
|
- Dirección IP
|
|
- Fecha y hora de último acceso
|
|
- Indicador de "Sesión actual" si es la sesión activa
|
|
- Botón "Cerrar sesión" (excepto para sesión actual)
|
|
|
|
### AC-003: Sesión actual destacada
|
|
|
|
**Dado** que estoy viendo mis sesiones
|
|
**Cuando** identifico mi sesión actual
|
|
**Entonces** debería estar:
|
|
- Marcada claramente como "Esta sesión"
|
|
- En la parte superior de la lista
|
|
- Con un badge o color distintivo
|
|
- Sin botón de "Cerrar sesión"
|
|
|
|
### AC-004: Cerrar una sesión individual
|
|
|
|
**Dado** que veo una sesión que no reconozco
|
|
**Cuando** hago click en "Cerrar sesión"
|
|
**Entonces** debería:
|
|
1. Ver confirmación "¿Cerrar esta sesión?"
|
|
2. Al confirmar, invalidar ese token JWT
|
|
3. Ver mensaje "Sesión cerrada"
|
|
4. La sesión desaparece de la lista
|
|
5. Si esa sesión intenta hacer requests, recibe 401 Unauthorized
|
|
|
|
### AC-005: Cerrar todas las demás sesiones
|
|
|
|
**Dado** que quiero cerrar sesión en todos mis dispositivos
|
|
**Cuando** hago click en "Cerrar todas las demás sesiones"
|
|
**Entonces** debería:
|
|
1. Ver confirmación "¿Cerrar todas las sesiones excepto la actual?"
|
|
2. Al confirmar, invalidar todos los tokens excepto el actual
|
|
3. Ver mensaje "X sesiones cerradas"
|
|
4. Solo quedar mi sesión actual en la lista
|
|
|
|
### AC-006: Detección de dispositivo
|
|
|
|
**Dado** que inicio sesión desde diferentes dispositivos
|
|
**Cuando** reviso mis sesiones
|
|
**Entonces** debería ver íconos apropiados:
|
|
- 💻 Computadora de escritorio
|
|
- 📱 Teléfono móvil
|
|
- 📋 Tablet
|
|
- 🌐 Desconocido
|
|
|
|
### AC-007: Geolocalización
|
|
|
|
**Dado** que inicio sesión desde diferentes ubicaciones
|
|
**Cuando** reviso mis sesiones
|
|
**Entonces** debería ver ubicación aproximada basada en IP:
|
|
- "San Francisco, Estados Unidos"
|
|
- "Ciudad de México, México"
|
|
- "Madrid, España"
|
|
- Si no se puede determinar: "Ubicación desconocida"
|
|
|
|
### AC-008: Alerta de sesión sospechosa
|
|
|
|
**Dado** que inicio sesión desde un dispositivo o ubicación nueva
|
|
**Cuando** completo el login
|
|
**Entonces** debería:
|
|
1. Recibir email de notificación con:
|
|
- Dispositivo y ubicación
|
|
- Fecha y hora
|
|
- Link para cerrar sesión si no fui yo
|
|
2. (Opcional) Ver notificación in-app
|
|
|
|
### AC-009: Historial de sesiones
|
|
|
|
**Dado** que quiero ver sesiones pasadas
|
|
**Cuando** veo la sección de historial
|
|
**Entonces** debería ver últimas 20 sesiones incluyendo:
|
|
- Sesiones activas (verde)
|
|
- Sesiones cerradas manualmente (gris)
|
|
- Sesiones expiradas (naranja)
|
|
- Con timestamps de inicio y fin
|
|
|
|
### AC-010: Auto-expiración de sesiones
|
|
|
|
**Dado** que una sesión lleva 30 días sin actividad
|
|
**Cuando** esa sesión intenta hacer un request
|
|
**Entonces** debería:
|
|
- Recibir 401 Unauthorized
|
|
- Ser redirigido a login
|
|
- Desaparecer de la lista de sesiones activas
|
|
|
|
### AC-011: Sesión sin "Recordarme"
|
|
|
|
**Dado** que inicié sesión sin marcar "Recordarme"
|
|
**Cuando** pasan 24 horas
|
|
**Entonces** esa sesión debería expirar automáticamente
|
|
|
|
### AC-012: Sesión con "Recordarme"
|
|
|
|
**Dado** que inicié sesión con "Recordarme" marcado
|
|
**Cuando** paso tiempo sin usar la app
|
|
**Entonces** la sesión debería permanecer activa hasta 30 días
|
|
|
|
### AC-013: Refresh tokens
|
|
|
|
**Dado** que tengo una sesión activa
|
|
**Cuando** mi access token expira (15 minutos)
|
|
**Entonces** debería:
|
|
- Usar refresh token automáticamente
|
|
- Obtener nuevo access token
|
|
- Continuar usando la app sin interrupciones
|
|
|
|
### AC-014: Cerrar sesión actual
|
|
|
|
**Dado** que quiero cerrar mi sesión actual
|
|
**Cuando** hago click en "Cerrar sesión" en el header
|
|
**Entonces** debería:
|
|
1. Invalidar mi token actual
|
|
2. Limpiar localStorage/cookies
|
|
3. Ser redirigido a la página de login
|
|
4. No poder acceder a rutas protegidas
|
|
|
|
---
|
|
|
|
## 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 │ │
|
|
│ │ │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ │ │
|
|
│ │ 💻 Firefox en Windows [Cerrar sesión]│ │
|
|
│ │ │ │
|
|
│ │ Madrid, España • 85.123.45.67 │ │
|
|
│ │ Última actividad: Hace 2 días │ │
|
|
│ │ │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ 🚪 Cerrar todas las demás sesiones │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ──────────────────────────────────────────────────────── │
|
|
│ │
|
|
│ Historial de sesiones │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ 📋 Chrome en iPad ⏱️ Sesión expirada │ │
|
|
│ │ Barcelona, España │ │
|
|
│ │ 15 Nov 2025 - 18 Nov 2025 │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ 📱 Chrome en Android ✓ Cerrada manual │ │
|
|
│ │ Bogotá, Colombia │ │
|
|
│ │ 10 Nov 2025 - 12 Nov 2025 │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
|
|
Modal de confirmación:
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ │
|
|
│ ⚠️ Cerrar todas las demás sesiones │
|
|
│ │
|
|
│ Esto cerrará sesión en todos tus otros dispositivos. │
|
|
│ Solo permanecerá activa tu sesión actual. │
|
|
│ │
|
|
│ Se cerrarán 2 sesiones. │
|
|
│ │
|
|
│ ┌──────────────────────┐ ┌──────────────────────┐ │
|
|
│ │ Cancelar │ │ Cerrar sesiones │ │
|
|
│ └──────────────────────┘ └──────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
|
|
Email de nueva sesión:
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ De: Trading Platform Security <security@trading.com> │
|
|
│ Asunto: Nueva sesión iniciada en tu cuenta │
|
|
│ │
|
|
│ Hola Juan, │
|
|
│ │
|
|
│ Se inició sesión en tu cuenta desde un nuevo dispositivo: │
|
|
│ │
|
|
│ 📱 Safari en iPhone │
|
|
│ 📍 Ciudad de México, México │
|
|
│ 🕐 5 de Diciembre, 2025 a las 14:30 CST │
|
|
│ 🌐 IP: 189.203.45.12 │
|
|
│ │
|
|
│ ¿Fuiste tú? │
|
|
│ Si reconoces esta actividad, puedes ignorar este email. │
|
|
│ │
|
|
│ ¿No fuiste tú? │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ Cerrar esta sesión inmediatamente │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ También puedes cambiar tu contraseña desde: │
|
|
│ Configuración > Seguridad > Cambiar contraseña │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Tareas Técnicas
|
|
|
|
### Database (DB)
|
|
|
|
- [ ] Tabla `user_sessions`:
|
|
```sql
|
|
CREATE TABLE user_sessions (
|
|
id UUID PRIMARY KEY,
|
|
user_id UUID REFERENCES users(id),
|
|
refresh_token VARCHAR(512) UNIQUE NOT NULL,
|
|
access_token_jti VARCHAR(255), -- JWT ID del access token actual
|
|
device_type VARCHAR(50), -- 'desktop', 'mobile', 'tablet'
|
|
device_name VARCHAR(255), -- 'Chrome on macOS'
|
|
os VARCHAR(100),
|
|
browser VARCHAR(100),
|
|
ip_address VARCHAR(45),
|
|
location_city VARCHAR(100),
|
|
location_country VARCHAR(100),
|
|
user_agent TEXT,
|
|
remember_me BOOLEAN DEFAULT false,
|
|
last_activity TIMESTAMP DEFAULT NOW(),
|
|
expires_at TIMESTAMP NOT NULL,
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
INDEX idx_user_expires (user_id, expires_at),
|
|
INDEX idx_refresh_token (refresh_token),
|
|
INDEX idx_last_activity (last_activity)
|
|
);
|
|
```
|
|
- [ ] Tabla `session_history`:
|
|
```sql
|
|
CREATE TABLE session_history (
|
|
id UUID PRIMARY KEY,
|
|
user_id UUID REFERENCES users(id),
|
|
device_name VARCHAR(255),
|
|
ip_address VARCHAR(45),
|
|
location_city VARCHAR(100),
|
|
location_country VARCHAR(100),
|
|
status VARCHAR(50), -- 'active', 'closed_manual', 'expired'
|
|
started_at TIMESTAMP NOT NULL,
|
|
ended_at TIMESTAMP,
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
INDEX idx_user_started (user_id, started_at)
|
|
);
|
|
```
|
|
|
|
### Backend (BE)
|
|
|
|
- [ ] Modificar login para crear sesión:
|
|
- Generar access token (JWT, 15 min)
|
|
- Generar refresh token (aleatorio, 30 días)
|
|
- Parsear user agent
|
|
- Obtener geolocalización de IP
|
|
- Guardar sesión en DB
|
|
- Detectar si es nuevo dispositivo/ubicación
|
|
- Enviar email si es sospechoso
|
|
- [ ] Endpoint `GET /api/v1/auth/sessions`
|
|
- Listar sesiones activas del usuario
|
|
- Marcar sesión actual
|
|
- Ordenar por last_activity DESC
|
|
- [ ] Endpoint `GET /api/v1/auth/sessions/history`
|
|
- Últimas 20 sesiones (activas + cerradas)
|
|
- [ ] Endpoint `DELETE /api/v1/auth/sessions/:id`
|
|
- Cerrar sesión específica
|
|
- Invalidar tokens
|
|
- Registrar en historial
|
|
- [ ] Endpoint `DELETE /api/v1/auth/sessions/others`
|
|
- Cerrar todas excepto actual
|
|
- [ ] Endpoint `POST /api/v1/auth/refresh`
|
|
- Validar refresh token
|
|
- Generar nuevo access token
|
|
- Actualizar last_activity
|
|
- [ ] Endpoint `POST /api/v1/auth/logout`
|
|
- Cerrar sesión actual
|
|
- Invalidar tokens
|
|
- [ ] Service `SessionService`
|
|
- `createSession()`
|
|
- `validateSession()`
|
|
- `refreshAccessToken()`
|
|
- `terminateSession()`
|
|
- `terminateAllOtherSessions()`
|
|
- `getActiveSessions()`
|
|
- `isNewDevice()`
|
|
- [ ] Service `DeviceDetectionService`
|
|
- `parseUserAgent()`
|
|
- `getDeviceType()`
|
|
- `getOS()`
|
|
- `getBrowser()`
|
|
- [ ] Service `GeolocationService`
|
|
- `getLocationFromIP()`
|
|
- Usar: ipapi.co, ip-api.com, o MaxMind GeoIP2
|
|
- [ ] Librería: `ua-parser-js` (user agent parsing)
|
|
- [ ] Cron job: Limpiar sesiones expiradas diariamente
|
|
- [ ] Tests unitarios (15 casos)
|
|
- [ ] Tests de integración (10 escenarios)
|
|
|
|
### Frontend (FE)
|
|
|
|
- [ ] Página `Settings/Security/Sessions.tsx`
|
|
- [ ] Componente `SessionCard.tsx`
|
|
- [ ] Componente `SessionHistoryCard.tsx`
|
|
- [ ] Modal de confirmación de cierre
|
|
- [ ] Servicio de auto-refresh de tokens
|
|
- Interceptor de Axios/Fetch
|
|
- Detectar 401
|
|
- Llamar a /refresh
|
|
- Reintentar request original
|
|
- [ ] Almacenamiento de tokens:
|
|
- Access token en memoria (state)
|
|
- Refresh token en httpOnly cookie (más seguro)
|
|
- [ ] Tests con React Testing Library
|
|
|
|
### Testing (QA)
|
|
|
|
- [ ] E2E: Ver sesiones activas
|
|
- [ ] E2E: Cerrar sesión individual
|
|
- [ ] E2E: Cerrar todas las demás sesiones
|
|
- [ ] E2E: Auto-refresh de access token
|
|
- [ ] E2E: Sesión expira después de 30 días
|
|
- [ ] E2E: Email de nueva sesión
|
|
- [ ] E2E: Login desde múltiples dispositivos
|
|
- [ ] Test de seguridad: Refresh token rotation
|
|
- [ ] Test de seguridad: Session fixation
|
|
- [ ] Performance: Lista de sesiones < 300ms
|
|
|
|
---
|
|
|
|
## Dependencias
|
|
|
|
- **Bloqueantes:**
|
|
- US-AUTH-002: Login genera tokens
|
|
- Servicio de geolocalización (ipapi.co o similar)
|
|
|
|
---
|
|
|
|
## Definition of Ready (DoR)
|
|
|
|
- [ ] Mockups aprobados
|
|
- [ ] API contract definido
|
|
- [ ] Estrategia de tokens definida (access + refresh)
|
|
- [ ] Servicio de geolocalización seleccionado
|
|
- [ ] Esquema de base de datos revisado
|
|
|
|
---
|
|
|
|
## Definition of Done (DoD)
|
|
|
|
- [ ] Código implementado y revisado
|
|
- [ ] Tests unitarios con 80%+ cobertura
|
|
- [ ] Tests de integración pasando
|
|
- [ ] Tests E2E implementados
|
|
- [ ] Auto-refresh de tokens funcional
|
|
- [ ] Geolocalización funcional
|
|
- [ ] Device detection funcional
|
|
- [ ] Email de alertas configurado
|
|
- [ ] Cron job de limpieza configurado
|
|
- [ ] Documentación actualizada
|
|
- [ ] QA aprobado en staging
|
|
- [ ] Deploy a producción exitoso
|
|
|
|
---
|
|
|
|
## Notas Técnicas
|
|
|
|
### Token Strategy: Access + Refresh
|
|
|
|
**Access Token (JWT):**
|
|
- Duración: 15 minutos
|
|
- Almacenado en: Memoria (React state)
|
|
- Incluye: user_id, email, role, jti (JWT ID)
|
|
- Se envía en header: `Authorization: Bearer <token>`
|
|
|
|
**Refresh Token:**
|
|
- Duración: 24 horas (sin "Recordarme") o 30 días (con "Recordarme")
|
|
- Almacenado en: httpOnly cookie
|
|
- Es un string aleatorio (crypto.randomBytes)
|
|
- Se guarda hasheado en DB
|
|
- Rotation: Cada refresh genera nuevo token
|
|
|
|
### JWT Structure
|
|
|
|
```json
|
|
{
|
|
"jti": "session-uuid", // JWT ID (unique)
|
|
"sub": "user-uuid",
|
|
"email": "user@example.com",
|
|
"role": "user",
|
|
"iat": 1234567890,
|
|
"exp": 1234568790 // +15 min
|
|
}
|
|
```
|
|
|
|
### Auto-Refresh Flow
|
|
|
|
```typescript
|
|
// Axios interceptor
|
|
axios.interceptors.response.use(
|
|
response => response,
|
|
async error => {
|
|
if (error.response?.status === 401) {
|
|
// Access token expiró, intentar refresh
|
|
try {
|
|
const { accessToken } = await refreshTokens();
|
|
// Actualizar token en memoria
|
|
setAccessToken(accessToken);
|
|
// Reintentar request original
|
|
error.config.headers.Authorization = `Bearer ${accessToken}`;
|
|
return axios.request(error.config);
|
|
} catch (refreshError) {
|
|
// Refresh falló, redirect a login
|
|
window.location.href = '/login';
|
|
}
|
|
}
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
```
|
|
|
|
### User Agent Parsing
|
|
|
|
```typescript
|
|
import UAParser from 'ua-parser-js';
|
|
|
|
const parser = new UAParser(userAgent);
|
|
const result = parser.getResult();
|
|
|
|
const deviceInfo = {
|
|
device_type: result.device.type || 'desktop', // mobile, tablet, desktop
|
|
device_name: `${result.browser.name} on ${result.os.name}`,
|
|
os: `${result.os.name} ${result.os.version}`,
|
|
browser: `${result.browser.name} ${result.browser.version}`
|
|
};
|
|
```
|
|
|
|
### Geolocation Service
|
|
|
|
```typescript
|
|
// Opción 1: ipapi.co (gratuito, 30k requests/mes)
|
|
const response = await fetch(`https://ipapi.co/${ip}/json/`);
|
|
const data = await response.json();
|
|
|
|
const location = {
|
|
city: data.city,
|
|
country: data.country_name,
|
|
latitude: data.latitude,
|
|
longitude: data.longitude
|
|
};
|
|
|
|
// Opción 2: ip-api.com (gratuito, 45 requests/min)
|
|
// Opción 3: MaxMind GeoIP2 (pago, más preciso)
|
|
```
|
|
|
|
### New Device Detection
|
|
|
|
```typescript
|
|
async function isNewDevice(userId, deviceName, ipAddress) {
|
|
const existingSession = await db.userSessions.findFirst({
|
|
where: {
|
|
user_id: userId,
|
|
device_name: deviceName,
|
|
ip_address: ipAddress,
|
|
created_at: { gte: thirtyDaysAgo }
|
|
}
|
|
});
|
|
|
|
return !existingSession;
|
|
}
|
|
```
|
|
|
|
### Refresh Token Rotation
|
|
|
|
Cada vez que se usa un refresh token, se genera uno nuevo:
|
|
|
|
```typescript
|
|
async function refreshAccessToken(oldRefreshToken) {
|
|
// 1. Validar refresh token
|
|
const session = await findSessionByRefreshToken(oldRefreshToken);
|
|
|
|
if (!session || session.expires_at < new Date()) {
|
|
throw new Error('Invalid or expired refresh token');
|
|
}
|
|
|
|
// 2. Generar nuevo access token
|
|
const accessToken = generateJWT(session.user_id);
|
|
|
|
// 3. Generar nuevo refresh token
|
|
const newRefreshToken = crypto.randomBytes(64).toString('hex');
|
|
|
|
// 4. Actualizar sesión
|
|
await db.userSessions.update({
|
|
where: { id: session.id },
|
|
data: {
|
|
refresh_token: hashToken(newRefreshToken),
|
|
access_token_jti: accessToken.jti,
|
|
last_activity: new Date()
|
|
}
|
|
});
|
|
|
|
// 5. Devolver tokens
|
|
return { accessToken, refreshToken: newRefreshToken };
|
|
}
|
|
```
|
|
|
|
### Security Considerations
|
|
|
|
1. **Refresh Token Rotation:**
|
|
- Cada refresh invalida el token anterior
|
|
- Previene replay attacks
|
|
|
|
2. **httpOnly Cookies:**
|
|
- Refresh token en httpOnly cookie
|
|
- No accesible desde JavaScript
|
|
- Previene XSS
|
|
|
|
3. **Secure Cookies:**
|
|
- Flag `Secure` (solo HTTPS)
|
|
- Flag `SameSite=Strict`
|
|
|
|
4. **JTI (JWT ID):**
|
|
- Cada access token tiene ID único
|
|
- Permite invalidación específica
|
|
|
|
5. **Device Fingerprinting:**
|
|
- Detectar cambios sospechosos
|
|
- Alertar al usuario
|
|
|
|
6. **Rate Limiting:**
|
|
- Limitar requests a /refresh
|
|
- Prevenir brute force
|
|
|
|
### Environment Variables
|
|
|
|
```env
|
|
JWT_SECRET=your-secret-key-256-bits
|
|
JWT_ACCESS_EXPIRY=15m
|
|
JWT_REFRESH_EXPIRY_SHORT=24h # sin "Recordarme"
|
|
JWT_REFRESH_EXPIRY_LONG=30d # con "Recordarme"
|
|
GEOLOCATION_API_KEY=your-api-key # si usas servicio de pago
|
|
```
|
|
|
|
### Cron Job: Cleanup
|
|
|
|
```typescript
|
|
// Ejecutar diariamente a las 3 AM
|
|
cron.schedule('0 3 * * *', async () => {
|
|
// Eliminar sesiones expiradas
|
|
await db.userSessions.deleteMany({
|
|
where: {
|
|
expires_at: { lt: new Date() }
|
|
}
|
|
});
|
|
|
|
// Mover a historial
|
|
// (opcional, si no usas soft deletes)
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Requerimientos Relacionados
|
|
|
|
- [RF-AUTH-006: Gestión de Sesiones](../requerimientos/RF-AUTH-006-sessions.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)
|