import crypto from 'crypto' import { hashPassword, readUsers, revokeRefreshSessionsForUser, writeUsers } from '../../../utils/auth.js' import { getClientIp } from '../../../utils/rate-limit.js' import { writeAuditLog } from '../../../utils/audit-log.js' import { writePasswordResetLog } from '../../../utils/password-reset-log.js' function hashResetToken(token) { return crypto.createHash('sha256').update(String(token), 'utf8').digest('hex') } function isStrongEnoughPassword(password) { return typeof password === 'string' && password.length >= 8 } export default defineEventHandler(async (event) => { const requestId = crypto.randomUUID() const ip = getClientIp(event) const body = await readBody(event) const token = String(body?.token || '').trim() const password = String(body?.password || '') const logStep = async (step, status, detail = {}) => { try { await writePasswordResetLog({ requestId, email: detail.email || '', ip, step, status, ...detail }) } catch (logError) { console.error('Password-Reset-Diagnoselog-Fehler:', logError) } } if (!token) { await logStep('complete_validation', 'failed', { reason: 'token_missing' }) throw createError({ statusCode: 400, message: 'Reset-Link fehlt.' }) } if (!isStrongEnoughPassword(password)) { await logStep('complete_validation', 'failed', { reason: 'password_too_short' }) throw createError({ statusCode: 400, message: 'Das Passwort muss mindestens 8 Zeichen lang sein.' }) } const users = await readUsers() const tokenHash = hashResetToken(token) const now = Date.now() let matchedUser = null let matchedToken = null for (const user of users) { const tokens = Array.isArray(user.passwordResetTokens) ? user.passwordResetTokens : [] const candidate = tokens.find(entry => entry.tokenHash === tokenHash) if (candidate) { matchedUser = user matchedToken = candidate break } } if (!matchedUser || !matchedToken || matchedToken.usedAt || new Date(matchedToken.expiresAt).getTime() <= now) { await logStep('complete_token', 'failed', { reason: 'invalid_or_expired' }) throw createError({ statusCode: 400, message: 'Der Reset-Link ist ungültig oder abgelaufen.' }) } const nowIso = new Date().toISOString() matchedUser.password = await hashPassword(password) matchedUser.passwordResetRequired = false matchedToken.usedAt = nowIso matchedUser.passwordResetTokens = (Array.isArray(matchedUser.passwordResetTokens) ? matchedUser.passwordResetTokens : []) .filter(entry => entry.usedAt || new Date(entry.expiresAt).getTime() > now) const stored = await writeUsers(users) if (!stored) { await logStep('complete_password_storage', 'failed', { userId: matchedUser.id, email: matchedUser.email, reason: 'write_failed' }) throw createError({ statusCode: 500, message: 'Das neue Passwort konnte nicht gespeichert werden.' }) } await revokeRefreshSessionsForUser(matchedUser.id, 'password_reset_completed') await writeAuditLog('auth.reset.complete', { ip, userId: matchedUser.id, requestId }) await logStep('complete_password_storage', 'completed', { userId: matchedUser.id, email: matchedUser.email }) return { success: true, message: 'Ihr Passwort wurde geändert. Sie können sich jetzt mit dem neuen Passwort anmelden.' } })