const RATE_LIMIT_WINDOW = 60 * 60 * 1000; // 1 hour in milliseconds const MAX_REQUESTS_PER_WINDOW = 5; const RATE_LIMIT_PREFIX = 'ratelimit:signup:'; const isDev = !process.env.KV_REST_API_URL; // In-memory fallback for development const devRateLimits = new Map(); export async function checkRateLimit(identifier: string): Promise<{ allowed: boolean; remaining: number; resetAt: number; }> { const now = Date.now(); if (isDev) { console.warn('[DEV MODE] Using in-memory rate limiting - data will not persist'); const existing = devRateLimits.get(identifier); if (existing && existing.resetAt > now) { if (existing.count >= MAX_REQUESTS_PER_WINDOW) { return { allowed: false, remaining: 0, resetAt: existing.resetAt, }; } existing.count += 1; devRateLimits.set(identifier, existing); return { allowed: true, remaining: MAX_REQUESTS_PER_WINDOW - existing.count, resetAt: existing.resetAt, }; } const resetAt = now + RATE_LIMIT_WINDOW; devRateLimits.set(identifier, { count: 1, resetAt }); return { allowed: true, remaining: MAX_REQUESTS_PER_WINDOW - 1, resetAt, }; } // Production: Use Vercel KV const { kv } = await import('@vercel/kv'); const key = `${RATE_LIMIT_PREFIX}${identifier}`; try { const count = await kv.get(key) || 0; if (count >= MAX_REQUESTS_PER_WINDOW) { const ttl = await kv.ttl(key); const resetAt = now + (ttl * 1000); return { allowed: false, remaining: 0, resetAt, }; } const newCount = count + 1; if (count === 0) { await kv.set(key, newCount, { px: RATE_LIMIT_WINDOW, }); } else { await kv.incr(key); } const ttl = await kv.ttl(key); const resetAt = now + (ttl * 1000); return { allowed: true, remaining: MAX_REQUESTS_PER_WINDOW - newCount, resetAt, }; } catch (error) { 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' ); }