35 lines
1.3 KiB
JavaScript
35 lines
1.3 KiB
JavaScript
// 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?.();
|