/** * Custom Login Endpoint mit Audit-Logging * * POST /api/auth/login * * Dieser Endpoint wrappet den nativen Payload-Login und loggt fehlgeschlagene * Login-Versuche im Audit-Log. Erfolgreiche Logins werden durch den * afterLogin-Hook in der Users-Collection geloggt. * * Security: * - Rate-Limiting: 5 Versuche pro 15 Minuten pro IP (Anti-Brute-Force) * - IP-Blocklist-Prüfung * - Audit-Logging für fehlgeschlagene Logins * * Body: * - email: string (erforderlich) * - password: string (erforderlich) */ import { getPayload } from 'payload' import configPromise from '@payload-config' import { NextRequest, NextResponse } from 'next/server' import { logLoginFailed, logRateLimit } from '@/lib/audit/audit-service' import { authLimiter, rateLimitHeaders, getClientIpFromRequest, isIpBlocked, } from '@/lib/security' /** * Extrahiert Client-Informationen aus dem Request für Audit-Logging */ function getClientInfo(req: NextRequest): { ipAddress: string; userAgent: string } { const forwarded = req.headers.get('x-forwarded-for') const realIp = req.headers.get('x-real-ip') const ipAddress = (forwarded ? forwarded.split(',')[0]?.trim() : undefined) || realIp || 'unknown' const userAgent = req.headers.get('user-agent') || 'unknown' return { ipAddress, userAgent } } export async function POST(req: NextRequest): Promise { try { // IP-Blocklist prüfen const clientIp = getClientIpFromRequest(req) if (isIpBlocked(clientIp)) { return NextResponse.json( { success: false, error: 'Access denied' }, { status: 403 }, ) } // Rate-Limiting prüfen (Anti-Brute-Force) const rateLimit = await authLimiter.check(clientIp) if (!rateLimit.allowed) { const payload = await getPayload({ config: configPromise }) await logRateLimit(payload, '/api/auth/login', undefined, undefined) return NextResponse.json( { success: false, error: 'Too many login attempts. Please try again later.', }, { status: 429, headers: rateLimitHeaders(rateLimit, 5), }, ) } const payload = await getPayload({ config: configPromise }) const body = await req.json() const { email, password } = body // Validierung if (!email || !password) { return NextResponse.json( { error: 'E-Mail und Passwort sind erforderlich' }, { status: 400 }, ) } try { // Versuche Login über Payload const result = await payload.login({ collection: 'users', data: { email, password, }, }) // Erfolgreicher Login - afterLogin Hook hat bereits geloggt // Setze Cookie für die Session const response = NextResponse.json({ success: true, user: { id: result.user.id, email: result.user.email, }, message: 'Login erfolgreich', }) // Set the token cookie if (result.token) { response.cookies.set('payload-token', result.token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', path: '/', // Token expiration (default 2 hours) maxAge: result.exp ? result.exp - Math.floor(Date.now() / 1000) : 7200, }) } return response } catch (loginError) { // Login fehlgeschlagen - Audit-Log erstellen mit vollem Context const errorMessage = loginError instanceof Error ? loginError.message : 'Unbekannter Fehler' // Bestimme den Grund für das Fehlschlagen let reason = 'Unbekannter Fehler' if (errorMessage.includes('not found') || errorMessage.includes('incorrect')) { reason = 'Ungültige Anmeldedaten' } else if (errorMessage.includes('locked')) { reason = 'Konto gesperrt' } else if (errorMessage.includes('disabled')) { reason = 'Konto deaktiviert' } else if (errorMessage.includes('verify')) { reason = 'E-Mail nicht verifiziert' } else { reason = errorMessage } // Client-Info für Audit-Log extrahieren const clientInfo = getClientInfo(req) // Audit-Log für fehlgeschlagenen Login mit vollem Client-Context await logLoginFailed(payload, email, reason, clientInfo) console.log( `[Audit:Auth] Login failed for ${email}: ${reason} (IP: ${clientInfo.ipAddress})`, ) return NextResponse.json( { success: false, error: 'Anmeldung fehlgeschlagen', // Keine detaillierten Infos aus Sicherheitsgründen }, { status: 401 }, ) } } catch (error) { console.error('[API:Auth] Login error:', error) return NextResponse.json( { error: 'Interner Serverfehler' }, { status: 500 }, ) } } /** * GET /api/auth/login * * Gibt API-Dokumentation zurück. */ export async function GET(): Promise { return NextResponse.json({ endpoint: '/api/auth/login', method: 'POST', description: 'Login endpoint with audit logging for failed attempts', body: { email: 'string (required)', password: 'string (required)', }, response: { success: { success: true, user: { id: 'number', email: 'string' }, message: 'string', }, error: { success: false, error: 'string', }, }, note: 'Successful logins are logged via afterLogin hook. Failed attempts are logged here.', }) }