Sistema NEXUS v3.4 migrado con: Estructura principal: - core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles) - core/catalog: Catalogo de funcionalidades reutilizables - shared/knowledge-base: Base de conocimiento compartida - devtools/scripts: Herramientas de desarrollo - control-plane/registries: Control de servicios y CI/CD - orchestration/: Configuracion de orquestacion de agentes Proyectos incluidos (11): - gamilit (submodule -> GitHub) - trading-platform (OrbiquanTIA) - erp-suite con 5 verticales: - erp-core, construccion, vidrio-templado - mecanicas-diesel, retail, clinicas - betting-analytics - inmobiliaria-analytics - platform_marketing_content - pos-micro, erp-basico Configuracion: - .gitignore completo para Node.js/Python/Docker - gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git) - Sistema de puertos estandarizado (3005-3199) Generated with NEXUS v3.4 Migration System EPIC-010: Configuracion Git y Repositorios
187 lines
4.4 KiB
TypeScript
187 lines
4.4 KiB
TypeScript
/**
|
|
* RATE LIMITER SERVICE - REFERENCE IMPLEMENTATION
|
|
*
|
|
* @description Servicio de rate limiting para proteger endpoints.
|
|
* Implementación in-memory simple con soporte para diferentes estrategias.
|
|
*
|
|
* @usage
|
|
* ```typescript
|
|
* // En middleware o guard
|
|
* const limiter = getRateLimiter({ windowMs: 60000, max: 100 });
|
|
* if (limiter.isRateLimited(req.ip)) {
|
|
* throw new TooManyRequestsException();
|
|
* }
|
|
* ```
|
|
*
|
|
* @origin gamilit/apps/backend/src/shared/services/rate-limiter.service.ts
|
|
*/
|
|
|
|
/**
|
|
* Configuración del rate limiter
|
|
*/
|
|
export interface RateLimitConfig {
|
|
/** Ventana de tiempo en milisegundos */
|
|
windowMs: number;
|
|
/** Máximo de requests por ventana */
|
|
max: number;
|
|
/** Mensaje de error personalizado */
|
|
message?: string;
|
|
/** Función para generar key (default: IP) */
|
|
keyGenerator?: (req: any) => string;
|
|
}
|
|
|
|
/**
|
|
* Estado interno de un cliente
|
|
*/
|
|
interface ClientState {
|
|
count: number;
|
|
resetTime: number;
|
|
}
|
|
|
|
/**
|
|
* Rate Limiter Factory
|
|
*
|
|
* @param config - Configuración del limiter
|
|
* @returns Instancia del rate limiter
|
|
*/
|
|
export function getRateLimiter(config: RateLimitConfig): RateLimiter {
|
|
const clients = new Map<string, ClientState>();
|
|
|
|
// Limpieza periódica de entradas expiradas
|
|
const cleanup = setInterval(() => {
|
|
const now = Date.now();
|
|
for (const [key, state] of clients.entries()) {
|
|
if (state.resetTime <= now) {
|
|
clients.delete(key);
|
|
}
|
|
}
|
|
}, config.windowMs);
|
|
|
|
return {
|
|
/**
|
|
* Verificar si un cliente está rate limited
|
|
*/
|
|
check(key: string): RateLimitResult {
|
|
const now = Date.now();
|
|
const state = clients.get(key);
|
|
|
|
// Cliente nuevo o ventana expirada
|
|
if (!state || state.resetTime <= now) {
|
|
clients.set(key, {
|
|
count: 1,
|
|
resetTime: now + config.windowMs,
|
|
});
|
|
return {
|
|
limited: false,
|
|
remaining: config.max - 1,
|
|
resetTime: now + config.windowMs,
|
|
};
|
|
}
|
|
|
|
// Incrementar contador
|
|
state.count++;
|
|
|
|
// Verificar límite
|
|
if (state.count > config.max) {
|
|
return {
|
|
limited: true,
|
|
remaining: 0,
|
|
resetTime: state.resetTime,
|
|
retryAfter: Math.ceil((state.resetTime - now) / 1000),
|
|
};
|
|
}
|
|
|
|
return {
|
|
limited: false,
|
|
remaining: config.max - state.count,
|
|
resetTime: state.resetTime,
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Resetear contador de un cliente
|
|
*/
|
|
reset(key: string): void {
|
|
clients.delete(key);
|
|
},
|
|
|
|
/**
|
|
* Limpiar todos los contadores
|
|
*/
|
|
clear(): void {
|
|
clients.clear();
|
|
},
|
|
|
|
/**
|
|
* Destruir el limiter (detener cleanup)
|
|
*/
|
|
destroy(): void {
|
|
clearInterval(cleanup);
|
|
clients.clear();
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Interfaz del rate limiter
|
|
*/
|
|
export interface RateLimiter {
|
|
check(key: string): RateLimitResult;
|
|
reset(key: string): void;
|
|
clear(): void;
|
|
destroy(): void;
|
|
}
|
|
|
|
/**
|
|
* Resultado de verificación
|
|
*/
|
|
export interface RateLimitResult {
|
|
limited: boolean;
|
|
remaining: number;
|
|
resetTime: number;
|
|
retryAfter?: number;
|
|
}
|
|
|
|
/**
|
|
* Middleware para Express/NestJS
|
|
*/
|
|
export function rateLimitMiddleware(config: RateLimitConfig) {
|
|
const limiter = getRateLimiter(config);
|
|
const keyGenerator = config.keyGenerator || ((req) => req.ip || 'unknown');
|
|
|
|
return (req: any, res: any, next: () => void) => {
|
|
const key = keyGenerator(req);
|
|
const result = limiter.check(key);
|
|
|
|
// Agregar headers informativos
|
|
res.setHeader('X-RateLimit-Limit', config.max);
|
|
res.setHeader('X-RateLimit-Remaining', result.remaining);
|
|
res.setHeader('X-RateLimit-Reset', result.resetTime);
|
|
|
|
if (result.limited) {
|
|
res.setHeader('Retry-After', result.retryAfter);
|
|
res.status(429).json({
|
|
statusCode: 429,
|
|
message: config.message || 'Too many requests',
|
|
retryAfter: result.retryAfter,
|
|
});
|
|
return;
|
|
}
|
|
|
|
next();
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Error de rate limit
|
|
*/
|
|
export class TooManyRequestsError extends Error {
|
|
public readonly statusCode = 429;
|
|
public readonly retryAfter: number;
|
|
|
|
constructor(retryAfter: number, message?: string) {
|
|
super(message || 'Too many requests');
|
|
this.retryAfter = retryAfter;
|
|
}
|
|
}
|