diff --git a/messages/de.json b/messages/de.json index 0e35b2b9c..74149fdad 100644 --- a/messages/de.json +++ b/messages/de.json @@ -1326,10 +1326,10 @@ "deleteAllData": "Alle Daten global löschen", "deleteAllDescription": "Löscht alle Mess-, Medikamenten-, Integrations- und Protokoll-Daten aller Benutzer. Benutzerkonten bleiben bestehen.", "deleteAllConfirm": "Alle Daten wirklich löschen?", - "deleteAllConfirmDescription": "Dieser Vorgang löscht alle Messwerte, Medikamente, Einnahmen und Integrationsdaten aller Benutzer unwiderruflich. Benutzerkonten bleiben erhalten.", + "deleteAllConfirmDescription": "Dieser Vorgang löscht alle Messwerte, Medikamente, Einnahmen, API-Tokens, Withings-Verbindungen und offene Authentifizierungs-Challenges aller Benutzer unwiderruflich. Benutzerkonten und das Audit-Log bleiben erhalten, damit der Löschvorgang selbst nachvollziehbar bleibt.", "deleteButton": "Alle Daten löschen", "finalDelete": "Endgültig löschen", - "deletedResult": "Gelöscht: {measurements} Messwerte, {medications} Medikamente, {intakeEvents} Einnahmen", + "deletedResult": "Gelöscht: {measurements} Messwerte, {medications} Medikamente, {intakeEvents} Einnahmen, {apiTokens} API-Tokens, {withingsConnections} Withings-Verbindungen, {authChallenges} Auth-Challenges", "authRegister": "Registrierung", "authLogin": "Anmeldung", "authLoginPasskey": "Passkey-Anmeldung", diff --git a/messages/en.json b/messages/en.json index 87dbb3f06..883752108 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1326,10 +1326,10 @@ "deleteAllData": "Delete all data globally", "deleteAllDescription": "Deletes all measurement, medication, integration, and log data for all users. User accounts will be preserved.", "deleteAllConfirm": "Really delete all data?", - "deleteAllConfirmDescription": "This will permanently delete all measurements, medications, intake records, and integration data for all users. User accounts will be preserved.", + "deleteAllConfirmDescription": "This will permanently delete all measurements, medications, intake events, API tokens, Withings connections, and any open authentication challenges for all users. User accounts and the audit log are preserved so the wipe itself remains traceable.", "deleteButton": "Delete all data", "finalDelete": "Delete permanently", - "deletedResult": "Deleted: {measurements} measurements, {medications} medications, {intakeEvents} intakes", + "deletedResult": "Deleted: {measurements} measurements, {medications} medications, {intakeEvents} intakes, {apiTokens} API tokens, {withingsConnections} Withings connections, {authChallenges} auth challenges", "authRegister": "Registration", "authLogin": "Login", "authLoginPasskey": "Passkey login", diff --git a/src/app/api/admin/data/route.ts b/src/app/api/admin/data/route.ts index 11837eda0..a8c160ca0 100644 --- a/src/app/api/admin/data/route.ts +++ b/src/app/api/admin/data/route.ts @@ -37,13 +37,23 @@ export const DELETE = apiHandler(async (request: NextRequest) => { return apiError("Confirmation missing", 422); } + // Audit-log the *intent* before the transaction begins so the trail + // describing "an admin is about to wipe data" survives even if the + // operation crashes midway. AuditLog rows are intentionally NOT + // deleted as part of the wipe — the entire point of an audit trail + // is to outlive the data it documents. + await auditLog("admin.data.clear.start", { + userId: user.id, + ipAddress: getClientIp(request), + details: { confirm: "DELETE ALL" }, + }); + const result = await prisma.$transaction(async (tx) => { const measurements = await tx.measurement.deleteMany({}); const intakeEvents = await tx.medicationIntakeEvent.deleteMany({}); const medications = await tx.medication.deleteMany({}); const apiTokens = await tx.apiToken.deleteMany({}); const withingsConnections = await tx.withingsConnection.deleteMany({}); - const auditLogs = await tx.auditLog.deleteMany({}); const authChallenges = await tx.authChallenge.deleteMany({}); await tx.user.updateMany({ @@ -69,7 +79,6 @@ export const DELETE = apiHandler(async (request: NextRequest) => { medications: medications.count, apiTokens: apiTokens.count, withingsConnections: withingsConnections.count, - auditLogs: auditLogs.count, authChallenges: authChallenges.count, }; }); diff --git a/src/components/admin/danger-zone-section.tsx b/src/components/admin/danger-zone-section.tsx index 999bf5ab4..abcd06b18 100644 --- a/src/components/admin/danger-zone-section.tsx +++ b/src/components/admin/danger-zone-section.tsx @@ -35,6 +35,9 @@ export function DangerZoneSection({ id }: { id: string }) { measurements: number; intakeEvents: number; medications: number; + apiTokens: number; + withingsConnections: number; + authChallenges: number; }; }, onSuccess: (data) => { @@ -44,6 +47,9 @@ export function DangerZoneSection({ id }: { id: string }) { measurements: data.measurements, medications: data.medications, intakeEvents: data.intakeEvents, + apiTokens: data.apiTokens, + withingsConnections: data.withingsConnections, + authChallenges: data.authChallenges, }), ); }, @@ -107,7 +113,7 @@ export function DangerZoneSection({ id }: { id: string }) { {wipeMsg && (

{wipeMsg}