import { kv } from '@vercel/kv'; /** * Rate limit configuration */ const RATE_LIMIT_WINDOW = 60 * 60 * 1000; // 1 hour in milliseconds const MAX_REQUESTS_PER_WINDOW = 5; /** * Rate limit key prefix */ const RATE_LIMIT_PREFIX = 'ratelimit:signup:'; /** * Check if an IP address has exceeded rate limits * @param identifier - IP address or unique identifier * @returns Object with allowed status and retry information */ export async function checkRateLimit(identifier: string): Promise<{ allowed: boolean; remaining: number; resetAt: number; }> { const key = `${RATE_LIMIT_PREFIX}${identifier}`; const now = Date.now(); try { // Get current count const count = await kv.get(key) || 0; if (count >= MAX_REQUESTS_PER_WINDOW) { // Get TTL to determine reset time const ttl = await kv.ttl(key); const resetAt = now + (ttl * 1000); return { allowed: false, remaining: 0, resetAt, }; } // Increment counter const newCount = count + 1; if (count === 0) { // First request - set with expiry await kv.set(key, newCount, { px: RATE_LIMIT_WINDOW, }); } else { // Subsequent request - increment without changing TTL await kv.incr(key); } // Calculate reset time const ttl = await kv.ttl(key); const resetAt = now + (ttl * 1000); return { allowed: true, remaining: MAX_REQUESTS_PER_WINDOW - newCount, resetAt, }; } catch (error) { // If rate limiting fails, allow the request console.error('Rate limit check failed:', error); return { allowed: true, remaining: MAX_REQUESTS_PER_WINDOW, resetAt: now + RATE_LIMIT_WINDOW, }; } } /** * Get client IP from request headers */ export function getClientIp(request: Request): string { // Try various headers that might contain the IP const headers = request.headers; return ( headers.get('x-forwarded-for')?.split(',')[0]?.trim() || headers.get('x-real-ip') || headers.get('cf-connecting-ip') || // Cloudflare 'unknown' ); }