Kontaktseite mit Formular ohne Tracking (MAIN-15) #6

Merged
Owner

Kontaktseite /kontakt mit eigenem Formular, ohne Drittdienste und ohne Tracking. Löst MAIN-15.

Was ist drin

  • Seite /kontakt (vorgerendert): Formular mit UiWindow, UiField und UiButton aus dem Design-System, dazu direkte Kontaktdaten und das Antwortversprechen «meistens innerhalb von 24 Stunden» (aus dem Inventar MAIN-5).
  • POST /api/contact: eigener Nitro-Endpoint. Der Server prüft alle Felder und versendet per SMTP (nodemailer). Reply-To ist die anfragende Person. Anfragen werden nicht gespeichert, Logs enthalten keine Formularinhalte.
  • Spam-Schutz: ein Honeypot-Feld, eine signierte Zeitprüfung (GET /api/contact/token, mindestens 3 s, höchstens 2 h) und ein Rate-Limit von 5 Anfragen pro IP in 10 Minuten. Bots bekommen beim Honeypot und bei zu schnellem Absenden ein stilles «ok». Es gibt kein Captcha.
  • Fehlermeldungen: auf Deutsch, direkt am Feld (aria-invalid und aria-describedby) und als Zusammenfassung mit role="alert". Nach einem Fehler springt der Fokus ins erste ungültige Feld. Browser und Server nutzen dieselbe Prüflogik. Bei 429, 502 und 503 nennt die Meldung die E-Mail-Adresse als Ausweichweg. Fehlercodes stehen in docs/kontaktformular.md.
  • Tests: Vitest ist neu. npm test prüft Validierung, Token, Rate-Limit und Mail-Aufbau und versendet per SMTP an einen Test-Empfänger. npm run test:e2e startet den gebauten Server und prüft: Die Testanfrage kommt an, Honeypot und Zeitprüfung greifen, Fehlermeldungen kommen an, das Rate-Limit wirkt, und /kontakt enthält keine externen URLs.

⚠️ Deployment-Änderung

Bisher war die Seite rein statisch (nuxt generate). Das Formular braucht jetzt einen Node-Prozess. Alle Seiten bleiben vorgerendert, nur /api/contact* läuft im Server.

  • Build: npm run build, Start: node .output/server/index.mjs (Node ≥ 20.19)
  • Die CI baut jetzt mit nuxt build statt nuxt generate und lädt .output als Artefakt hoch.
  • npm run generate gibt es noch. Damit funktioniert das Formular aber nicht (es gibt dann keinen Endpoint). Für MAIN-18 (Deployment) wichtig.

Konfiguration (neu)

Umgebungsvariablen NUXT_CONTACT_*, siehe .env.example. Pflicht sind SMTP-Host, TO und FROM. Für Produktion NUXT_CONTACT_SECRET setzen. NUXT_CONTACT_TRUST_PROXY=true nur hinter einem Reverse-Proxy. Ohne SMTP-Konfiguration antwortet der Endpoint mit 503 und nennt die E-Mail-Adresse.

Testen

npm ci
npm run lint && npm run typecheck && npm test
npm run build && npm run test:e2e

Manuell: .env aus .env.example mit einem echten SMTP-Zugang anlegen, npm run dev, dann http://localhost:3000/kontakt öffnen.

Lokal geprüft: Lint, Typecheck, 23 Unit-/Integrationstests und 5 E2E-Tests sind grün. Im Client-Bundle ist kein Server-Code (nodemailer und crypto).

Grenzen

  • Nicht visuell geprüft: Auf der Maschine läuft kein Browser, weil Systembibliotheken fehlen. Bitte /kontakt bei Handy- und Desktop-Breite, im Dunkelmodus und in diesen Zuständen ansehen: leer, Fehler, «Wird gesendet …», Erfolg.
  • Das Rate-Limit liegt im Arbeitsspeicher. Es gilt nur pro Instanz und wird bei einem Neustart zurückgesetzt.
  • Ohne JavaScript erscheint ein Hinweis mit der E-Mail-Adresse statt des Formulars.
  • Die Texte sind ein Entwurf in der Sie-Form (wie im Design-System). Den finalen Wortlaut legt MAIN-7 fest.
  • Navigation und Footer fehlen noch (MAIN-10). Ein Link auf die Datenschutzseite kommt mit MAIN-16.
  • Basis: Der PR baut auf #3 (Design-System) auf, der wiederum auf #1 aufbaut. Bitte in dieser Reihenfolge mergen und danach die Basis umstellen.

🤖 Generated with Claude Code

