feat: Spamschutz für Kontakt-Widget und Registrierung mit Cloudflare Turnstile

- Turnstile für Gäste und unbestätigte Konten (TurnstileVerifier,
  <x-turnstile>); ist Cloudflare nicht erreichbar, wird durchgelassen und
  im Verlauf, in der Kontakt-Queue und in der Betriebsüberwachung markiert
- Inhaltsfilter des Kontaktformulars (fremde Links, Floskel mit
  Seitentitel, Zeichensalat, Sperrliste, Absenden unter 3 Sekunden);
  Treffer landen in der Spam-Quarantäne ohne Mail, Löschung nach 30 Tagen
- Admin: Filter „Spam“, Sammelaktion, „Kein Spam“ holt die Mail nach
- Registrierung lehnt Zufallsnamen ab; users:prune-bot-registrations
  (täglicher Lauf erst mit USERS_PRUNE_BOT_REGISTRATIONS=true)
- contact-messages:quarantine-spam für den Altbestand
- CSP erlaubt challenges.cloudflare.com, Log-Kanal spam
- Datenschutzerklärung: Abschnitt 6a Cloudflare Turnstile (Vorschlag)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-10-09 08:49:58 +00:00
parent 43420f0422
commit beae7e2b13
40 changed files with 1992 additions and 40 deletions

View file

@ -64,4 +64,19 @@ return [
'portal_enabled' => env('UMAMI_PORTAL_ENABLED', false),
'identify_users' => env('UMAMI_IDENTIFY_USERS', false),
],
/*
| Cloudflare Turnstile (Spamschutz, dev/sicherheit/ENTWICKLUNGSPLAN-
| SPAMSCHUTZ-2026-10-09.md): Bot-Prüfung für Kontakt-Widget und
| Registrierung. Ohne beide Keys ist die Prüfung aus (Tests, lokal ohne
| Keys). Lokal die Cloudflare-Testkeys verwenden — die echten Keys sind an
| die Live-Hostnamen gebunden. `fail_open`: Ist Cloudflare nicht
| erreichbar, wird durchgelassen und markiert (Entscheidung 09.10.2026).
*/
'turnstile' => [
'site_key' => env('TURNSTILE_SITE_KEY'),
'secret_key' => env('TURNSTILE_SECRET_KEY'),
'timeout' => (int) env('TURNSTILE_TIMEOUT', 3),
'fail_open' => (bool) env('TURNSTILE_FAIL_OPEN', true),
],
];