Add ADR-0003 with Forgejo 8.0.3 options and a step-by-step guide for runner/Forgejo settings, correct the misleading guard comments in ci.yml and apply the comfort guard to supabase-tests too. Refs #43 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
25 KiB
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.
# 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-Formit 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/otpundPOST /auth/v1/magiclinkab (z. B. 404/403). Beide Endpunkte lassen sich in GoTrue nicht abschalten;/otplegt 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/confirmausliefern (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 mitdb.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=falsebzw.ENABLE_PHONE_AUTOCONFIRM=false: Signup mit bestätigter Adresse → 200,identitiesleer (AUTH-CFG-24).- Drosselung am echten Gateway: 31 Fehllogins einer IP → 429; ein vom Client gefälschter
X-Forwarded-Forwird überschrieben; GoTrue ist von außen nicht direkt erreichbar (AUTH-CFG-13, -10). POST /auth/v1/otpund/auth/v1/magiclinkwerden 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=30gesetzt und Monitoring auf 429over_email_send_rate_limitaktiv (AUTH-CFG-09, R3).- Nach Passwortänderung kommt keine Benachrichtigungs-Mail (AUTH-CFG-19).
- Monitoring/Alarm auf SMTP-Fehler (Release-Blocker, D1, Issue #37/F13): GoTrue-Logs mit
unexpected_failure/HTTP 500 bei/recover,/resendund/signupalarmieren. Die App zeigt bei einem SMTP-Ausfall bewusst die neutrale „Prüfe dein Postfach“-Seite (Konten-Schutz); Nutzer bekommen keinen Hinweis und keine Mail, nur der Betrieb sieht den Ausfall. Die App loggt zusätzlich Operation, Fehlerart, Status und Code (ohne E-Mail). - SMTP-Anbieter mit AVV und Absender-Domain entschieden und eingerichtet (AUTH-CFG-22, F7).
- SPA-Rewrite:
/auth/confirmwird von der Web-App ausgeliefert. - Rechtstexte-URLs und
TERMS_VERSIONfinal (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).
- SMTP-Ausfall: Auf API-Ebene unterscheiden sich bekannte und unbekannte Adressen (500 vs. 200, Spike #37). Die App zeigt neutral (F13, R17); Restrisiko rohe API akzeptiert.
- 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.
7. E2E-Tests und SMTP-Ausfall (Issue #38, Spike #37)
Die Playwright-Tests unter e2e/ (Projekte api, web, throttle, spike; Anleitung e2e/README.md) sichern diese Konfiguration ab. Konstanten stehen in e2e/support/auth-config.ts; tests/api/config.spec.ts prüft sie gegen supabase/config.toml und diese Datei (alle IDs AUTH-CFG-01 bis -25 müssen in Abschnitt 1 vorkommen).
| ID | Absichernder Test |
|---|---|
| AUTH-CFG-04, -24 | tests/api/krit04-enumeration.spec.ts (Signup mit bestätigter Adresse → 200, identities leer, keine Mail, altes Passwort gilt) |
| AUTH-CFG-05 | krit02-03-validierung.spec.ts (9 Zeichen → 422 weak_password) |
| AUTH-CFG-07 | krit08-reset-link.spec.ts und web/krit08-reset.spec.ts (Link nach 1 h + 1 s abgelaufen, 60 s vor Ablauf gültig) |
| AUTH-CFG-10, -13 | tests/throttle/krit07-drosselung.spec.ts (Login #31 → 429, zweite IP unbeeinflusst, kein Retry-After, ohne Header keine Drosselung) |
| AUTH-CFG-14, -15, -16 | krit05-sitzung.spec.ts, config.spec.ts |
| AUTH-CFG-17, -18, -19 | krit11-sprache-benachrichtigungen.spec.ts |
CI-Job e2e (Issue #38, Spec #1 D2)
Der Job e2e in .forgejo/workflows/ci.yml läuft auf dem Runner docker-host (Docker-CLI + dind per TCP, wie supabase-tests) und führt die Projekte api, web und throttle aus. Ablauf:
supabase start -x studio,imgproxy,edge-runtime,logflare,vector(Mailpit und Postgres bleiben) undsupabase db reset.- Anon- und Service-Role-Key kommen zur Laufzeit aus
supabase status -o env; daraus entsteht eine temporäre Env-Datei (jqaufapp/env/ci.example.json, imRUNNER_TEMP, nicht im Repo) fürflutter build web --dart-define-from-file. Die Dateici.example.jsonbleibt unverändert. - Zweiter GoTrue-Container für Krit. 7:
BIND_ADDR=0.0.0.0 HEALTH_HOST=<dind-Gateway> e2e/support/start-throttle-gotrue.sh start throttle(Port 9998). .forgejo/scripts/e2e-run.shstartet den TCP-Relaye2e/support/tcp-forward.mjs(127.0.0.1:54321/54322/54324/9998 -> dind-Gateway) und ruftnpx playwright test --project=api --project=web --project=throttleauf. Der Relay ist nötig, weil die Ports im dind liegen, die Tests, die Web-App (CORS/Origin) unde2e/support/db.ts(erlaubt nur lokale Hosts) aber 127.0.0.1 erwarten. Die statische Auslieferung der Web-App auf Port 3000 übernimmt daswebServerder Playwright-Konfiguration (serve -s).- Bei Fehlern werden
e2e/playwright-reportunde2e/test-results(Traces) als Artefaktplaywright-<Lauf>hochgeladen. Der Schritt „Aufräumen“ (if: always()) entfernt den Extra-Container und stoppt den Supabase-Stack.
Das Projekt spike (SMTP-Ausfall, SPIKE_GOTRUE_URL) läuft in CI nicht. Der Job hat needs: supabase-tests (gleiche Container-Namen und Ports im dind) und läuft nicht für Fork-PRs (nur Komfort-Guard, kein Schutz vor böswilligen Forks): Wer PR-Code auf docker-host ausführt, hat Zugriff auf den dind-Daemon des Runners. Absicherung und offene Schritte: docs/adr/0003-ci-runner-sicherheit.md.
Achtung Artefakte: Playwright-Reports, Traces und Screenshots (CI-Artefakte, test-results/, playwright-report/) enthalten Tokens, Passwörter und Mailinhalte der angelegten Testkonten. Die Konten existieren nur in der Wegwerf-Instanz (lokale CLI/CI), dennoch Artefakte nicht in ein öffentliches Repo oder eine öffentliche Release-Seite legen und nur kurz aufbewahren. Die Tests lehnen nicht-lokale Hosts ab (SUPABASE_URL, MAILPIT_URL, E2E_DB_URL; CI-Hosts nur explizit über E2E_ALLOWED_HOSTS).
SMTP-Ausfall (Spike #37, Stand 2026-11-03, GoTrue v2.197.0)
Messung gegen einen zusätzlichen GoTrue-Container, dessen SMTP-Port 1 die Verbindung ablehnt (e2e/support/start-throttle-gotrue.sh start smtp-down, Test e2e/tests/spike/smtp-down.spec.ts). Mailpit bleibt für die anderen Tests unberührt. Die Tests prüfen nichts, sie protokollieren.
| Aufruf | bekannte Adresse | unbekannte Adresse |
|---|---|---|
POST /recover |
500 unexpected_failure „Error sending recovery email“ |
200 |
POST /resend (signup) |
unbestätigt: 500 „Error sending confirmation email“ | 200 |
POST /signup |
unbestätigt: 500; bestätigt: 200 (erfundener Nutzer, kein Versand) | neu: 500 „Error sending confirmation email“, Nutzer wird nicht angelegt (Rollback) |
Antwortzeiten liegen alle im einstelligen bis niedrigen zweistelligen Millisekundenbereich (Verbindung wird sofort abgelehnt), also kein Zeit-Seitenkanal. Befund: Bei nicht erreichbarem SMTP unterscheiden sich die Antworten von /recover, /resend und /signup je nach Existenz des Kontos (500 vs. 200), und zwar nicht nur bei erschöpftem Mail-Limit (R3, F12). Die rohe API verrät dann die Existenz; die App zeigt seit F13 (Issue #37, Spec R17) bei signUp, recover und resend auf CheckEmail dafür den neutralen Bildschirm (im LoginScreen bleibt der Resend-Fehler sichtbar, das Konto ist dort bewiesen). Die ursprüngliche Befürchtung, die App zeige einen Fehler statt des neutralen Bildschirms, ist damit behoben. Ob die App bei HTTP 500 aus Sendefehlern neutral bleiben soll (wie bei over_email_send_rate_limit), ist offen (Issue #37). Nicht untersucht: ein SMTP-Server, der die Verbindung annimmt, aber nicht antwortet (Timeout), und GoTrue hinter dem Produktions-Gateway. Dafür den Container mit einer nicht routbaren SMTP-Adresse (z. B. GOTRUE_SMTP_HOST=10.255.255.1) starten und GOTRUE_SMTP_PORT bis zum Timeout beobachten.