Shuffle/docs/auth-konfiguration.md
cschlaefke fb0f4b9d3f
All checks were successful
CI / flutter (pull_request) Successful in 1m3s
CI / web-build (pull_request) Successful in 58s
CI / supabase (pull_request) Successful in 3s
CI / supabase-tests (pull_request) Successful in 34s
fix(db): trim all Unicode whitespace in display name, harden templates
- add public.trim_ws mirroring Dart String.trim
- require JSON string metadata, tolerate non-string locale in templates
- add tests, align docs with review

Refs #1

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 23:48:01 +01:00

178 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Auth-Konfiguration (Supabase Auth / GoTrue)
| | |
|---|---|
| Gilt für | KTO-01 (Issue #1) und alle folgenden Auth-Stories |
| Stand | 2026-11-02 |
| Versionen (verifiziert) | Supabase CLI 3.0.0, GoTrue `supabase/gotrue:v2.197.0`, Postgres 17, Mailpit v1.31.3 |
| Quellen | `docs/specs/1-auth-registrierung-anmeldung.md` (§ 4.1, 4.3), `docs/specs/1-auth-spike-ergebnisse.md` (Nachweis, bleibt unverändert liegen) |
| Pflege | Jede Änderung an `supabase/config.toml` (Abschnitt `[auth*]`) und am Env-Block unten gemeinsam ändern. Tests referenzieren die IDs `AUTH-CFG-nn`. |
Zweck: Die Auth-Einstellungen sind explizit festgelegt, weil sich die Standardwerte je nach Quelle unterscheiden (GoTrue-Binary, CLI-Vorlage, Docker-Setup; Klärung 18). Lokal und in der CI gilt `supabase/config.toml`. Staging und Produktion (self-hosted, ADR-0002) setzen dieselben Werte per Umgebungsvariablen.
## 1. Einstellungen (AUTH-CFG)
Spalte „Wirkung“: **lokal** = wirkt in der CLI über `config.toml`; **Env** = wirkt nur im Self-Hosting über Umgebungsvariablen (in `config.toml` nur als Kommentar bzw. Zielwert); **Gateway** = nur am Proxy/Gateway lösbar.
| ID | Einstellung | Wert | Wirkung | `config.toml` / Env | Begründung | Krit. |
|---|---|---|---|---|---|---|
| AUTH-CFG-01 | Site-URL | lokal `http://localhost:3000`; Prod `https://shuffle.cshomelabs.work` (Platzhalter); Staging legt devops fest | lokal + Env | `auth.site_url` / `GOTRUE_SITE_URL` | Basis der Mail-Links (`{{ .SiteURL }}`) | 1, 8 |
| AUTH-CFG-02 | Redirect-Allow-List | nur `<Site-URL>/auth/confirm` | lokal + Env | `auth.additional_redirect_urls` / `GOTRUE_URI_ALLOW_LIST` | Nur eigene Web-URLs erlaubt | 1, 8 |
| AUTH-CFG-03 | Registrierung erlaubt | an | lokal + Env | `auth.enable_signup`, `auth.email.enabled` / `GOTRUE_DISABLE_SIGNUP=false`, `GOTRUE_EXTERNAL_EMAIL_ENABLED=true` | Selbstregistrierung ist der Kern von KTO-01 | 1 |
| AUTH-CFG-04 | E-Mail-Bestätigung Pflicht | an | lokal + Env | `auth.email.enable_confirmations = true` / `GOTRUE_MAILER_AUTOCONFIRM=false` | CLI-Standard ist `false`. Voraussetzung für den Enumerationsschutz, zusammen mit AUTH-CFG-24 | 1, 4, 6 |
| AUTH-CFG-05 | Passwort-Mindestlänge | 10 | lokal + Env | `auth.minimum_password_length` / `GOTRUE_PASSWORD_MIN_LENGTH` | Entscheidung O10; Standard wäre 6 | 3 |
| AUTH-CFG-06 | Zeichenklassen | keine | lokal + Env | `auth.password_requirements = ""` / `GOTRUE_PASSWORD_REQUIRED_CHARACTERS` leer | Nicht gefordert | 3 |
| AUTH-CFG-07 | Gültigkeit Bestätigungs- und Reset-Link | 3600 s (1 h) | lokal + Env | `auth.email.otp_expiry` / `GOTRUE_MAILER_OTP_EXP=3600` | Entscheidung F3. Das Self-hosted-Binary hätte 86400, deshalb im Env explizit setzen | 1, 8 |
| AUTH-CFG-08 | Mindestabstand zwischen Mails je Nutzer | lokal/CI 1 s; Staging/Prod 60 s | lokal + Env | `auth.email.max_frequency` / `GOTRUE_SMTP_MAX_FREQUENCY=60s` | Lokal bewusst 1 s, sonst scheitert der Resend-Test direkt nach dem Signup an 429. Die 60 s werden auf Staging geprüft (Checkliste) | 6, 8 |
| AUTH-CFG-09 | Mails pro Stunde, **instanzweit** | lokal/CI-Zielwert 100; Staging/Prod **30** | **nur Env** | `auth.rate_limit.email_sent` (lokal ohne Wirkung) / `GOTRUE_RATE_LIMIT_EMAIL_SENT=30` | Entscheidung F4. Die CLI setzt intern `360000` (Spike A3). Risiko R3 | 1, 6, 8 |
| AUTH-CFG-10 | Drosselung Passwort-Login und Token-Refresh | 150 je 5 min je IP, Burst 30 | **nur Env, nur mit AUTH-CFG-13** | `auth.rate_limit.token_refresh` / `GOTRUE_RATE_LIMIT_TOKEN_REFRESH=150` | Login Nr. 31 einer IP → 429 `over_request_rate_limit`. Keine Sperre je Konto, nur je IP. 429 ohne `Retry-After` | 7 |
| AUTH-CFG-11 | Drosselung Registrierung, Reset, erneut senden | 30 je 5 min je IP, Burst 30 | **nur Env, nur mit AUTH-CFG-13** | `auth.rate_limit.sign_in_sign_ups` / `GOTRUE_RATE_LIMIT_OTP=30` | Für Signup getestet; dass Recover und Resend denselben Limiter nutzen, ist nur aus dem Code abgeleitet | 1, 6, 8 |
| AUTH-CFG-12 | Drosselung Link-Prüfung | 30 je 5 min je IP | **nur Env, nur mit AUTH-CFG-13** | `auth.rate_limit.token_verifications` / `GOTRUE_RATE_LIMIT_VERIFY=30` | – | 8 |
| AUTH-CFG-13 | Client-IP hinter Proxy | `GOTRUE_RATE_LIMIT_HEADER=X-Forwarded-For` | **nur Env (Pflicht Staging/Prod)** | – / `GOTRUE_RATE_LIMIT_HEADER` | Ohne diese Einstellung gibt es **keine** IP-Drosselung; Requests ohne den Header werden auch mit ihr nicht gedrosselt. Der Proxy muss den Header überschreiben | 7 |
| AUTH-CFG-14 | JWT-Laufzeit | 3600 s | lokal + Env | `auth.jwt_expiry` / `GOTRUE_JWT_EXP=3600` | Entscheidung F1. Andere Geräte sind nach einem Passwort-Reset spätestens nach 60 min abgemeldet (Restrisiko R8) | 5, 9 |
| AUTH-CFG-15 | Refresh-Token-Rotation | an, Wiederverwendungsfenster 10 s | lokal + Env | `auth.enable_refresh_token_rotation`, `auth.refresh_token_reuse_interval` / `GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=true`, `GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=10` | Standard | 5, 9 |
| AUTH-CFG-16 | Sitzungsbegrenzung | **nicht gesetzt** | lokal + Env | `auth.sessions.*` (auskommentiert) / `GOTRUE_SESSIONS_TIMEBOX`, `GOTRUE_SESSIONS_INACTIVITY_TIMEOUT` ungesetzt | Angemeldet bis zum Abmelden | 5 |
| AUTH-CFG-17 | Mailvorlage Bestätigung | `supabase/templates/confirmation.html`, Betreff de/en | lokal + Env | `auth.email.template.confirmation` / `GOTRUE_MAILER_TEMPLATES_CONFIRMATION` (URL), `GOTRUE_MAILER_SUBJECTS_CONFIRMATION` | Sprache aus `user_metadata.locale`, sonst Englisch. Self-hosted lädt per URL (siehe 3) | 1, 11 |
| AUTH-CFG-18 | Mailvorlage Reset | `supabase/templates/recovery.html`, Betreff de/en | lokal + Env | `auth.email.template.recovery` / `GOTRUE_MAILER_TEMPLATES_RECOVERY`, `GOTRUE_MAILER_SUBJECTS_RECOVERY` | wie oben | 8, 11 |
| AUTH-CFG-19 | Benachrichtigungs-Mails | alle aus | lokal + Env | `auth.email.notification.<typ>.enabled = false` / `GOTRUE_MAILER_NOTIFICATIONS_<TYP>_ENABLED=false` | Nur Bestätigung und Reset (Krit. 11). `email_changed` schaltet KTO-11 ein (N2) | 11 |
| AUTH-CFG-20 | Magic Link / OTP-Login | `POST /auth/v1/otp` und `/auth/v1/magiclink` am Gateway sperren | **Gateway** | – (kein Schalter in GoTrue) | Sonst Mails außerhalb von Krit. 11 und Konten ohne Passwort (Spike A7). Der Trigger ersetzt die Sperre nicht. Lokal nicht gesperrt | 11, 3 |
| AUTH-CFG-21 | Leaked-Password-Prüfung (HIBP) | aus | **nur Env** | – / `GOTRUE_PASSWORD_HIBP_ENABLED=false` | Entscheidung F2: keine Übermittlung an Dritte | 3 |
| AUTH-CFG-22 | SMTP / Mailfänger | lokal Mailpit; Staging/Prod EU-Anbieter mit AVV (**offen**, F7) | lokal + Env | `local_smtp` / `GOTRUE_SMTP_*` | Release-Blocker, kein Merge-Blocker | 1, 8, 11 |
| AUTH-CFG-23 | E-Mail-Wechsel auf beiden Adressen bestätigen | **in #1 nicht festgelegt** (CLI-Standard `true`) | – | `auth.email.double_confirm_changes` / `GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED` | Entscheidung fällt in KTO-11 (N2) | (KTO-11) |
| AUTH-CFG-24 | SMS-Autoconfirm aus | `SMS_AUTOCONFIRM=false` | lokal + Env | `auth.sms.enable_confirmations = true` / `GOTRUE_SMS_AUTOCONFIRM=false`; in der Self-Hosting-Vorlage `ENABLE_PHONE_AUTOCONFIRM=false` | SMS ist nicht im Einsatz, die Einstellung steuert aber den Enumerationsschutz beim E-Mail-Signup. Mit `true` (CLI-Standard und Vorlage `docker/.env.example`!) liefert Signup mit bestätigter Adresse 422 `user_already_exists` | 4 |
| AUTH-CFG-25 | Passwortänderung ohne erneute Anmeldung | `false` (Standard, ausdrücklich gesetzt) | lokal + Env | `auth.email.secure_password_change` / `GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION` (Env-Name vor Release gegen das Image prüfen) | Recovery-Sitzungen sind ausgenommen; ob das aktuelle Passwort verlangt wird, entscheidet KTO-11 (N13) | (KTO-11) |
## 2. Env-Block für Staging und Produktion (GoTrue, ohne Secrets)
Gilt für das Image `supabase/gotrue:v2.197.0` (pinnen, nicht `latest`). Secrets (JWT-Secret, SMTP-Passwort, DB-URL) stehen nicht hier, sondern im Secret-Speicher des Hosting-Setups.
```env
# AUTH-CFG-01, -02, -03
GOTRUE_SITE_URL=https://shuffle.cshomelabs.work
GOTRUE_URI_ALLOW_LIST=https://shuffle.cshomelabs.work/auth/confirm
GOTRUE_DISABLE_SIGNUP=false
GOTRUE_EXTERNAL_EMAIL_ENABLED=true
# AUTH-CFG-04, -24 (beide Autoconfirm-Schalter müssen false sein; Supabase-Vorlage: ENABLE_PHONE_AUTOCONFIRM=false)
GOTRUE_MAILER_AUTOCONFIRM=false
GOTRUE_SMS_AUTOCONFIRM=false
# AUTH-CFG-05, -06, -21
GOTRUE_PASSWORD_MIN_LENGTH=10
GOTRUE_PASSWORD_REQUIRED_CHARACTERS=
GOTRUE_PASSWORD_HIBP_ENABLED=false
# AUTH-CFG-23 (Stagingwert; in #1 nicht festgelegt, CLI-Standard true, Entscheidung in KTO-11)
GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED=true
# AUTH-CFG-25 (Env-Name vor Release gegen das Image prüfen)
GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION=false
# AUTH-CFG-07, -08
GOTRUE_MAILER_OTP_EXP=3600
GOTRUE_SMTP_MAX_FREQUENCY=60s
# AUTH-CFG-09 bis -13 (GOTRUE_RATE_LIMIT_HEADER ist Pflicht, sonst keine IP-Drosselung)
GOTRUE_RATE_LIMIT_HEADER=X-Forwarded-For
GOTRUE_RATE_LIMIT_EMAIL_SENT=30
GOTRUE_RATE_LIMIT_TOKEN_REFRESH=150
GOTRUE_RATE_LIMIT_OTP=30
GOTRUE_RATE_LIMIT_VERIFY=30
# AUTH-CFG-14, -15 (AUTH-CFG-16: Sitzungs-Variablen NICHT setzen)
GOTRUE_JWT_EXP=3600
GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=true
GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=10
# AUTH-CFG-17, -18 (Betreff je Sprache verzweigt, Text identisch zu config.toml; URLs siehe Abschnitt 3)
GOTRUE_MAILER_TEMPLATES_CONFIRMATION=https://<vorlagen-host>/confirmation.html
GOTRUE_MAILER_TEMPLATES_RECOVERY=https://<vorlagen-host>/recovery.html
GOTRUE_MAILER_SUBJECTS_CONFIRMATION={{ if and .Data (eq (printf "%v" (index .Data "locale")) "de") }}Bestätige deine E-Mail-Adresse für Shuffle{{ else }}Confirm your email address for Shuffle{{ end }}
GOTRUE_MAILER_SUBJECTS_RECOVERY={{ if and .Data (eq (printf "%v" (index .Data "locale")) "de") }}Passwort für Shuffle zurücksetzen{{ else }}Reset your Shuffle password{{ end }}
# AUTH-CFG-19 (Benachrichtigungen aus; KTO-11 schaltet email_changed ein)
GOTRUE_MAILER_NOTIFICATIONS_PASSWORD_CHANGED_ENABLED=false
GOTRUE_MAILER_NOTIFICATIONS_EMAIL_CHANGED_ENABLED=false
GOTRUE_MAILER_NOTIFICATIONS_PHONE_CHANGED_ENABLED=false
GOTRUE_MAILER_NOTIFICATIONS_IDENTITY_LINKED_ENABLED=false
GOTRUE_MAILER_NOTIFICATIONS_IDENTITY_UNLINKED_ENABLED=false
GOTRUE_MAILER_NOTIFICATIONS_MFA_FACTOR_ENROLLED_ENABLED=false
GOTRUE_MAILER_NOTIFICATIONS_MFA_FACTOR_UNENROLLED_ENABLED=false
# AUTH-CFG-22 (Anbieter offen, F7): GOTRUE_SMTP_HOST, _PORT, _USER, _PASS, _ADMIN_EMAIL, _SENDER_NAME
```
Hinweis zu den Benachrichtigungs-Variablen: Namen aus dem GoTrue-Schema für `v2.197.0` abgeleitet; beim ersten Aufsetzen mit einer Testmail (Passwortänderung) prüfen, dass keine Mail ausgeht (Checkliste).
## 3. Gateway- und Dashboard-Einstellungen
- **Proxy vor GoTrue (AUTH-CFG-13):** GoTrue ist nur über den Proxy erreichbar. Der Proxy **überschreibt** `X-Forwarded-For` mit der echten Client-IP (nicht anhängen), sonst fälschen Clients ihre IP und umgehen die Drosselung. Ein direkter Zugriff auf GoTrue umgeht die Drosselung ebenfalls.
- **Magic-Link-Sperre (AUTH-CFG-20):** Das Gateway lehnt `POST /auth/v1/otp` und `POST /auth/v1/magiclink` ab (z. B. 404/403). Beide Endpunkte lassen sich in GoTrue nicht abschalten; `/otp` legt mit gültigen Metadaten ein Konto an.
- **Vorlagen-Hosting (AUTH-CFG-17, -18):** Self-hosted GoTrue lädt Mailvorlagen **per URL**. Die Dateien aus `supabase/templates/` werden unter einer intern erreichbaren URL bereitgestellt (z. B. statisch am Gateway). Ist die URL nicht erreichbar, fällt GoTrue **still** auf die englische Standardvorlage zurück (Risiko R5). Die URL gehört in den Health-Check.
- **SPA-Rewrite:** Die Web-App muss `/auth/confirm` ausliefern (alle Pfade → `index.html`), sonst laufen die Mail-Links ins Leere.
- **Dashboard:** Im Self-Hosting gibt es keine Cloud-Einstellungen; Studio ändert keine Auth-Werte. Alle Werte stehen im Env-Block oben. Änderungen im Studio sind zu unterlassen, damit Repo und Betrieb übereinstimmen.
- **Image-Pinning:** `supabase/gotrue:v2.197.0`. Die Postgres-Hauptversion 17 muss mit `db.major_version` übereinstimmen.
## 4. Verifiziert (Spike B0, 2026-11-02)
Getestet gegen CLI 3.0.0 und GoTrue `v2.197.0` über Kong (wie die App). Die Drosselungs- und Mail-Limit-Tests liefen zusätzlich gegen einen zweiten GoTrue-Container desselben Images mit den Env-Variablen der CLI plus `GOTRUE_RATE_LIMIT_HEADER=X-Forwarded-For` und `GOTRUE_RATE_LIMIT_EMAIL_SENT=5` (entspricht dem Self-Hosting-Pfad). Nicht aussagekräftig lokal: SMTP-Anbieter, Verhalten hinter dem Produktions-Gateway, Header-Behandlung des Proxys, echte Zustellung. Vollständige Nachweise: `docs/specs/1-auth-spike-ergebnisse.md`.
| Punkt | Ergebnis |
|---|---|
| (a) Betreff und Vorlage werten `.Data.locale` aus | Bestätigt. `de` → deutsch; `en` oder fehlend → englisch. `printf "%v" (index .Data "locale")` ist sicher, wenn der Schlüssel fehlt oder kein String ist (sonst HTTP 500 bei `eq`). Nach Profiländerung (Sync-Trigger) kommt die Mail in der neuen Sprache. |
| (b) `type` für `token_hash` | Bestätigung `email` (auch `signup`), Reset `recovery`. Zweites Einlösen → 403 `otp_expired`. |
| (c) Limiter Passwort-Login | `GOTRUE_RATE_LIMIT_TOKEN_REFRESH` (150 je 5 min, Burst 30); Signup/Recover/Resend `GOTRUE_RATE_LIMIT_OTP` (30 je 5 min). **Greifen nur mit `GOTRUE_RATE_LIMIT_HEADER` und Requests mit dem Header.** Die lokale CLI kann den Header nicht setzen, also lokal keine Drosselung. 429 ohne `Retry-After`. |
| (d) `email_sent` | Instanzweit. In der CLI wirkungslos (`360000`). Bei erschöpftem Limit antwortet `/recover` für bestehende Adressen 429, für unbekannte 200 (Seitenkanal, akzeptiert F12). |
| (e) Mailfänger | Mailpit v1.31.3, `http://127.0.0.1:54324/api/v1/messages`, `/api/v1/message/{id}`. |
| (f) Trigger-Fehler bei `signUp` | HTTP 500, Header `X-Sb-Error-Code: unexpected_failure`, Body `{"code":"P0001","message":"<grund>"}`. Format nicht vertraglich zugesichert. |
| (g) `weak_password` | 422, `reasons = ["length"]`. |
| (h) Versionen | CLI 3.0.0 startet GoTrue `v2.197.0`, identisch zur `docker-compose.yml` des Supabase-Repos (Stand 2026-11-02). |
| (i) Linkablauf | Maßgeblich sind `auth.users.confirmation_sent_at` / `recovery_sent_at`, nicht `auth.one_time_tokens.created_at`. |
| (j) `amr` im Recovery-JWT | `[{"method":"otp"}]`, identisch zur Bestätigungssitzung; nicht unterscheidbar (R12). |
Abweichungen von der ursprünglichen Spec-Annahme:
| # | Abweichung | Konsequenz |
|---|---|---|
| A1 | Enumerationsschutz hängt an `GOTRUE_SMS_AUTOCONFIRM=false` (CLI-Standard und Self-Hosting-Vorlage setzen `true`). Sonst 422 `user_already_exists`. | AUTH-CFG-24 |
| A2 | Ohne `GOTRUE_RATE_LIMIT_HEADER` keine IP-Drosselung; Requests ohne Header werden auch mit Einstellung nicht gedrosselt. | AUTH-CFG-13 Pflicht; Drosselungstest gegen Extra-Container |
| A3 | `email_sent` wirkt in der CLI nicht. | AUTH-CFG-09 nur per Env |
| A4 | Trigger-Fehler kommen als HTTP 500 mit der Trigger-Meldung im Body. | Mapping über Status 500 |
| A5 | Recovery-JWT ist an `amr` nicht erkennbar. | Guard nur im laufenden Prozess |
| A6 | Linkablauf wird über `auth.users.*_sent_at` bestimmt. | E2E-Helfer setzt diese Spalten |
| A7 | `/otp` und `/magiclink` nicht abschaltbar, `/otp` kann Konten anlegen. | AUTH-CFG-20 am Gateway |
### Prüfung der Konfiguration und Mailvorlagen (B2, lokal)
Mit der committeten `config.toml` geprüft (Mailpit-API): Registrierung mit `locale=de` → Betreff „Bestätige deine E-Mail-Adresse für Shuffle“, `lang="de"`; `locale=en` und ohne `locale` → englischer Betreff und Text; Passwort-Reset analog („Passwort für Shuffle zurücksetzen“ / „Reset your Shuffle password“). Links: `http://localhost:3000/auth/confirm?token_hash=…&type=email` bzw. `…&type=recovery`; der Token aus der Mail lässt sich per `POST /verify` einlösen. Im GoTrue-Container sind `GOTRUE_MAILER_AUTOCONFIRM=false`, `GOTRUE_SMS_AUTOCONFIRM=false`, `GOTRUE_PASSWORD_MIN_LENGTH=10`, `GOTRUE_MAILER_OTP_EXP=3600` und `GOTRUE_URI_ALLOW_LIST=http://localhost:3000/auth/confirm` gesetzt. Signup mit 9 Zeichen → 422 `weak_password`; Signup mit bestätigter Adresse → 200 mit erfundenem Nutzer und leeren `identities`, keine Mail.
## 5. Release-Checkliste (vor jedem Release auf Staging/Produktion)
Automatisiert nicht abdeckbar (Spec § 7.1); vor dem Release abhaken:
- [ ] GoTrue-Image ist `supabase/gotrue:v2.197.0` (oder bewusst aktualisiert, dann Spike-Punkte (c), (d), (f) wiederholen).
- [ ] Env-Block aus Abschnitt 2 ist vollständig gesetzt; `GOTRUE_PASSWORD_HIBP_ENABLED=false` (AUTH-CFG-21).
- [ ] `GOTRUE_SMS_AUTOCONFIRM=false` bzw. `ENABLE_PHONE_AUTOCONFIRM=false`: Signup mit bestätigter Adresse → 200, `identities` leer (AUTH-CFG-24).
- [ ] Drosselung am echten Gateway: 31 Fehllogins einer IP → 429; ein vom Client gefälschter `X-Forwarded-For` wird überschrieben; GoTrue ist von außen nicht direkt erreichbar (AUTH-CFG-13, -10).
- [ ] `POST /auth/v1/otp` und `/auth/v1/magiclink` werden vom Gateway abgelehnt (AUTH-CFG-20).
- [ ] Mailvorlagen-URLs erreichbar; Testmail de und en (Bestätigung und Reset) auf Staging, Betreff und Text in der richtigen Sprache (AUTH-CFG-17, -18, Risiko R5).
- [ ] Mindestabstand 60 s zwischen Mails je Nutzer (AUTH-CFG-08) und Link-Gültigkeit 1 h (AUTH-CFG-07) auf Staging geprüft.
- [ ] `GOTRUE_RATE_LIMIT_EMAIL_SENT=30` gesetzt und Monitoring auf 429 `over_email_send_rate_limit` aktiv (AUTH-CFG-09, R3).
- [ ] Nach Passwortänderung kommt keine Benachrichtigungs-Mail (AUTH-CFG-19).
- [ ] SMTP-Anbieter mit AVV und Absender-Domain entschieden und eingerichtet (AUTH-CFG-22, F7).
- [ ] SPA-Rewrite: `/auth/confirm` wird von der Web-App ausgeliefert.
- [ ] Rechtstexte-URLs und `TERMS_VERSION` final (R11); Bundle-/Application-ID final (R15).
## 6. Bekannte Grenzen
- **Keine Drosselung lokal und in der CI-CLI** (A2). Der Drosselungstest läuft gegen einen Extra-Container (Spec D2/T3).
- **Keine Wartezeit-Angabe:** Die 429-Antwort trägt kein `Retry-After`; die App nennt keine Dauer.
- **Drosselung nur je IP**, nicht je Konto.
- **Mail-Limit instanzweit** (30/h): Angreifer können Mailausfälle auslösen. Während der Sperre ist die Existenz eines Kontos über 429 vs. 200 erkennbar (R3, akzeptiert F12).
- **Enumeration auf API-Ebene:** Signup mit unbestätigter Adresse gibt den echten Nutzer zurück, mit bestätigter einen erfundenen (R6). Die App zeigt in beiden Fällen denselben Bildschirm.
- **Abgemeldete Geräte nach Reset:** Access-Tokens gelten bis zu 60 min weiter (R8, F1).
- **Abgebrochene Recovery** bleibt nach einem Reload als normale Anmeldung bestehen (R12).
- **Häufige Passwörter** prüft nur der Client; der Server erzwingt nur die Länge 10 (R14).
- **Magic Link / OTP** sind nicht abschaltbar, nur am Gateway sperrbar (R4).
- **Vorlagen-Rückfall:** Unerreichbare Vorlagen-URL → stille englische Standardvorlage (R5).
- **Plain-Text-Teil der Mails:** GoTrue sendet nur HTML. Die Vorlagen zeigen den Link deshalb zusätzlich als Klartext; Mailpit erzeugt lokal einen Text-Teil.