/* ===================================================================== webdesign-datenschutz.js — Auskunft und Löschung (DSGVO Art. 15/17) Drei Endpunkte, und die Reihenfolge ist Absicht: auskunft — was ist gespeichert (unschädlich, jederzeit) loeschvorschau — was geschähe bei einer Löschung loeschen — führt aus, und nur mit ausdrücklicher Bestätigung Eine Löschung ist endgültig. Sie gehört nicht hinter einen Knopf, der sofort zuschlägt — deshalb ist die Vorschau ein eigener Schritt und die Ausführung verlangt zusätzlich die Adresse als Bestätigung, so wie man es beim Löschen eines Kontos kennt. ===================================================================== */ import { db } from "../db.js"; import { json } from "../lib/http.js"; import { requireSession } from "../lib/auth.js"; import { hasPermission } from "../lib/permissions.js"; import { ausweisPruefen } from "../lib/webdesign-ausweis.js"; import { text, verlauf } from "../lib/webdesign-helfer.js"; import { datenZuPerson, loeschvorschau } from "../lib/webdesign-personendaten.js"; async function darf(req, recht) { const ausweis = ausweisPruefen(req); if (ausweis) return { erlaubt: true, sitzung: { actor: ausweis.rolle, isOwner: true } }; const sitzung = await requireSession(req); if (!sitzung) return { erlaubt: false, grund: 401, sitzung: null }; if (sitzung.isOwner) return { erlaubt: true, sitzung }; const ok = hasPermission(sitzung.permissions, sitzung.overrides, recht); return { erlaubt: ok, grund: ok ? null : 403, sitzung }; } /* Auskunft und Löschung berühren ALLE Kundendaten. Das ist bewusst an das Recht zum Verwalten von Kunden geknüpft, nicht an das blosse Ansehen von Anfragen. */ const RECHT = "WD_KUNDEN_MANAGE"; export async function dsAuskunft(req, res) { const p = await darf(req, RECHT); if (!p.erlaubt) return json(res, { ok: false, error: "Kein Zugriff." }, p.grund); try { const daten = datenZuPerson(text(req.body?.email, 200)); /* Der Zugriff wird protokolliert. Eine Auskunft ist ein Verwaltungsvorgang mit Frist -- man will später belegen können, wann sie erstellt wurde. */ verlauf("datenschutz", daten.email, "auskunft erstellt", p.sitzung?.actor || "unbekannt", { datensaetze: daten.datensaetze }); return json(res, { ok: true, ...daten }); } catch (e) { return json(res, { ok: false, error: e.message || "Fehlgeschlagen." }, 400); } } export async function dsLoeschvorschau(req, res) { const p = await darf(req, RECHT); if (!p.erlaubt) return json(res, { ok: false, error: "Kein Zugriff." }, p.grund); try { return json(res, { ok: true, ...loeschvorschau(text(req.body?.email, 200)) }); } catch (e) { return json(res, { ok: false, error: e.message || "Fehlgeschlagen." }, 400); } } export async function dsLoeschen(req, res) { const p = await darf(req, RECHT); if (!p.erlaubt) return json(res, { ok: false, error: "Kein Zugriff." }, p.grund); const email = text(req.body?.email, 200); const bestaetigung = text(req.body?.bestaetigung, 200); /* Die Adresse muss ein zweites Mal eingegeben werden. Das ist keine Schikane: Der Unterschied zwischen "anna@example.org" und "anna@example.com" ist ein Buchstabe, und die Folge wäre, dass die Daten der falschen Person gelöscht werden -- unwiederbringlich. */ if (!email || email.toLowerCase() !== bestaetigung.toLowerCase()) { return json(res, { ok: false, error: "Zur Bestätigung bitte dieselbe E-Mail-Adresse ein zweites Mal eingeben.", }, 400); } let vorschau; try { vorschau = loeschvorschau(email); } catch (e) { return json(res, { ok: false, error: e.message }, 400); } if (!vorschau.loeschbar.length) { return json(res, { ok: true, geloescht: 0, hinweis: vorschau.bleibt.length ? "Es gibt nichts zu löschen — alles Gefundene unterliegt einer Aufbewahrungspflicht." : "Zu dieser Adresse ist nichts gespeichert.", bleibt: vorschau.bleibt, }); } /* Alles oder nichts. Bricht es mittendrin ab, bliebe ein halbgelöschter Bestand zurück -- und niemand wüsste, welcher Teil schon weg ist. */ const daten = datenZuPerson(email); let geloescht = 0; const durchfuehren = db.transaction(() => { for (const bereich of vorschau.loeschbar) { const b = daten.bereiche.find((x) => x.tabelle === bereich.tabelle); if (!b) continue; for (const zeile of b.zeilen) { if (!zeile.id) continue; const r = db.prepare(`DELETE FROM ${bereich.tabelle} WHERE id = ?`).run(zeile.id); geloescht += r.changes; } } }); try { durchfuehren(); } catch (e) { return json(res, { ok: false, error: "Löschung abgebrochen: " + e.message }, 500); } /* Der Vorgang selbst wird festgehalten -- OHNE die gelöschten Inhalte. Man muss belegen können, DASS und WANN gelöscht wurde; die Daten dabei erneut zu speichern wäre das Gegenteil des Zwecks. */ verlauf("datenschutz", email, "daten geloescht", p.sitzung?.actor || "unbekannt", { geloescht, bereiche: vorschau.loeschbar.map((x) => x.was), aufbewahrt: vorschau.bleibt.length }); return json(res, { ok: true, geloescht, bleibt: vorschau.bleibt, hinweis: vorschau.bleibt.length ? "Gelöscht, soweit zulässig. Der Rest unterliegt einer Aufbewahrungspflicht " + "und wird nur noch aufbewahrt, nicht mehr verwendet (Art. 18 DSGVO)." : "Alle Daten zu dieser Adresse wurden gelöscht.", }); }