"use server"; import { prisma } from "@/lib/prisma"; import { getCurrentCustomer, verifyPassword, destroySession } from "@/lib/auth"; import { rateLimit, getClientIp } from "@/lib/rate-limit"; import { sendMail } from "@/lib/mail"; import { securityAlertEmail } from "@/lib/email-templates"; import { getLocale } from "@/i18n/locale"; import { fail, ACCOUNT_PASSWORD_CONFIRM_ATTEMPTS_PER_WINDOW, ACCOUNT_PASSWORD_CONFIRM_WINDOW_MS } from "./_shared"; import { succeed } from "@/lib/flash"; /** * Selbstbedienungs-Kontoloeschung (Art. 17 DSGVO). Bestellungen bleiben aus * Aufbewahrungspflichten erhalten (Order.customer hat onDelete: SetNull) — * sie speichern Name/Adresse bereits als eigenstaendigen Snapshot, sodass die * Rechnung auch ohne verknuepftes Kundenkonto vollstaendig bleibt. Alle * anderen persoenlichen Daten (Adressen, Wunschliste, Bewertungen, * Newsletter, Backup-Codes) werden per Cascade mitgeloescht. */ export async function deleteOwnAccountAction(formData: FormData): Promise { const customer = await getCurrentCustomer(); if (!customer) return fail("/konto", "Bitte melde dich an."); if (customer.role === "ADMIN") { return fail("/konto", "Admin-Konten können nicht selbst gelöscht werden. Bitte kontaktiere einen anderen Admin."); } const ip = await getClientIp(); if ( !rateLimit( `delete-account:${ip}:${customer.id}`, ACCOUNT_PASSWORD_CONFIRM_ATTEMPTS_PER_WINDOW, ACCOUNT_PASSWORD_CONFIRM_WINDOW_MS ) ) { return fail("/konto", "Zu viele Versuche. Bitte versuche es später erneut."); } const password = String(formData.get("password") ?? ""); const passwordValid = await verifyPassword(password, customer.passwordHash); if (!passwordValid) { return fail("/konto", "Passwort ist falsch."); } const { subject, html, text } = await securityAlertEmail(await getLocale(), "accountDeleted"); await sendMail({ to: customer.email, subject, html, text }); await prisma.customer.delete({ where: { id: customer.id } }); await destroySession(); return succeed("/konto", "Dein Konto wurde endgültig gelöscht."); } /** * Widerruft alle bestehenden Sessions — auch das aktuell genutzte Geraet. * Bewusst KEIN Re-Login des aktuellen Tabs mehr (fruehere Variante): "alle * Geraete abmelden" muss auch die eigene, gerade aktive Sitzung beenden, * sonst waere ein gestohlenes/kompromittiertes aktuelles Session-Cookie von * dieser Aktion gar nicht betroffen. */ export async function logoutAllDevicesAction(): Promise { const customer = await getCurrentCustomer(); if (!customer) return fail("/konto", "Bitte melde dich an."); await prisma.customer.update({ where: { id: customer.id }, data: { sessionVersion: { increment: 1 } }, }); await destroySession(); return succeed("/konto", "Alle Geräte wurden abgemeldet. Bitte melde dich erneut an."); }