chore(docker): .dockerignore angepasst; lokale Build-Schritte in Rebuild-Skripten; Doku/README zu production vs production-prebuilt aktualisiert
This commit is contained in:
@@ -99,19 +99,127 @@ export function checkBookingRateLimit(params) {
|
||||
*/
|
||||
export function getClientIP(headers) {
|
||||
// Check common proxy headers
|
||||
const forwardedFor = headers['x-forwarded-for'];
|
||||
const get = (name) => {
|
||||
if (typeof headers.get === 'function') {
|
||||
// Headers interface
|
||||
const v = headers.get(name);
|
||||
return v === null ? undefined : v;
|
||||
}
|
||||
return headers[name];
|
||||
};
|
||||
const forwardedFor = get('x-forwarded-for');
|
||||
if (forwardedFor) {
|
||||
// x-forwarded-for can contain multiple IPs, take the first one
|
||||
return forwardedFor.split(',')[0].trim();
|
||||
}
|
||||
const realIP = headers['x-real-ip'];
|
||||
const realIP = get('x-real-ip');
|
||||
if (realIP) {
|
||||
return realIP;
|
||||
}
|
||||
const cfConnectingIP = headers['cf-connecting-ip']; // Cloudflare
|
||||
const cfConnectingIP = get('cf-connecting-ip'); // Cloudflare
|
||||
if (cfConnectingIP) {
|
||||
return cfConnectingIP;
|
||||
}
|
||||
// No IP found
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Reset a rate limit entry immediately (e.g., after successful login)
|
||||
*/
|
||||
export function resetRateLimit(key) {
|
||||
rateLimitStore.delete(key);
|
||||
}
|
||||
/**
|
||||
* Convenience helper to reset login attempts for an IP
|
||||
*/
|
||||
export function resetLoginRateLimit(ip) {
|
||||
if (!ip)
|
||||
return;
|
||||
resetRateLimit(`login:ip:${ip}`);
|
||||
}
|
||||
import { getSessionFromCookies } from "./auth.js";
|
||||
/**
|
||||
* Enforce admin rate limiting by IP and user. Throws standardized German error on exceed.
|
||||
*/
|
||||
export async function enforceAdminRateLimit(context) {
|
||||
const ip = getClientIP(context.req.raw.headers);
|
||||
const session = await getSessionFromCookies(context);
|
||||
if (!session)
|
||||
return; // No session -> owner assertion elsewhere; no per-user throttling
|
||||
const result = checkAdminRateLimit({ ip, userId: session.userId });
|
||||
if (!result.allowed) {
|
||||
throw new Error(`Zu viele Admin-Anfragen. Bitte versuche es in ${result.retryAfterSeconds} Sekunden erneut.`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Brute-Force-Schutz für Logins (IP-basiert)
|
||||
*
|
||||
* Konfiguration:
|
||||
* - max. 5 Versuche je IP in 15 Minuten
|
||||
*
|
||||
* Schlüssel: "login:ip:${ip}"
|
||||
*/
|
||||
export function checkLoginRateLimit(ip) {
|
||||
// Wenn keine IP ermittelbar ist, erlauben (kein Tracking möglich)
|
||||
if (!ip) {
|
||||
return {
|
||||
allowed: true,
|
||||
remaining: 5,
|
||||
resetAt: Date.now() + 15 * 60 * 1000,
|
||||
};
|
||||
}
|
||||
const loginConfig = {
|
||||
maxRequests: 5,
|
||||
windowMs: 15 * 60 * 1000, // 15 Minuten
|
||||
};
|
||||
const key = `login:ip:${ip}`;
|
||||
return checkRateLimit(key, loginConfig);
|
||||
}
|
||||
/**
|
||||
* Rate Limiting für Admin-Operationen
|
||||
*
|
||||
* Konfigurationen (beide Checks werden geprüft, restriktiverer gewinnt):
|
||||
* - Benutzer-basiert: 30 Anfragen je Benutzer in 5 Minuten
|
||||
* - IP-basiert: 50 Anfragen je IP in 5 Minuten
|
||||
*
|
||||
* Schlüssel:
|
||||
* - "admin:user:${userId}"
|
||||
* - "admin:ip:${ip}"
|
||||
*/
|
||||
export function checkAdminRateLimit(params) {
|
||||
const { ip, userId } = params;
|
||||
const userConfig = {
|
||||
maxRequests: 30,
|
||||
windowMs: 5 * 60 * 1000, // 5 Minuten
|
||||
};
|
||||
const ipConfig = {
|
||||
maxRequests: 50,
|
||||
windowMs: 5 * 60 * 1000, // 5 Minuten
|
||||
};
|
||||
const userKey = `admin:user:${userId}`;
|
||||
const userResult = checkRateLimit(userKey, userConfig);
|
||||
// Wenn Benutzerlimit bereits überschritten ist, direkt zurückgeben
|
||||
if (!userResult.allowed) {
|
||||
return { ...userResult, allowed: false };
|
||||
}
|
||||
// Falls IP verfügbar, zusätzlich prüfen
|
||||
if (ip) {
|
||||
const ipKey = `admin:ip:${ip}`;
|
||||
const ipResult = checkRateLimit(ipKey, ipConfig);
|
||||
if (!ipResult.allowed) {
|
||||
return { ...ipResult, allowed: false };
|
||||
}
|
||||
// Beide Checks erlaubt: restriktivere Restwerte/Reset nehmen
|
||||
return {
|
||||
allowed: true,
|
||||
remaining: Math.min(userResult.remaining, ipResult.remaining),
|
||||
resetAt: Math.min(userResult.resetAt, ipResult.resetAt),
|
||||
};
|
||||
}
|
||||
// Kein IP-Check möglich
|
||||
return {
|
||||
allowed: true,
|
||||
remaining: userResult.remaining,
|
||||
resetAt: userResult.resetAt,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user