import { Context, Next } from 'hono'; import { config } from '../config'; interface RateLimitStore { [key: string]: { count: number; resetTime: number; }; } const store: RateLimitStore = {}; // Simple in-memory rate limiter export const rateLimitMiddleware = async (c: Context, next: Next) => { const ip = c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'; const now = Date.now(); const windowStart = now - config.rateLimit.windowMs; // Clean up old entries Object.keys(store).forEach(key => { if (store[key].resetTime < windowStart) { delete store[key]; } }); // Check rate limit if (!store[ip]) { store[ip] = { count: 1, resetTime: now + config.rateLimit.windowMs }; } else if (store[ip].resetTime < now) { store[ip] = { count: 1, resetTime: now + config.rateLimit.windowMs }; } else { store[ip].count++; } if (store[ip].count > config.rateLimit.max) { return c.json( { error: 'Too many requests', retryAfter: Math.ceil((store[ip].resetTime - now) / 1000) }, 429, { 'Retry-After': Math.ceil((store[ip].resetTime - now) / 1000).toString(), 'X-RateLimit-Limit': config.rateLimit.max.toString(), 'X-RateLimit-Remaining': '0', 'X-RateLimit-Reset': new Date(store[ip].resetTime).toISOString(), } ); } // Add rate limit headers c.header('X-RateLimit-Limit', config.rateLimit.max.toString()); c.header('X-RateLimit-Remaining', (config.rateLimit.max - store[ip].count).toString()); c.header('X-RateLimit-Reset', new Date(store[ip].resetTime).toISOString()); await next(); };