Kontaktseite `/kontakt` mit eigenem Formular, ohne Drittdienste und ohne Tracking. Löst MAIN-15. ## Was ist drin - **Seite `/kontakt`** (vorgerendert): Formular mit `UiWindow`, `UiField` und `UiButton` aus dem Design-System, dazu direkte Kontaktdaten und das Antwortversprechen «meistens innerhalb von 24 Stunden» (aus dem Inventar MAIN-5). - **`POST /api/contact`**: eigener Nitro-Endpoint. Der Server prüft alle Felder und versendet per SMTP (`nodemailer`). Reply-To ist die anfragende Person. Anfragen werden nicht gespeichert, Logs enthalten keine Formularinhalte. - **Spam-Schutz**: ein Honeypot-Feld, eine signierte Zeitprüfung (`GET /api/contact/token`, mindestens 3 s, höchstens 2 h) und ein Rate-Limit von 5 Anfragen pro IP in 10 Minuten. Bots bekommen beim Honeypot und bei zu schnellem Absenden ein stilles «ok». Es gibt kein Captcha. - **Fehlermeldungen**: auf Deutsch, direkt am Feld (`aria-invalid` und `aria-describedby`) und als Zusammenfassung mit `role="alert"`. Nach einem Fehler springt der Fokus ins erste ungültige Feld. Browser und Server nutzen dieselbe Prüflogik. Bei 429, 502 und 503 nennt die Meldung die E-Mail-Adresse als Ausweichweg. Fehlercodes stehen in `docs/kontaktformular.md`. - **Tests**: Vitest ist neu. `npm test` prüft Validierung, Token, Rate-Limit und Mail-Aufbau und versendet per SMTP an einen Test-Empfänger. `npm run test:e2e` startet den gebauten Server und prüft: Die Testanfrage kommt an, Honeypot und Zeitprüfung greifen, Fehlermeldungen kommen an, das Rate-Limit wirkt, und `/kontakt` enthält keine externen URLs. ## ⚠️ Deployment-Änderung Bisher war die Seite rein statisch (`nuxt generate`). Das Formular braucht jetzt einen Node-Prozess. Alle Seiten bleiben vorgerendert, nur `/api/contact*` läuft im Server. - Build: `npm run build`, Start: `node .output/server/index.mjs` (Node ≥ 20.19) - Die CI baut jetzt mit `nuxt build` statt `nuxt generate` und lädt `.output` als Artefakt hoch. - `npm run generate` gibt es noch. Damit funktioniert das Formular aber nicht (es gibt dann keinen Endpoint). Für MAIN-18 (Deployment) wichtig. ## Konfiguration (neu) Umgebungsvariablen `NUXT_CONTACT_*`, siehe `.env.example`. Pflicht sind SMTP-Host, `TO` und `FROM`. Für Produktion `NUXT_CONTACT_SECRET` setzen. `NUXT_CONTACT_TRUST_PROXY=true` nur hinter einem Reverse-Proxy. Ohne SMTP-Konfiguration antwortet der Endpoint mit 503 und nennt die E-Mail-Adresse. ## Testen ```bash npm ci npm run lint && npm run typecheck && npm test npm run build && npm run test:e2e ``` Manuell: `.env` aus `.env.example` mit einem echten SMTP-Zugang anlegen, `npm run dev`, dann http://localhost:3000/kontakt öffnen. Lokal geprüft: Lint, Typecheck, 23 Unit-/Integrationstests und 5 E2E-Tests sind grün. Im Client-Bundle ist kein Server-Code (nodemailer und crypto). ## Grenzen - **Nicht visuell geprüft:** Auf der Maschine läuft kein Browser, weil Systembibliotheken fehlen. Bitte `/kontakt` bei Handy- und Desktop-Breite, im Dunkelmodus und in diesen Zuständen ansehen: leer, Fehler, «Wird gesendet …», Erfolg. - Das Rate-Limit liegt im Arbeitsspeicher. Es gilt nur pro Instanz und wird bei einem Neustart zurückgesetzt. - Ohne JavaScript erscheint ein Hinweis mit der E-Mail-Adresse statt des Formulars. - Die Texte sind ein Entwurf in der Sie-Form (wie im Design-System). Den finalen Wortlaut legt MAIN-7 fest. - Navigation und Footer fehlen noch (MAIN-10). Ein Link auf die Datenschutzseite kommt mit MAIN-16. - **Basis:** Der PR baut auf #3 (Design-System) auf, der wiederum auf #1 aufbaut. Bitte in dieser Reihenfolge mergen und danach die Basis umstellen. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: Kontaktseite mit Formular ohne Tracking (MAIN-15)
Some checks are pending
CI / build (push) Waiting to run
CI / build (pull_request) Waiting to run
51b0b059fe
- /kontakt (vorgerendert) mit Formular aus dem Design-System
- POST /api/contact: serverseitige Validierung, Versand per SMTP (nodemailer)
- Spam-Schutz: Honeypot, signierte Zeitprüfung, Rate-Limit pro IP
- Verständliche Fehlermeldungen auf Deutsch, am Feld und als Zusammenfassung
- Vitest: Unit-/Integrationstests und E2E gegen den Build
- CI: Tests und nuxt build statt nuxt generate

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
rainer merged commit 701af92040 into agent/full-stack-web-engineer/5f28348cf2ed 2026-11-03 18:14:40 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
rainer/test!6
No description provided.