Merge commit '80eca1bc7c1174f9c44a8df9af4ddc8ab855291b' as 'cms/core'
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
// 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?.();
|
||||
Reference in New Issue
Block a user