import { Injectable, ExecutionContext } from "@nestjs/common"; import { ThrottlerGuard, ThrottlerException } from "@nestjs/throttler"; interface RequestWithHeaders { headers?: Record; ip?: string; connection?: { remoteAddress?: string }; url?: string; } /** * OrchestratorThrottlerGuard - Rate limiting guard for orchestrator API endpoints * * Uses the X-Forwarded-For header for client IP identification when behind a proxy, * falling back to the direct connection IP. * * Usage: * @UseGuards(OrchestratorThrottlerGuard) * @Controller('agents') * export class AgentsController { ... } */ @Injectable() export class OrchestratorThrottlerGuard extends ThrottlerGuard { /** * Get the client IP address for rate limiting tracking * Prioritizes X-Forwarded-For header for proxy setups */ protected getTracker(req: Record): Promise { const request = req as RequestWithHeaders; const headers = request.headers; // Check X-Forwarded-For header first (for proxied requests) if (headers) { const forwardedFor = headers["x-forwarded-for"]; if (forwardedFor) { // Get the first IP in the chain (original client) const ips = Array.isArray(forwardedFor) ? forwardedFor[0] : forwardedFor; if (ips) { const clientIp = ips.split(",")[0]?.trim(); if (clientIp) { return Promise.resolve(clientIp); } } } } // Fallback to direct connection IP const ip = request.ip ?? request.connection?.remoteAddress ?? "unknown"; return Promise.resolve(ip); } /** * Custom error message for rate limit exceeded */ protected throwThrottlingException(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); const endpoint = request.url ?? "unknown"; throw new ThrottlerException( `Rate limit exceeded for endpoint ${endpoint}. Please try again later.` ); } }