// Einfacher In-Memory-Rate-Limiter (ein Container, eine Instanz → genügt). // Fixed-Window pro Schlüssel (Standard: Client-IP). Bei Überschreitung 429. // Hinter einem Reverse-Proxy liefert X-Forwarded-For die echte IP. const buckets = new Map(); // key -> { count, reset } function clientIp(c) { const xff = c.req.header('x-forwarded-for'); if (xff) return xff.split(',')[0].trim(); return c.req.header('x-real-ip') || 'unknown'; } // max Anfragen je windowMs. keyFn erlaubt eigene Schlüssel (z.B. IP+E-Mail). export function rateLimit({ max = 10, windowMs = 60_000, keyFn = clientIp } = {}) { return async (c, next) => { const key = keyFn(c); const now = Date.now(); let b = buckets.get(key); if (!b || now > b.reset) { b = { count: 0, reset: now + windowMs }; buckets.set(key, b); } b.count += 1; if (b.count > max) { const retry = Math.ceil((b.reset - now) / 1000); c.header('Retry-After', String(retry)); return c.json({ error: 'Zu viele Anfragen — bitte später erneut.' }, 429); } await next(); }; } // Speicher sauber halten: abgelaufene Buckets periodisch wegräumen. setInterval(() => { const now = Date.now(); for (const [k, b] of buckets) if (now > b.reset) buckets.delete(k); }, 5 * 60_000).unref?.();