diff --git a/src/middleware/pdfRateLimit.ts b/src/middleware/pdfRateLimit.ts index e34d64a..0d64d89 100644 --- a/src/middleware/pdfRateLimit.ts +++ b/src/middleware/pdfRateLimit.ts @@ -43,7 +43,7 @@ function getRateLimit(apiKey: string): number { return isProKey(apiKey) ? PRO_RATE_LIMIT : FREE_RATE_LIMIT; } -function checkRateLimit(apiKey: string): boolean { +function checkRateLimit(apiKey: string): RateLimitResult { cleanupExpiredEntries(); const now = Date.now(); @@ -51,19 +51,35 @@ function checkRateLimit(apiKey: string): boolean { const entry = rateLimitStore.get(apiKey); if (!entry || now >= entry.resetTime) { + const resetTime = now + RATE_WINDOW_MS; rateLimitStore.set(apiKey, { count: 1, - resetTime: now + RATE_WINDOW_MS + resetTime }); - return true; + return { + allowed: true, + limit, + remaining: limit - 1, + resetTime + }; } if (entry.count >= limit) { - return false; + return { + allowed: false, + limit, + remaining: 0, + resetTime: entry.resetTime + }; } entry.count++; - return true; + return { + allowed: true, + limit, + remaining: limit - entry.count, + resetTime: entry.resetTime + }; } function getQueuedCountForKey(apiKey: string): number { @@ -106,10 +122,18 @@ export function pdfRateLimitMiddleware(req: Request & { apiKeyInfo?: any }, res: const apiKey = keyInfo?.key || "unknown"; // Check rate limit first - if (!checkRateLimit(apiKey)) { - const limit = getRateLimit(apiKey); + const rateLimitResult = checkRateLimit(apiKey); + + // Set rate limit headers on ALL responses + res.set('X-RateLimit-Limit', String(rateLimitResult.limit)); + res.set('X-RateLimit-Remaining', String(rateLimitResult.remaining)); + res.set('X-RateLimit-Reset', String(Math.ceil(rateLimitResult.resetTime / 1000))); + + if (!rateLimitResult.allowed) { const tier = isProKey(apiKey) ? "pro" : "free"; - res.status(429).json({ error: `Rate limit exceeded: ${limit} PDFs/min allowed for ${tier} tier. Retry after 60s.` }); + const retryAfterSeconds = Math.ceil((rateLimitResult.resetTime - Date.now()) / 1000); + res.set('Retry-After', String(retryAfterSeconds)); + res.status(429).json({ error: `Rate limit exceeded: ${rateLimitResult.limit} PDFs/min allowed for ${tier} tier. Retry after ${retryAfterSeconds}s.` }); return; }