Formulare mit Validierung, Einwilligung und Spamschutz (Arbeitspaket D)
Migration 20260912000700 legt form und form_submission an. Der Formularblock im Seiteninhalt enthält nur die Adresse: Felder, Empfänger und Einwilligungstext liegen in der Datenbank, damit ein gespeichertes Dokument nicht ändern kann, wonach gefragt wird oder wohin eine Anfrage geht. Serverseitige Validierung mit einer Meldung je Feld über aria-describedby und aria-invalid; eine Ablehnung rendert die Seite mit HTTP 422 und allen Eingaben neu, eine gültige Absendung wird mit HTTP 303 zurückgeleitet. Alles ohne JavaScript. Spamschutz ohne Dienst Dritter: Honeypot, signierter Zeitstempel gegen zu schnelle und zu alte Absendungen, fünf Absendungen je Absender und Stunde. Ein erkannter Bot bekommt dieselbe Antwort wie ein Erfolg. Die Absenderadresse wird nie gespeichert, nur ein Hash. Gespeichert wird vor dem Versand, damit ein kaputter Mailversand keine Anfrage verliert. Kein CSRF-Token auf dem öffentlichen Weg, weil das eine Sitzung und damit ein Cookie auf jeder öffentlichen Seite bedeuten würde; Begründung in ADR 017. Dateiuploads fehlen bewusst. Ein echter Fehler von den Tests gefunden: das Stundenfenster des Limits entstand aus lokaler Zeit, die Zeitstempel aber aus UTC - auf einer Uhr vor UTC hätte das Limit nie gegriffen. Geprüft: 117 PHP-Tests / 450 Assertions unter PHP 8.2.12 und 8.4.22, 14 JavaScript-Modelltests, Lint/Format/Container, composer audit. Der E-Mail-Versand wurde nie wirklich ausgeführt; keine Browser- oder Lighthouse-Läufe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c6ec00a906
commit
d43bd91069
31 files changed
+1445
-26
No files matched your search
@@ -192,3 +192,21 @@ ADR 015 hatte Tabs zurückgestellt, weil es ohne JavaScript keine zugängliche L
|
||||
Die Editorvorschau führt kein JavaScript aus (siehe QUALITY). Tabs erscheinen dort deshalb als untereinanderliegende Abschnitte. Das steht so im Eigenschaftenpanel, damit der Unterschied zur veröffentlichten Seite nicht als Fehler missverstanden wird.
|
||||
|
||||
Ein neuer Tab-Block bringt zwei Tabs mit: ein einzelner Tab ist keine Tabgruppe, und das Skript verbessert erst ab zwei Einträgen. Ein Tab außerhalb einer Tabgruppe und eine Tabgruppe mit anderem Inhalt werden abgewiesen, auch auf oberster Dokumentebene.
|
||||
|
||||
## ADR 017 – Formulare (Dokumentschema v8, 12.09.2026)
|
||||
|
||||
Migration `20260912000700` legt `form` und `form_submission` an. **Ein Formularblock im Seiteninhalt enthält nur die Adresse des Formulars.** Felder, Empfänger, Betreff, Antwortadresse und Einwilligungstext liegen in der Datenbank. Ein gespeichertes Dokument kann dadurch weder ändern, wonach gefragt wird, noch wohin eine Anfrage geht – dieselbe Trennung wie beim Block `postlist`.
|
||||
|
||||
**Kein CSRF-Token und keine Sitzung auf dem öffentlichen Weg.** Symfonys CSRF-Schutz braucht eine Sitzung, und eine Sitzung bedeutet ein Cookie auf jeder öffentlichen Seite; das vermeidet das Projekt bewusst. Die Route ist anonym und ändert nichts, was einem Besucher gehört: eine gefälschte Anfrage könnte höchstens eine Nachricht senden, und genau dagegen wirkt der Spamschutz. Die Verwaltung der Formulardefinitionen bleibt dagegen sitzungsgebunden und CSRF-geschützt.
|
||||
|
||||
**Spamschutz ohne Dienst Dritter**, in drei Stufen: ein Honeypot-Feld außerhalb von Sichtbarkeit, Tabreihenfolge und Hilfstechnik; ein **signierter Zeitstempel** im Formular, der zu schnelle und zu alte Absendungen erkennt, ohne dafür etwas zu speichern; und ein Limit von fünf Absendungen je Absender und Stunde. Ein erkannter Bot bekommt dieselbe Antwort wie ein Erfolg, damit er nichts über die Prüfung lernt – gespeichert und gesendet wird nichts.
|
||||
|
||||
**Die Absenderadresse wird nie gespeichert**, nur ein mit dem Anwendungsgeheimnis gebildeter Hash. Das reicht für das Limit und erlaubt keinen Rückschluss auf die Herkunft. Der Zeitpunkt der Einwilligung wird getrennt festgehalten.
|
||||
|
||||
Ablauf ohne JavaScript: eine fehlerhafte Absendung rendert dieselbe Seite erneut mit HTTP 422, allen Eingaben und einer Meldung je Feld, verbunden über `aria-describedby` und `aria-invalid`; eine gültige Absendung wird gespeichert, versendet und mit HTTP 303 auf die Seite zurückgeleitet, sodass ein Neuladen nichts doppelt sendet.
|
||||
|
||||
**Gespeichert wird vor dem Versand.** Ein kaputter Mailversand verliert damit keine Anfrage: die Einsendung bleibt in der Verwaltung sichtbar und als nicht versendet gekennzeichnet. Als Transport ist `sendmail` voreingestellt, weil das gewöhnliches PHP-Hosting anbietet; per `MAILER_DSN` lässt sich SMTP einstellen.
|
||||
|
||||
**Dateiuploads sind nicht enthalten.** Anhänge brauchen einen eigenen, nicht öffentlichen Ablageort mit Zugriffsschutz, eine Typprüfung und eine Entscheidung über den Versand als Anhang – eine eigene Sicherheitsfläche, die nicht nebenbei entsteht. Das Formular des Piloten hat kein Dateifeld. Der Punkt bleibt im Ausbauplan offen.
|
||||
|
||||
Beim Schreiben der Tests fiel ein echter Fehler auf: das Stundenfenster des Limits wurde mit der lokalen Uhrzeit gebildet, die Zeitstempel der Einsendungen aber in UTC. Bei einer Uhr vor UTC hätte das Limit nie gegriffen. Beide Seiten rechnen jetzt in UTC.
|
||||
Reference in new issue
Block a user