From c03793b25f52eb8330a7b6a9562bd6e93a8b9524 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Wed, 26 Aug 2026 20:03:35 +0200 Subject: [PATCH] Oberflaeche fuer Auskunft und Loeschung Sitzt im Kunden-Bereich, zugeklappt. Ein eigener Reiter waere zu prominent fuer etwas, das man vielleicht zweimal im Jahr braucht -- ganz weglassen hiesse, im Ernstfall von Hand durch ein Dutzend Tabellen zu suchen, bei laufender Monatsfrist. DREI SCHRITTE, IN DIESER REIHENFOLGE Auskunft erstellen unschaedlich, jederzeit Loeschvorschau zeigt, was betroffen waere Loeschen nur nach Vorschau, mit doppelter Eingabe Die Auskunft laesst sich als Datei herunterladen, nicht nur ansehen. Man muss sie der Person schicken koennen, und zwar vollstaendig -- abtippen waere der sicherste Weg, etwas zu vergessen. JSON, weil Art. 20 DSGVO ein "gaengiges, maschinenlesbares Format" verlangt. Die Vorschau zeigt beides getrennt: was geloescht wuerde, und was bleiben muss -- mit Grund und mit dem Datum, ab dem es weg darf. Ohne diese Angabe muesste man bei jeder Anfrage neu nachschlagen, welche Frist gilt. Vor dem Loeschen wird die Adresse ein zweites Mal verlangt. Der Unterschied zwischen .de und .com ist ein Buchstabe, die Folge unwiederbringlich. GEPRUEFT Im Handy-Format mit nachgebildeten Antworten: Auskunft zeigt die Bereiche samt Kennzeichnung "aufbewahrungspflichtig", der Herunterladen-Knopf erscheint, die Vorschau trennt richtig, eine abweichende Bestaetigung wird abgelehnt. Kein Ueberlauf, keine Skriptfehler. Die Handy-Suite bleibt bei 25/25, keine Namenskollision. Ein Fehler lag dabei im Test selbst: Playwright prueft die ZULETZT registrierte Route zuerst, und meine allgemeine Auffangregel ueberdeckte die spezifischen. Die Auskunft meldete "nichts gespeichert", obwohl die Oberflaeche richtig arbeitete. Die Endpunkte dazu liegen in server-internal und brauchen noch einen Deploy durch Filipe (/home/dogiintern, Rechte 700). Bis dahin meldet die Oberflaeche einen 404 -- mit dem Hinweis, dass der Server einen Neustart mit dem neuen Stand braucht, statt nur "Fehler" zu sagen. Co-Authored-By: Claude Opus 5 --- assets/js/wd-core.js | 2 +- webdesign/sw.js | 2 +- webdesign/verwaltung.html | 193 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+), 2 deletions(-) diff --git a/assets/js/wd-core.js b/assets/js/wd-core.js index d720d54f..8338d20b 100644 --- a/assets/js/wd-core.js +++ b/assets/js/wd-core.js @@ -863,7 +863,7 @@ Stand haben. Die Nummer wird zusammen mit CACHE_NAME in sw.js hochgezaehlt -- beide gehoeren zusammen. */ navigator.serviceWorker - .register("/webdesign/sw.js?v=60", { scope: "/webdesign/" }) + .register("/webdesign/sw.js?v=61", { scope: "/webdesign/" }) .catch(function () { /* still schlucken: ohne SW funktioniert alles weiter */ }); }); } diff --git a/webdesign/sw.js b/webdesign/sw.js index c6b943c5..24c12d7a 100644 --- a/webdesign/sw.js +++ b/webdesign/sw.js @@ -34,7 +34,7 @@ Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten Stand fest. */ -const CACHE_NAME = "dogfather-webdesign-v60"; +const CACHE_NAME = "dogfather-webdesign-v61"; /* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE HTML-Datei in dieser Liste. */ diff --git a/webdesign/verwaltung.html b/webdesign/verwaltung.html index 26fbe001..c65bdbf5 100644 --- a/webdesign/verwaltung.html +++ b/webdesign/verwaltung.html @@ -2275,6 +2275,43 @@

Bestehende Zugänge

Wird geladen…
+ + +
+ + Auskunft und Löschung (DSGVO) + + +

+ Für Anfragen nach Art. 15 („Welche Daten haben Sie über mich?“) + und Art. 17 („Bitte löschen Sie meine Daten“). Beides ist + binnen eines Monats zu beantworten. +

