import { headers } from "next/headers"; /** * Tiny in-process fixed-window rate limiter for abuse-prone server actions * (register, password reset, login). It's per-node (not shared across * instances) — fine for a single-server retro hotel; swap for Redis if you * ever scale out. Keys are typically `${action}:${ip}`. */ type Bucket = { count: number; resetAt: number }; const buckets = new Map(); export interface RateLimitResult { ok: boolean; /** Seconds until the window resets (0 when allowed). */ retryAfter: number; } export function rateLimit(key: string, limit: number, windowMs: number): RateLimitResult { const now = Date.now(); // Opportunistic cleanup so the map can't grow without bound. if (buckets.size > 5000) { for (const [k, b] of buckets) if (now >= b.resetAt) buckets.delete(k); } const bucket = buckets.get(key); if (!bucket || now >= bucket.resetAt) { buckets.set(key, { count: 1, resetAt: now + windowMs }); return { ok: true, retryAfter: 0 }; } if (bucket.count >= limit) { return { ok: false, retryAfter: Math.max(1, Math.ceil((bucket.resetAt - now) / 1000)) }; } bucket.count += 1; return { ok: true, retryAfter: 0 }; } /** Best-effort client IP from the proxy headers our edge proxy forwards. */ export async function clientIp(): Promise { try { const h = await headers(); return ( h.get("x-real-client-ip") ?? h.get("cf-connecting-ip") ?? h.get("x-forwarded-for")?.split(",")[0]?.trim() ?? h.get("x-real-ip") ?? "0.0.0.0" ); } catch { return "0.0.0.0"; } }