FASE 1 ✅: Rate limiting específico para /auth/refresh - Nuevo refreshTokenRateLimiter (15 refreshes/15min por token) - Key generator: IP + hash(refreshToken) - Previene abuse de tokens individuales FASE 2 ✅: Token rotation mechanism - Backend code implementado (backward-compatible) - Detección de token reuse → revoca todas las sesiones - Nuevo refresh token en cada refresh - Migration SQL creada: apps/database/migrations/2026-01-27_add_token_rotation.sql Archivos de código modificados (en .gitignore): - apps/backend/src/core/middleware/rate-limiter.ts - apps/backend/src/modules/auth/auth.routes.ts - apps/backend/src/modules/auth/services/token.service.ts - apps/backend/src/modules/auth/types/auth.types.ts - apps/database/ddl/schemas/auth/tables/04-sessions.sql - apps/database/migrations/2026-01-27_add_token_rotation.sql Pendiente: FASE 3 (Session Validation) y FASE 4 (Proactive Refresh) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6.2 KiB
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
cryptopara 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:
// 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:
- 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_hashyrefresh_token_issued_atexisten → 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:
// 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:
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:
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
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)
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:
- Agregar sessionId al JWT payload
- Validar session activa en auth.middleware.ts
- Implementar cache de 30s
- Invalidar cache en revocación
FASE 4: Proactive Refresh (Pendiente)
Estado: Pendiente
Tareas:
- Backend: Enviar header
X-Token-Expires-At - Frontend: Programar refresh 5min antes de expiry
- Multi-tab sync con BroadcastChannel
- CORS: Exponer custom headers