import { readUsers, writeUsers, verifyPassword, generateToken, createSession, migrateUserRoles } from '../../utils/auth.js' import { assertRateLimit, getClientIp, registerRateLimitFailure, registerRateLimitSuccess } from '../../utils/rate-limit.js' import { getAuthCookieOptions } from '../../utils/cookies.js' import { writeAuditLog } from '../../utils/audit-log.js' export default defineEventHandler(async (event) => { try { const body = await readBody(event) const { email, password } = body if (!email || !password) { throw createError({ statusCode: 400, message: 'E-Mail und Passwort sind erforderlich' }) } const ip = getClientIp(event) const emailKey = String(email || '').trim().toLowerCase() // Rate Limiting (IP + Account) assertRateLimit(event, { name: 'auth:login:ip', keyParts: [ip], windowMs: 10 * 60 * 1000, maxAttempts: 30, lockoutMs: 15 * 60 * 1000 }) assertRateLimit(event, { name: 'auth:login:account', keyParts: [emailKey], windowMs: 10 * 60 * 1000, maxAttempts: 10, lockoutMs: 30 * 60 * 1000 }) // Find user const users = await readUsers() const user = users.find(u => u.email.toLowerCase() === email.toLowerCase()) if (!user) { await registerRateLimitFailure(event, { name: 'auth:login:ip', keyParts: [ip] }) await registerRateLimitFailure(event, { name: 'auth:login:account', keyParts: [emailKey] }) await writeAuditLog('auth.login.failed', { ip, email: emailKey, reason: 'user_not_found' }) throw createError({ statusCode: 401, message: 'Ungültige Anmeldedaten' }) } // Check if user is active if (user.active === false) { throw createError({ statusCode: 403, message: 'Ihr Konto wurde noch nicht freigeschaltet. Bitte warten Sie auf die Bestätigung des Vorstands.' }) } // Verify password const isValid = await verifyPassword(password, user.password) if (!isValid) { await registerRateLimitFailure(event, { name: 'auth:login:ip', keyParts: [ip] }) await registerRateLimitFailure(event, { name: 'auth:login:account', keyParts: [emailKey] }) await writeAuditLog('auth.login.failed', { ip, email: emailKey, userId: user.id, reason: 'bad_password' }) throw createError({ statusCode: 401, message: 'Ungültige Anmeldedaten' }) } // Erfolg: Limiter zurücksetzen registerRateLimitSuccess(event, { name: 'auth:login:ip', keyParts: [ip] }) registerRateLimitSuccess(event, { name: 'auth:login:account', keyParts: [emailKey] }) // Generate token const token = generateToken(user) // Create session await createSession(user.id, token) await writeAuditLog('auth.login.success', { ip, email: emailKey, userId: user.id }) // Update last login user.lastLogin = new Date().toISOString() const updatedUsers = users.map(u => u.id === user.id ? user : u) await writeUsers(updatedUsers) // Set cookie setCookie(event, 'auth_token', token, { ...getAuthCookieOptions() }) // Migriere Rollen falls nötig const migratedUser = migrateUserRoles({ ...user }) const roles = Array.isArray(migratedUser.roles) ? migratedUser.roles : (migratedUser.role ? [migratedUser.role] : ['mitglied']) // Return user data (without password) and token for API usage return { success: true, token: token, // Token auch im Body für externe API-Clients user: { id: user.id, email: user.email, name: user.name, roles: roles }, // Rückwärtskompatibilität: erste Rolle als role role: roles[0] || 'mitglied' } } catch (error) { console.error('Login-Fehler:', error) throw error } })