import type { APIRoute } from 'astro'; import { login, makeSessionCookie, rateLimited, noteFailure, clearFailures } from '../../../lib/auth'; import { echteIp, zuOft } from '../../../lib/limit'; export const prerender = false; export const POST: APIRoute = async ({ request, clientAddress }) => { // Nicht `clientAddress` direkt: Das ist der **erste** Wert aus // X-Forwarded-For und damit vom Aufrufer selbst gesetzt — die Sperre nach // fünf Fehlversuchen ließe sich sonst mit einem wechselnden Header umgehen. const ip = echteIp(request, clientAddress); if (rateLimited(ip)) { return new Response(JSON.stringify({ error: 'Zu viele Versuche. Bitte in 15 Minuten erneut.' }), { status: 429, headers: { 'Content-Type': 'application/json' } }); } let username = '', password = ''; const ct = request.headers.get('content-type') || ''; if (ct.includes('application/json')) { const b = await request.json().catch(() => ({})); username = b.username || ''; password = b.password || ''; } else { const f = await request.formData(); username = String(f.get('username') || ''); password = String(f.get('password') || ''); } // Zusätzlich je Benutzername zählen — sonst hilft die IP-Sperre nichts, wenn // der Angreifer aus vielen Netzen kommt. if (zuOft('login-user:' + username.trim().toLowerCase(), 10, 15 * 60_000)) { return new Response(JSON.stringify({ error: 'Zu viele Versuche. Bitte in 15 Minuten erneut.' }), { status: 429, headers: { 'Content-Type': 'application/json' } }); } const angemeldet = await login(username.trim(), password); if (!angemeldet) { noteFailure(ip); return new Response(JSON.stringify({ error: 'Benutzername oder Passwort falsch.' }), { status: 401, headers: { 'Content-Type': 'application/json' } }); } clearFailures(ip); return new Response(JSON.stringify({ ok: true, role: angemeldet.user.role }), { status: 200, headers: { 'Content-Type': 'application/json', 'Set-Cookie': makeSessionCookie(angemeldet.user, angemeldet.epoch) }, }); };