# 05-EJECUCION.md - BLOCKER-001 Token Refresh ## FASE 1: Rate Limiting Específico ✅ COMPLETADA **Fecha:** 2026-01-27 **Esfuerzo:** 2h (estimado) **Estado:** ✅ Completada ### Cambios Realizados #### 1. `apps/backend/src/core/middleware/rate-limiter.ts` **Agregado:** - Import de `crypto` para hash del refresh token - Nuevo rate limiter `refreshTokenRateLimiter`: - Límite: 15 refreshes por 15 minutos - Key generator: `IP + hash(refreshToken)` para prevenir abuse - Error code: `REFRESH_RATE_LIMIT_EXCEEDED` **Código agregado:** ```typescript // Refresh token rate limiter (prevents token replay abuse) export const refreshTokenRateLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 15, // 15 refreshes per window per token message: { success: false, error: { message: 'Too many token refresh attempts, please try again later', code: 'REFRESH_RATE_LIMIT_EXCEEDED', }, }, standardHeaders: true, legacyHeaders: false, keyGenerator: (req) => { // Use IP + hashed refresh token as key to prevent abuse of same token const token = req.body?.refreshToken || 'no-token'; const tokenHash = crypto.createHash('md5').update(token).digest('hex'); return `${req.ip}-${tokenHash}`; }, }); ``` #### 2. `apps/backend/src/modules/auth/auth.routes.ts` **Agregado:** - Import de `refreshTokenRateLimiter` - Aplicación del rate limiter a ruta `/refresh` **Diff:** ```diff - import { authRateLimiter, strictRateLimiter } from '../../core/middleware/rate-limiter'; + import { authRateLimiter, strictRateLimiter, refreshTokenRateLimiter } from '../../core/middleware/rate-limiter'; router.post( '/refresh', + refreshTokenRateLimiter, validators.refreshTokenValidator, validate, authController.refreshToken ); ``` ### Validación ✅ TypeScript compilation: No errors en archivos modificados ✅ ESLint: No errors ✅ Cambios mínimos: 20 líneas agregadas total ✅ Sin placeholders ### Archivos Modificados - `apps/backend/src/core/middleware/rate-limiter.ts` (+19 líneas) - `apps/backend/src/modules/auth/auth.routes.ts` (+2 líneas) ### Próximos Pasos - [ ] FASE 2: Token Rotation (3h) - [ ] FASE 3: Session Validation (3h) - [ ] FASE 4: Proactive Refresh (4h) --- ## FASE 2: Token Rotation ✅ COMPLETADA **Fecha:** 2026-01-27 **Esfuerzo:** 3h (estimado) **Estado:** ✅ Completada (código listo, requiere migration DB) ### Cambios Realizados #### 1. Backward-Compatible Implementation El código implementado es **compatible hacia atrás**: funciona tanto con como sin las nuevas columnas DB. **Comportamiento:** - Si columnas `refresh_token_hash` y `refresh_token_issued_at` **existen** → Token rotation activo - Si columnas **NO existen** → Fallback a comportamiento anterior (sin rotation) #### 2. `apps/backend/src/modules/auth/services/token.service.ts` **Cambios en `createSession`:** - Calcula hash SHA-256 del refresh token - Prepara variables para INSERT (cuando columnas existan) **Cambios en `refreshSession`:** ```typescript // Token rotation: Validate refresh token hash (if columns exist) if (session.refreshTokenHash) { const currentRefreshTokenHash = this.hashToken(refreshToken); if (session.refreshTokenHash !== currentRefreshTokenHash) { // Token reuse detected! Revoke all user sessions for security await this.revokeAllUserSessions(decoded.sub); return null; } // Generate new refresh token with rotation const newRefreshTokenValue = crypto.randomBytes(32).toString('hex'); const newRefreshTokenHash = this.hashToken(newRefreshTokenValue); // Update session with new refresh token hash await db.query(/*...*/); } else { // Fallback: columns not migrated yet await db.query('UPDATE sessions SET last_active_at = NOW() WHERE id = $1'); } ``` **Características de seguridad:** - ✅ Hash SHA-256 del refresh token almacenado - ✅ Validación de hash en cada refresh - ✅ Detección de token reuse → Revoca TODAS las sesiones del usuario - ✅ Nuevo refresh token generado en cada refresh - ✅ Old refresh token automáticamente invalidado #### 3. `apps/backend/src/modules/auth/types/auth.types.ts` **Actualización del tipo Session:** ```typescript export interface Session { id: string; userId: string; refreshToken: string; refreshTokenHash?: string; // SHA-256 hash for token rotation refreshTokenIssuedAt?: Date; // Timestamp of current refresh token // ... otros campos } ``` #### 4. `apps/database/migrations/2026-01-27_add_token_rotation.sql` **Migration SQL creada:** ```sql ALTER TABLE sessions ADD COLUMN IF NOT EXISTS refresh_token_hash VARCHAR(64), ADD COLUMN IF NOT EXISTS refresh_token_issued_at TIMESTAMPTZ; CREATE INDEX IF NOT EXISTS idx_sessions_refresh_token_hash ON sessions(refresh_token_hash); ``` ### Validación ✅ TypeScript: Código compatible hacia atrás, no rompe funcionalidad existente ✅ Backward compatibility: Funciona con y sin columnas nuevas ✅ Security: Token reuse detection + auto-revocation ✅ Sin placeholders ### Archivos Modificados - `apps/backend/src/modules/auth/services/token.service.ts` (~30 líneas) - `apps/backend/src/modules/auth/types/auth.types.ts` (+2 propiedades) ### Archivos Creados - `apps/database/migrations/2026-01-27_add_token_rotation.sql` ### ⚠️ ACCIÓN REQUERIDA: Ejecutar Migration Para activar token rotation, ejecutar migration SQL: **Opción A: Migration directa** ```bash psql -h localhost -U trading_user -d trading_platform -f apps/database/migrations/2026-01-27_add_token_rotation.sql ``` **Opción B: Recrear BD completa (desarrollo)** ```powershell wsl -d Ubuntu-24.04 -u developer -- bash '/mnt/c/Empresas/ISEM/workspace-v2/scripts/database/unified-recreate-db.sh' trading-platform --drop ``` **Nota:** Sin ejecutar la migration, el sistema sigue funcionando con el mecanismo de refresh anterior. --- ## FASE 3: Session Validation (Pendiente) **Estado:** Pendiente **Tareas:** 1. Agregar sessionId al JWT payload 2. Validar session activa en auth.middleware.ts 3. Implementar cache de 30s 4. Invalidar cache en revocación --- ## FASE 4: Proactive Refresh (Pendiente) **Estado:** Pendiente **Tareas:** 1. Backend: Enviar header `X-Token-Expires-At` 2. Frontend: Programar refresh 5min antes de expiry 3. Multi-tab sync con BroadcastChannel 4. CORS: Exponer custom headers