Login-Rate-Limit je IP UND Benutzername
Deploy App / deploy (push) Successful in 1m6s

Der Login-Zaehler lief nur je IP -- Fehlversuche gegen ein Konto sperrten
dadurch alle Konten derselben IP mit (beim Dogfooding aufgefallen). Neu wird
je IP+Benutzername gezaehlt (normalisiert trim/lowercase); Brute-Force gegen
ein Konto bleibt gebremst, ohne die uebrigen zu treffen. Body wird dafuer vor
dem Limit-Check geparst.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 20:55:37 +02:00
parent b47d3d0a3e
commit 2bb243b896
2 changed files with 11 additions and 6 deletions
+9 -4
View File
@@ -10,16 +10,21 @@ const loginSchema = z.object({
});
export async function POST(request: NextRequest) {
// Bremse gegen Brute-Force: 10 Versuche je IP und 15 Minuten.
const limited = rateLimit(`login:${clientIp(request)}`, 10, 15 * 60 * 1000);
if (!limited.ok) return tooManyRequests(limited.retryAfterSeconds);
const body = await request.json();
const parsed = loginSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json({ error: "Bitte Benutzername und Passwort angeben." }, { status: 400 });
}
// Bremse gegen Brute-Force: 10 Versuche je IP UND Benutzername in 15 Minuten. Bewusst nicht
// nur je IP -- sonst würden Fehlversuche gegen ein Konto alle anderen Konten derselben IP
// mitsperren (z. B. mehrere Testkonten hinter demselben Anschluss). Der Schlüssel wird
// normalisiert (trim/lowercase), damit sich die Bremse nicht per Gross-/Kleinschreibung
// umgehen lässt; die eigentliche Anmeldung prüft den Namen weiterhin exakt.
const rateKey = `login:${clientIp(request)}:${parsed.data.username.trim().toLowerCase()}`;
const limited = rateLimit(rateKey, 10, 15 * 60 * 1000);
if (!limited.ok) return tooManyRequests(limited.retryAfterSeconds);
const user = await verifyUserCredentials(parsed.data.username, parsed.data.password);
if (!user) {
return NextResponse.json({ error: "Benutzername oder Passwort falsch." }, { status: 401 });