179 lines
4.5 KiB
TypeScript
179 lines
4.5 KiB
TypeScript
import Redis from 'ioredis';
|
|
import { logger } from '../shared/utils/logger.js';
|
|
|
|
/**
|
|
* Configuración de Redis para blacklist de tokens JWT
|
|
*/
|
|
const redisConfig = {
|
|
host: process.env.REDIS_HOST || 'localhost',
|
|
port: parseInt(process.env.REDIS_PORT || '6379', 10),
|
|
password: process.env.REDIS_PASSWORD || undefined,
|
|
|
|
// Configuración de reconexión
|
|
retryStrategy(times: number) {
|
|
const delay = Math.min(times * 50, 2000);
|
|
return delay;
|
|
},
|
|
|
|
// Timeouts
|
|
connectTimeout: 10000,
|
|
maxRetriesPerRequest: 3,
|
|
|
|
// Logging de eventos
|
|
lazyConnect: true, // No conectar automáticamente, esperar a connect()
|
|
};
|
|
|
|
/**
|
|
* Cliente Redis para blacklist de tokens
|
|
*/
|
|
export const redisClient = new Redis(redisConfig);
|
|
|
|
// Event listeners
|
|
redisClient.on('connect', () => {
|
|
logger.info('Redis client connecting...', {
|
|
host: redisConfig.host,
|
|
port: redisConfig.port,
|
|
});
|
|
});
|
|
|
|
redisClient.on('ready', () => {
|
|
logger.info('Redis client ready');
|
|
});
|
|
|
|
redisClient.on('error', (error) => {
|
|
logger.error('Redis client error', {
|
|
error: error.message,
|
|
stack: error.stack,
|
|
});
|
|
});
|
|
|
|
redisClient.on('close', () => {
|
|
logger.warn('Redis connection closed');
|
|
});
|
|
|
|
redisClient.on('reconnecting', () => {
|
|
logger.info('Redis client reconnecting...');
|
|
});
|
|
|
|
/**
|
|
* Inicializa la conexión a Redis
|
|
* @returns Promise<boolean> - true si la conexión fue exitosa
|
|
*/
|
|
export async function initializeRedis(): Promise<boolean> {
|
|
try {
|
|
await redisClient.connect();
|
|
|
|
// Test de conexión
|
|
await redisClient.ping();
|
|
|
|
logger.info('Redis connection successful', {
|
|
host: redisConfig.host,
|
|
port: redisConfig.port,
|
|
});
|
|
|
|
return true;
|
|
} catch (error) {
|
|
logger.error('Failed to connect to Redis', {
|
|
error: (error as Error).message,
|
|
host: redisConfig.host,
|
|
port: redisConfig.port,
|
|
});
|
|
|
|
// Redis es opcional, no debe detener la app
|
|
logger.warn('Application will continue without Redis (token blacklist disabled)');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cierra la conexión a Redis
|
|
*/
|
|
export async function closeRedis(): Promise<void> {
|
|
try {
|
|
await redisClient.quit();
|
|
logger.info('Redis connection closed gracefully');
|
|
} catch (error) {
|
|
logger.error('Error closing Redis connection', {
|
|
error: (error as Error).message,
|
|
});
|
|
|
|
// Forzar desconexión si quit() falla
|
|
redisClient.disconnect();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifica si Redis está conectado
|
|
*/
|
|
export function isRedisConnected(): boolean {
|
|
return redisClient.status === 'ready';
|
|
}
|
|
|
|
// ===== Utilidades para Token Blacklist =====
|
|
|
|
/**
|
|
* Agrega un token a la blacklist
|
|
* @param token - Token JWT a invalidar
|
|
* @param expiresIn - Tiempo de expiración en segundos
|
|
*/
|
|
export async function blacklistToken(token: string, expiresIn: number): Promise<void> {
|
|
if (!isRedisConnected()) {
|
|
logger.warn('Cannot blacklist token: Redis not connected');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const key = `blacklist:${token}`;
|
|
await redisClient.setex(key, expiresIn, '1');
|
|
logger.debug('Token added to blacklist', { expiresIn });
|
|
} catch (error) {
|
|
logger.error('Error blacklisting token', {
|
|
error: (error as Error).message,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifica si un token está en la blacklist
|
|
* @param token - Token JWT a verificar
|
|
* @returns Promise<boolean> - true si el token está en blacklist
|
|
*/
|
|
export async function isTokenBlacklisted(token: string): Promise<boolean> {
|
|
if (!isRedisConnected()) {
|
|
logger.warn('Cannot check blacklist: Redis not connected');
|
|
return false; // Si Redis no está disponible, permitir el acceso
|
|
}
|
|
|
|
try {
|
|
const key = `blacklist:${token}`;
|
|
const result = await redisClient.get(key);
|
|
return result !== null;
|
|
} catch (error) {
|
|
logger.error('Error checking token blacklist', {
|
|
error: (error as Error).message,
|
|
});
|
|
return false; // En caso de error, no bloquear el acceso
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Limpia tokens expirados de la blacklist
|
|
* Nota: Redis hace esto automáticamente con SETEX, esta función es para uso manual si es necesario
|
|
*/
|
|
export async function cleanupBlacklist(): Promise<void> {
|
|
if (!isRedisConnected()) {
|
|
logger.warn('Cannot cleanup blacklist: Redis not connected');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Redis maneja automáticamente la expiración con SETEX
|
|
// Esta función está disponible para limpieza manual si se necesita
|
|
logger.info('Blacklist cleanup completed (handled by Redis TTL)');
|
|
} catch (error) {
|
|
logger.error('Error during blacklist cleanup', {
|
|
error: (error as Error).message,
|
|
});
|
|
}
|
|
}
|