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:
@@ -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 });
|
||||
|
||||
Reference in New Issue
Block a user