+ +
+ + +
+ +
+ + +
+ +
+
@@ -2966,6 +3003,162 @@ return browser + (system ? " auf " + system : ""); } + /* ===================================================================== + AUSKUNFT UND LÖSCHUNG (DSGVO Art. 15/17) + + Drei Schritte, und die Reihenfolge ist Absicht: sehen, Vorschau, + erst dann ausführen. Eine Löschung ist endgültig — sie gehört nicht + hinter einen Knopf, der sofort zuschlägt. + + Die Auskunft wird als Datei zum Herunterladen angeboten, nicht nur + angezeigt. Man muss sie der Person schicken können, und zwar + vollständig; abtippen wäre der sicherste Weg, etwas zu vergessen. + ===================================================================== */ + function dsMail() { + var f = document.getElementById("vw-ds-mail"); + return f ? f.value.trim() : ""; + } + + function dsMeldung(html, art) { + var z = document.getElementById("vw-ds-ergebnis"); + if (!z) return; + var farbe = art === "warn" ? "var(--wd-lila-hell)" + : art === "gut" ? "var(--wd-aqua)" : "var(--wd-rand)"; + z.innerHTML = '
' + html + '
'; + } + + async function dsAuskunft() { + var mail = dsMail(); + if (!mail) return dsMeldung("Bitte zuerst eine E-Mail-Adresse eintragen.", "warn"); + dsMeldung("Wird zusammengetragen…"); + + var d; + try { + d = await api("/webdesign/admin/datenschutz/auskunft", { method: "POST", body: { email: mail } }); + } catch (fehler) { + return dsMeldung("Nicht möglich: " + s((fehler && fehler.message) || "unbekannter Fehler") + + (fehler && fehler.status === 404 + ? '
Der Server kennt diesen Zugang noch nicht — er braucht einen Neustart mit dem neuen Stand.' + : ""), "warn"); + } + if (!d || !d.ok) return dsMeldung("Nicht möglich: " + s((d && d.error) || "unbekannt"), "warn"); + + if (!d.datensaetze) { + return dsMeldung("Zu " + s(mail) + " ist nichts gespeichert.", "gut"); + } + + var liste = (d.bereiche || []).map(function (b) { + return "
  • " + s(String(b.anzahl)) + "× " + s(b.was) + + (b.aufbewahren ? ' (aufbewahrungspflichtig)' : "") + + "
  • "; + }).join(""); + + dsMeldung( + "" + s(String(d.datensaetze)) + " Datensätze zu " + s(mail) + + '" + + '
    ' + + '
    ", "gut"); + + var knopf = document.getElementById("vw-ds-laden"); + if (knopf) { + knopf.addEventListener("click", function () { + /* Die vollständige Auskunft, wie sie herausgegeben wird. JSON, + weil Art. 20 ein "gängiges, maschinenlesbares Format" + verlangt — und weil nichts verloren geht. */ + var blob = new Blob([JSON.stringify(d, null, 2)], { type: "application/json" }); + var url = URL.createObjectURL(blob); + var a = document.createElement("a"); + a.href = url; + a.download = "auskunft-" + mail.replace(/[^a-z0-9]/gi, "-") + "-" + + new Date().toISOString().slice(0, 10) + ".json"; + a.click(); + URL.revokeObjectURL(url); + }); + } + } + + async function dsVorschau() { + var mail = dsMail(); + if (!mail) return dsMeldung("Bitte zuerst eine E-Mail-Adresse eintragen.", "warn"); + dsMeldung("Wird geprüft…"); + + var d; + try { + d = await api("/webdesign/admin/datenschutz/vorschau", { method: "POST", body: { email: mail } }); + } catch (fehler) { + return dsMeldung("Nicht möglich: " + s((fehler && fehler.message) || "unbekannt"), "warn"); + } + if (!d || !d.ok) return dsMeldung("Nicht möglich: " + s((d && d.error) || "unbekannt"), "warn"); + + var weg = (d.loeschbar || []).map(function (x) { + return "
  • " + s(String(x.anzahl)) + "× " + s(x.was) + "
  • "; + }).join("") || '
  • nichts
  • '; + + /* Der wichtigere Teil: was NICHT gelöscht werden darf, mit Grund + und Datum. Ohne diese Angabe müsste man bei jeder Anfrage neu + nachschlagen, welche Frist gilt. */ + var bleibt = (d.bleibt || []).map(function (x) { + return "
  • " + s(String(x.anzahl)) + "× " + s(x.was) + + '
    ' + s(x.grund) + + " · frei ab " + s(x.freiAb) + "
  • "; + }).join(""); + + dsMeldung( + "Würde gelöscht" + + '" + + (bleibt + ? "Bleibt (gesetzliche Aufbewahrung)" + + '" + : "") + + '

    ' + s(d.hinweis || "") + "

    " + + ((d.loeschbar || []).length + ? '
    ' + + '' + + '' + + "
    " + + '
    ' + + '
    " + : ""), "warn"); + + var lk = document.getElementById("vw-ds-loeschen"); + if (lk) lk.addEventListener("click", dsLoeschen); + } + + async function dsLoeschen() { + var mail = dsMail(); + var best = document.getElementById("vw-ds-best"); + var bestaetigung = best ? best.value.trim() : ""; + + if (mail.toLowerCase() !== bestaetigung.toLowerCase()) { + return dsMeldung("Die beiden Adressen stimmen nicht überein. " + + "Zum Schutz vor einem Vertipper wird sie ein zweites Mal verlangt — " + + "der Unterschied zwischen .de und .com ist ein Buchstabe, die Folge unwiederbringlich.", "warn"); + } + + dsMeldung("Wird gelöscht…"); + var d; + try { + d = await api("/webdesign/admin/datenschutz/loeschen", + { method: "POST", body: { email: mail, bestaetigung: bestaetigung } }); + } catch (fehler) { + return dsMeldung("Nicht möglich: " + s((fehler && fehler.message) || "unbekannt"), "warn"); + } + if (!d || !d.ok) return dsMeldung("Nicht möglich: " + s((d && d.error) || "unbekannt"), "warn"); + + dsMeldung("" + s(String(d.geloescht)) + " Datensätze gelöscht." + + '

    ' + s(d.hinweis || "") + "

    ", "gut"); + } + + (function dsVerdrahten() { + var a = document.getElementById("vw-ds-auskunft"); + if (a) a.addEventListener("click", dsAuskunft); + var v = document.getElementById("vw-ds-vorschau"); + if (v) v.addEventListener("click", dsVorschau); + })(); + async function ladeUebersicht() { var laedt = document.getElementById("vw-ub-laedt"); var inhalt = document.getElementById("vw-ub-inhalt");