62 KiB
Spec #1 – KTO-01 Registrieren, anmelden und Passwort zurücksetzen
| Issue | #1 (KTO-01) |
| Status | Entwurf (offene Fragen F1–F10 von Chris entschieden am 2026-11-02, Freigabe der Spec steht aus) |
| Autor | architect |
| Stand | 2026-11-02 |
| Abhängigkeit | Fundament-Issue #31 (blockiert #1) |
| Quellen | requirements/20-r1-akzeptanzkriterien.md § KTO-01 und § Querschnitt, requirements/r1-klaerungen.md (18, 21/47, 22, 45, N1–N5, N3a, N13), docs/datenmodell-r1.md, supabase/migrations/20261102100100_profiles.sql, ADR-0001, ADR-0002 |
1. Ziel
Brettspieler können sich mit E-Mail, Passwort und Anzeigenamen registrieren (mit Bestätigung „mindestens 16“ und Zustimmung zu Datenschutzerklärung und Nutzungsbedingungen), ihre E-Mail-Adresse bestätigen, sich anmelden und angemeldet bleiben sowie ihr Passwort über einen einmaligen, zeitlich begrenzten Link zurücksetzen. Grundlage ist Supabase Auth (GoTrue) im Self-Hosting (ADR-0002); alle Auth-Einstellungen werden explizit im Repo festgelegt und dokumentiert (Klärung 18).
Gleichzeitig ist #1 die erste Story mit App-Code. Das Flutter-Gerüst existiert noch nicht (app/ fehlt) und entsteht im vorgelagerten Fundament-Issue #31 (siehe 8.1).
Kernentscheidungen (Kurzfassung)
| # | Entscheidung | Begründung |
|---|---|---|
| E1 | App-Fundament als eigenes Chore-Issue #31 vor #1, nicht als Teil von #1 (entschieden, F10) | KAT-01/KAT-02 (R1.1) brauchen das Gerüst auch ohne Anmeldung; kleinere, prüfbare PRs; CLAUDE.md verlangt ein Issue je Änderung. datenmodell-r1.md Schritt 4 bündelt Gerüst und Anmeldung. Wir weichen davon nur im Zuschnitt ab, die Reihenfolge bleibt gleich. |
| E2 | Ein Issue #1, drei PRs (01a Registrierung, 01b Anmelden, 01c Passwort zurücksetzen), davor ein Backend-PR (Migration + Auth-Konfiguration) | Die Kriterien teilen Backend-Konfiguration und Domänenschicht. Getrennte Issues würden Abhängigkeiten nur verschieben. Die PRs referenzieren Refs #1, der letzte Closes #1. Abweichung vom Issue-Vorschlag: Krit. 6 liegt in 01b statt 01a, weil der Hinweis auf die ausstehende Bestätigung beim Anmelden erscheint und den LoginScreen braucht. Die Aktion „E-Mail erneut senden“ entsteht schon in 01a (CheckEmailScreen) und wird in 01b wiederverwendet. |
| E3 | Links in Mails zeigen auf eine App-Seite /auth/confirm?token_hash=…&type=…, nicht auf {{ .ConfirmationURL }}. Die Seite löst verifyOTP erst per Knopfdruck aus. |
Funktioniert geräteübergreifend: kein PKCE-Verifier nötig, z. B. Registrierung am Handy und Mail am PC. Mail-Scanner (z. B. Safe Links) verbrauchen den Einmal-Link nicht. Die Einmaligkeit bleibt serverseitig erhalten (Kriterium 8). |
| E4 | In R1 landen Mail-Links immer in der Web-App. Universal Links/App Links (iOS/Android) sind nicht Teil von #1. | Die Web-App kann alle Schritte ausführen. Universal Links brauchen AASA/assetlinks-Hosting und Store-Signaturen; das ist eine eigene Aufgabe und sollte ein Folge-Issue werden. |
| E5 | Mailsprache über user_metadata.locale: Je Mailtyp gibt es eine Vorlage mit Go-Template-Verzweigung de/en (Standard Englisch, F6). Ein neuer DB-Trigger hält auth.users.raw_user_meta_data.locale synchron zu profiles.locale. |
GoTrue kennt nur eine Vorlage je Typ und stellt .Data (= user_metadata) bereit. Die Profilsprache ist maßgeblich (Kriterium 11, KTO-11 Kriterium 2). Ein Send-Email-Hook (Edge Function + eigener SMTP-Versand) wäre mehr bewegliche Teile ohne zweiten Anwendungsfall. Er ist der Rückfall, falls der Spike B0 zeigt, dass Betreffzeilen .Data nicht auswerten. |
| E6 | Validierung zweistufig: Feldfehler im Client (Kriterien 2, 3, 10). Der Server erzwingt dieselben Regeln als Schutz (Trigger, minimum_password_length = 10). |
Der Client gibt sofort Rückmeldung am Feld. Der Server vertraut nie dem Client. GoTrue meldet Trigger-Fehler nur als generischen 500er, deshalb ist der Client die nutzersichtbare Stelle. |
| E7 | „Liste häufiger Passwörter“ wird nur im Client als gebündelte Liste geprüft; der Server erzwingt die Mindestlänge 10. Kein Have I Been Pwned (entschieden, F2): GOTRUE_PASSWORD_HIBP_ENABLED=false. |
HIBP würde einen SHA-1-Präfix an einen US-Dienst (Cloudflare/HIBP) übertragen (Klärung 21/47). Außerdem gibt es HIBP in der lokalen Supabase CLI nicht. Restrisiko: Über die rohe API lassen sich häufige Passwörter mit mindestens 10 Zeichen setzen (R14). |
| E8 | Drosselung und Linkgültigkeit: Es gelten die Supabase-Standardwerte, die explizit gepinnt werden, identisch in supabase/config.toml (lokal/CI) und im Env-Block für Staging/Produktion. Die Werte stehen in docs/auth-konfiguration.md und werden in Tests über die Doku-ID referenziert. |
Klärung 18. Die Standardwerte unterscheiden sich zwischen GoTrue-Binary, CLI-Vorlage und Docker-Setup (z. B. OTP-Gültigkeit 86400 s im Binary gegenüber 3600 s in der CLI, enable_confirmations in der CLI standardmäßig false). Ohne Pinning wären „die Standards“ nicht eindeutig. |
| E9 | Sitzung bleibt bestehen über den Standard-Speicher von supabase_flutter (Mobile: SharedPreferences, Web: localStorage). Kein Timebox, kein Inaktivitäts-Timeout. |
Kriterium 5. Ein zusätzliches Paket (z. B. flutter_secure_storage) gibt es erst bei konkretem Bedarf, siehe Risiko R7. |
2. Akzeptanzkriterien
Übernommen aus Issue #1 / 20-r1-akzeptanzkriterien.md § KTO-01. Referenz in Tests und PRs: „KTO-01 Kriterium n“.
- Gegeben ich habe kein Konto, wenn ich E-Mail-Adresse, Passwort und Anzeigenamen eingebe und bestätige, dass ich mindestens 16 Jahre alt bin, dann wird ein Konto angelegt und ich erhalte eine E-Mail zur Bestätigung.
- Gegeben ich bestätige das Mindestalter nicht, wenn ich mich registrieren will, dann wird die Registrierung abgelehnt und ich sehe den Grund.
- Gegeben das Passwort hat weniger als 10 Zeichen oder steht auf einer Liste häufiger Passwörter, wenn ich es eingebe, dann erklärt mir die App am Feld, was fehlt.
- Gegeben die E-Mail-Adresse ist schon registriert, wenn ich mich damit registriere, dann erfahre ich nicht, ob ein Konto existiert, und es entsteht kein zweites Konto.
- Gegeben ein bestätigtes Konto, wenn ich die richtigen Zugangsdaten eingebe, dann bin ich angemeldet und bleibe es auf diesem Gerät, bis ich mich abmelde.
- Gegeben ein unbestätigtes Konto, wenn ich mich anmelde, dann werde ich auf die ausstehende Bestätigung hingewiesen und kann die E-Mail erneut senden lassen.
- Gegeben falsche Zugangsdaten, wenn ich mich anmelde, dann sehe ich eine allgemeine Fehlermeldung, die nicht verrät, ob E-Mail oder Passwort falsch war; nach mehreren Fehlversuchen wird der Zugang zeitweise gebremst. Anzahl der Versuche und Dauer der Bremse folgen den Standards der Supabase-Auth-Konfiguration (self-hosted); die Werte sind im Repo dokumentiert und in den Tests referenziert (Klärung 18).
- Gegeben ich habe das Passwort vergessen, wenn ich das Zurücksetzen anfordere, dann erhalte ich einen zeitlich begrenzten Link, mit dem ich ein neues Passwort setze; der Link funktioniert nur einmal. Die Gültigkeit von Reset- und Bestätigungslink folgt der Supabase-Auth-Konfiguration und ist im Repo dokumentiert (Klärung 18).
- Gegeben ich habe das Passwort zurückgesetzt, dann bin ich auf allen anderen Geräten abgemeldet.
- Gegeben ich registriere mich, wenn der Anzeigename leer ist oder länger als 30 Zeichen, dann kann ich mich nicht registrieren und sehe den Fehler am Feld; es gelten dieselben Regeln wie in KTO-02 Kriterium 1 (Klärung 22).
- Gegeben Shuffle sendet mir eine E-Mail zur Bestätigung oder zum Zurücksetzen des Passworts, dann ist sie in der Sprache meines Profils; andere E-Mails verschickt Shuffle in R1 nicht, außer Bestätigungslink und Info-Mail beim Wechsel der E-Mail-Adresse (KTO-11 Kriterium 4) (Klärung 45, N2).
Relevante Querschnittskriterien (Referenz „QUE-04 Kriterium n“ usw.):
- Datenschutz (QUE-04) Kriterium 1: Datenschutzerklärung und Nutzungsbedingungen sind vor der Registrierung sichtbar, und die Zustimmung ist ausdrücklich (eigene Checkbox).
- Datenschutz Kriterien 2 und 4: In den Auth-Abläufen gibt es keine optionalen personenbeziehbaren Dienste, kein Tracking-SDK und keinen HIBP-Abgleich (F2).
- Datenschutz, Nachweis der Zustimmung: Gespeichert werden Zeitpunkt (
terms_accepted_at) und Version (terms_version) der zugestimmten Rechtstexte (F5). - Sicherheit der Daten Kriterium 2: Ohne Anmeldung keine privaten Daten. Router-Guard im Client, RLS im Server; die RLS für
profilesist bereits getestet. - Sicherheit der Daten Kriterium 3: Neue DB-Objekte (Trigger) bekommen pgTAP-Tests.
- Sprachen (QUE-05) Kriterien 1, 2, 4: Alle Texte stehen in ARB, Fallback ist Englisch. Die Sprache bei der Registrierung ist die aktive Oberflächensprache (KTO-11 Kriterium 11, N3a).
- Barrierefreiheit (QUE-06) Kriterien 1–4: Labels, Fehler werden per Screenreader angesagt, Kontrast AA, 48 dp, 200 % Schrift, Tastaturbedienung im Web.
- Zustände, Fehler und Netz Kriterien 2, 3: Offline gibt es einen verständlichen Hinweis; Eingaben bleiben bei Fehlern erhalten.
- Plattformen Kriterien 1, 2: iOS, Android, Web; auf breiten Bildschirmen ein zentriertes Formular mit begrenzter Breite.
3. Datenmodell
3.1 Bestand (Migration 20261102100100_profiles.sql) gegen die Kriterien
| Regel | Bestand | Abgleich | Handlung |
|---|---|---|---|
| Mindestalter (Krit. 1, 2) | Trigger handle_new_user wirft age_confirmation_required, wenn age_confirmed <> 'true'; profiles.age_confirmed_at not null |
passt | keine |
| Zustimmung Rechtstexte (QUE-04 Krit. 1) | Trigger wirft terms_acceptance_required; terms_accepted_at not null |
Zeitpunkt passt; die Version der Texte wird nicht gespeichert | neue Spalte terms_version (3.2, entschieden F5) |
| Anzeigename 1–30 (Krit. 10) | Check char_length(display_name) between 1 and 30. Der Trigger füllt aber leere Namen mit dem E-Mail-Präfix auf und kürzt zu lange Namen auf 30. Ein Name nur aus Leerzeichen (' ') besteht den Check. |
widerspricht Krit. 10 (Registrierung muss abgelehnt werden, nicht stillschweigend korrigiert) und KTO-02 Krit. 1 („leer“ schließt reine Leerzeichen ein) | neue Migration (3.2) |
| Sprache (Krit. 11, KTO-11 Krit. 11) | locale aus user_metadata.locale, wenn de/en, sonst 'de' |
Der Fallback 'de' und der Spalten-Standard 'de' weichen von QUE-05 ab. Entschieden (F6): Ohne Angabe gilt die Gerätesprache, sonst Englisch. Die Gerätesprache kennt nur der Client; er sendet sie immer als aktive Oberflächensprache. Der Server fällt deshalb auf 'en' zurück. |
neue Migration (3.2): Standard und Trigger-Fallback 'en' |
| Mailsprache folgt Profil (Krit. 11) | Mailvorlagen sehen nur user_metadata. profiles.locale und user_metadata.locale laufen auseinander, sobald KTO-11 die Sprache ändert. |
Lücke | neue Migration (3.2): Sync-Trigger |
RLS profiles |
nur das eigene Profil lesen und ändern; Spaltenrechte; anlegen nur per Trigger | passt (pgTAP 01_profiles.test.sql) |
keine |
3.2 Neue Migration supabase/migrations/<ts>_auth_registration_rules.sql
Nur additiv: create or replace function und neue Objekte, keine Änderung an bestehenden Dateien.
| Tabelle/Objekt | Spalte | Typ | Hinweis |
|---|---|---|---|
public.profiles |
display_name |
text | neuer Check profiles_display_name_not_blank: char_length(btrim(display_name)) between 1 and 30. Er gilt auch für KTO-02 (Updates). Bestehende Seed-Zeilen erfüllen ihn. |
public.profiles |
terms_version |
text, nullable | neue Spalte (F5): Version der zugestimmten Rechtstexte, z. B. 2026-11-draft. Check terms_version is null or char_length(terms_version) between 1 and 50. Nullable nur wegen bereits vorhandener Zeilen (nur Entwicklung); für neue Konten erzwingt der Trigger einen Wert. Nicht in den Update-Rechten von authenticated, der Nutzer kann sie also nicht ändern. Der Zeitstempel der Zustimmung steht bereits in terms_accepted_at (Trigger setzt now()). Eine erneute Zustimmung nach einer Änderung der Rechtstexte ist nicht Teil von R1 (siehe 9.4). |
public.profiles |
locale |
text | Standard geändert auf 'en' (alter column locale set default 'en', F6). Der Check ('de','en') bleibt. |
public.handle_new_user() |
– | Trigger-Funktion | ersetzt (create or replace). Neue Regeln: (a) Alter und Zustimmung wie bisher. (b) terms_version := btrim(meta->>'terms_version'); ist der Wert null, leer oder länger als 50 Zeichen, wirft der Trigger terms_version_required (P0001). (c) display_name := btrim(meta->>'display_name'). Ist der Wert null, leer oder länger als 30 Codepoints, wirft der Trigger display_name_invalid (SQLSTATE P0001). Kein Rückgriff auf das E-Mail-Präfix, kein Kürzen. (d) Der getrimmte Name und terms_version werden gespeichert, terms_accepted_at und age_confirmed_at = now(). (e) locale aus meta->>'locale', wenn de/en, sonst 'en' (F6). |
public.sync_profile_locale_to_auth() |
– | Trigger-Funktion, security definer, search_path = '' |
after update of locale on public.profiles, wenn sich der Wert ändert: update auth.users set raw_user_meta_data = coalesce(raw_user_meta_data,'{}') || jsonb_build_object('locale', new.locale) where id = new.id. Ausführen nur für den Trigger: revoke all … from public, anon, authenticated. |
Trim-Regel (verbindlich für Client und Server, auch KTO-02): Leerraum am Anfang und Ende wird entfernt. Danach muss der Name 1 bis 30 Unicode-Codepoints lang sein. Der Server prüft mit char_length(btrim(…)), der Client mit value.trim().runes.length, nicht mit String.length (UTF-16). Bekannte Abweichung: Dart-trim() entfernt auch Unicode-Leerraum (z. B. NBSP), btrim nur Leerzeichen. Der Client sendet bereits getrimmte Werte, die Abweichung ist deshalb nur über die rohe API erreichbar und unkritisch.
Keine neuen Tabellen. auth.users und auth.sessions verwaltet GoTrue.
RLS in Klartext: Es gibt keine neuen Policies. Profile legt weiterhin nur der Trigger bei der Registrierung an. Jeder Nutzer liest und ändert nur sein eigenes Profil. Bestätigungszeitpunkte, terms_version und Löschmarke kann der Nutzer nicht ändern, lesen darf er sie (eigenes Profil). Der Sync-Trigger schreibt als Definer nur die Sprache des eigenen Nutzers (where id = new.id). Ein Nutzer kann so nur seine eigene Mailsprache beeinflussen.
Doku: docs/datenmodell-r1.md (Regel Anzeigename, terms_version, Standard locale = 'en', Sync-Trigger) und supabase/README.md werden im selben PR aktualisiert.
4. Backend
4.1 Supabase-Auth-Konfiguration (self-hosted)
Alle Werte stehen identisch in supabase/config.toml (lokal/CI) und als GoTrue-Env-Block in docs/auth-konfiguration.md (Staging/Produktion, ohne Secrets). Die Tabelle unten ist der Entwurf. Spike B0 prüft jeden Wert gegen die tatsächlich eingesetzten Versionen (CLI und Image supabase/gotrue) und trägt das Ergebnis in die Doku ein. Tests referenzieren die Zeilen-ID (z. B. AUTH-CFG-07).
| ID | Einstellung | CLI config.toml |
GoTrue-Env (self-hosted) | Wert (Entwurf) | Quelle / Anmerkung | Krit. |
|---|---|---|---|---|---|---|
| AUTH-CFG-01 | Site-URL | auth.site_url |
GOTRUE_SITE_URL |
lokal http://localhost:3000; Prod https://shuffle.cshomelabs.work (Platzhalter-Domain, F8, leicht änderbar); Staging-Subdomain legt devops fest |
Basis für Mail-Links ({{ .SiteURL }}) |
1, 8 |
| AUTH-CFG-02 | Redirect-Allow-List | auth.additional_redirect_urls |
GOTRUE_URI_ALLOW_LIST |
nur eigene Web-URLs (…/auth/confirm) |
Mail-Links bauen auf Site-URL auf | 1, 8 |
| AUTH-CFG-03 | Registrierung erlaubt | auth.enable_signup, auth.email.enable_signup |
GOTRUE_DISABLE_SIGNUP=false, GOTRUE_EXTERNAL_EMAIL_ENABLED=true |
an | – | 1 |
| AUTH-CFG-04 | E-Mail-Bestätigung Pflicht | auth.email.enable_confirmations = true |
GOTRUE_MAILER_AUTOCONFIRM=false |
Bestätigung Pflicht | CLI-Standard ist false, muss gesetzt werden. Voraussetzung für den Enumeration-Schutz (4.3). |
1, 4, 6 |
| AUTH-CFG-05 | Passwort-Mindestlänge | auth.minimum_password_length |
GOTRUE_PASSWORD_MIN_LENGTH |
10 | O10; Standard wäre 6 | 3 |
| AUTH-CFG-06 | Zeichenklassen | auth.password_requirements |
GOTRUE_PASSWORD_REQUIRED_CHARACTERS |
keine (leer) | Nicht gefordert | 3 |
| AUTH-CFG-07 | Gültigkeit Bestätigungs- und Reset-Link | auth.email.otp_expiry |
GOTRUE_MAILER_OTP_EXP |
3600 s (1 h) | Entschieden (F3). Entspricht dem CLI- und Cloud-Standard; das Self-hosted-Binary hätte 86400, deshalb im Env explizit setzen. Ein Wert für beide Linkarten. | 1, 8 |
| AUTH-CFG-08 | Mindestabstand zwischen Mails je Nutzer | auth.email.max_frequency |
GOTRUE_SMTP_MAX_FREQUENCY |
Staging/Prod: 60 s (Standard); lokal/CI: 1 s | Lokale Abweichung ist bewusst, sonst scheitert der Resend-Test (Krit. 6) direkt nach dem Signup an 429. Ein eigener Test prüft die 60 s auf Staging (Release-Checkliste). | 6, 8 |
| AUTH-CFG-09 | Mails pro Stunde (Instanz) | auth.rate_limit.email_sent |
GOTRUE_RATE_LIMIT_EMAIL_SENT |
lokal/CI: 100 (sonst blockieren E2E-Tests); Staging/Prod: 30 (Binary-Standard, entschieden F4) | Wahrscheinlich instanzweit; B0 prüft das. Risiko R3. | 1, 6, 8 |
| AUTH-CFG-10 | Drosselung Anmeldung (Passwort) | auth.rate_limit.token_refresh (laut Code) bzw. sign_in_sign_ups (laut CLI-Doku) |
GOTRUE_RATE_LIMIT_TOKEN_REFRESH |
150 je 5 min je IP, Burst 30 | GoTrue-Code: Der Passwort-Login nutzt den Token-Limiter (RateLimitTokenRefresh/300 s, Burst 30). B0 klärt empirisch, welcher Schlüssel greift. Keine Sperre je Konto, nur je IP. |
7 |
| AUTH-CFG-11 | Drosselung Registrierung, Reset, erneut senden | auth.rate_limit.sign_in_sign_ups |
GOTRUE_RATE_LIMIT_OTP |
30 je 5 min je IP, Burst 30 | Signups, Recover, Resend teilen sich diesen Limiter | 1, 6, 8 |
| AUTH-CFG-12 | Drosselung Link-Prüfung | auth.rate_limit.token_verifications |
GOTRUE_RATE_LIMIT_VERIFY |
30 je 5 min je IP | – | 8 |
| AUTH-CFG-13 | Client-IP hinter Proxy | – (lokal irrelevant) | GOTRUE_RATE_LIMIT_HEADER (z. B. X-Forwarded-For) + Proxy überschreibt den Header |
Prod: gesetzt und nicht fälschbar | Ohne diese Einstellung teilen sich alle Nutzer die IP des Gateways (Risiko R2) | 7 |
| AUTH-CFG-14 | JWT-Laufzeit | auth.jwt_expiry |
GOTRUE_JWT_EXP |
3600 s | Bleibt (entschieden F1). Bestimmt die Verzögerung bei Krit. 9: andere Geräte sind spätestens nach 60 min abgemeldet (bewusstes Restrisiko R8). | 5, 9 |
| AUTH-CFG-15 | Refresh-Token-Rotation | auth.enable_refresh_token_rotation, refresh_token_reuse_interval |
GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED, …_REUSE_INTERVAL |
an, 10 s | Standard | 5, 9 |
| AUTH-CFG-16 | Sitzungsbegrenzung | auth.sessions.timebox, inactivity_timeout |
GOTRUE_SESSIONS_TIMEBOX, …_INACTIVITY_TIMEOUT |
nicht gesetzt | Krit. 5: angemeldet bis zum Abmelden | 5 |
| AUTH-CFG-17 | Mailvorlage Bestätigung | auth.email.template.confirmation.{subject,content_path} |
GOTRUE_MAILER_TEMPLATES_CONFIRMATION (URL), GOTRUE_MAILER_SUBJECTS_CONFIRMATION |
supabase/templates/confirmation.html |
Self-hosted lädt Vorlagen per URL; ist die URL nicht erreichbar, fällt GoTrue still auf die englische Standardvorlage zurück (Risiko R5) | 1, 11 |
| AUTH-CFG-18 | Mailvorlage Reset | auth.email.template.recovery.{…} |
GOTRUE_MAILER_TEMPLATES_RECOVERY, …_SUBJECTS_RECOVERY |
supabase/templates/recovery.html |
wie oben | 8, 11 |
| AUTH-CFG-19 | Benachrichtigungs-Mails (password_changed, email_changed, phone_changed, mfa_*, identity_*) |
auth.email.notification.<typ>.enabled |
GOTRUE_MAILER_NOTIFICATIONS_<TYP>_ENABLED |
alle aus | Krit. 11. email_changed (Info-Mail an die alte Adresse, N2) schaltet KTO-11 ein. |
11 |
| AUTH-CFG-20 | Magic Link / OTP-Login | – (kein Schalter) | – (kein Schalter) | Prod: POST /auth/v1/otp und /auth/v1/magiclink am Gateway sperren |
Sonst kann jeder Magic-Link-Mails an beliebige Adressen auslösen (Verstoß gegen Krit. 11) und Konten ohne Passwort anlegen (Risiko R4) | 11, 3 |
| AUTH-CFG-21 | Leaked-Password-Prüfung (HIBP) | nicht in der CLI | GOTRUE_PASSWORD_HIBP_ENABLED, …_FAIL_CLOSED |
aus (GOTRUE_PASSWORD_HIBP_ENABLED=false, explizit gesetzt) |
Entschieden (F2): kein HIBP, keine Drittlandübermittlung | 3 |
| AUTH-CFG-22 | SMTP | lokal: CLI-Mailfänger (Mailpit bzw. Inbucket, je nach CLI-Version) | GOTRUE_SMTP_* |
Staging/Prod: EU-Anbieter mit AVV, Anbieter und Absender-Domain noch offen (F7, später) | Release-Blocker über D1, kein Merge-Blocker | 1, 8, 11 |
| AUTH-CFG-23 | E-Mail-Wechsel beide Adressen bestätigen | auth.email.double_confirm_changes |
GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED |
in #1 nicht festlegen, Entscheidung in KTO-11 | Der Standard true schickt einen Bestätigungslink auch an die alte Adresse; N2 verlangt dort nur eine Info-Mail |
(KTO-11) |
Mailvorlagen (supabase/templates/confirmation.html, recovery.html):
- Je eine Vorlage mit Verzweigung
{{ if and .Data (eq (index .Data "locale") "de") }}…DE…{{ else }}…EN…{{ end }}. Der Else-Zweig ist Englisch und entspricht damit dem neuen Profil-Standard'en'(F6). - Die Betreffzeile wird gleich verzweigt. Zeigt B0, dass Betreffzeilen
.Datanicht sehen, gilt der Rückfall: zweisprachiger Betreff „Confirm your email address / Bestätige deine E-Mail-Adresse“. - Link:
{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email(Bestätigung) bzw.…&type=recovery(Reset). Die genauentype-Werte prüft B0. - Inhalt: kurzer Text, Gültigkeitsdauer in Klartext („1 Stunde“), Hinweis „Wenn du das nicht warst, ignoriere diese E-Mail“. Kein Tracking-Pixel, keine externen Bilder.
4.2 RPCs / Edge Functions
Keine. Alle Abläufe laufen über die GoTrue-REST-API (supabase_flutter):
| Ablauf | Aufruf | Eingabe | Ausgabe / Fehler |
|---|---|---|---|
| Registrieren | auth.signUp(email, password, data: {display_name, age_confirmed: true, terms_accepted: true, terms_version, locale}) |
siehe links | 200 mit Nutzer, Sitzung null (Bestätigung offen). Auch bei bereits registrierter Adresse 200 (4.3). weak_password (422, reasons), over_request_rate_limit (429), Trigger-Fehler → 500 „Database error saving new user“, Netzfehler |
| Bestätigung erneut senden | auth.resend(type: OtpType.signup, email) |
200 immer; 429 bei Drosselung | |
| Anmelden | auth.signInWithPassword(email, password) |
– | Sitzung; invalid_credentials (400), email_not_confirmed (400, nur bei richtigem Passwort, siehe 4.3), 429 |
| Reset anfordern | auth.resetPasswordForEmail(email) |
200 immer (keine Enumeration); 429 | |
| Link einlösen | auth.verifyOTP(tokenHash, type: email | recovery) |
aus der URL | Sitzung; bei Reset Ereignis passwordRecovery; otp_expired bzw. ungültig (403) |
| Neues Passwort | auth.updateUser(UserAttributes(password)) in der Recovery-Sitzung |
Passwort | 200; weak_password (422), same_password (422). GoTrue beendet alle anderen Sitzungen des Nutzers (LogoutAllExceptMe) |
4.3 Verhalten von GoTrue, auf das sich die Spec stützt (B0 prüft jeden Punkt)
- Krit. 4 (Enumeration): Bei
autoconfirm=falseliefertsignUpmit einer bestätigten Adresse 200 und einen erfundenen Nutzer (zufällige ID, leereidentities). Es wird keine Mail verschickt und kein Konto angelegt. Bei einer unbestätigten Adresse geht die Bestätigungsmail erneut an das bestehende Konto, Passwort und Metadaten bleiben unverändert, und es kommt der echte Nutzer zurück. Die Antworten unterscheiden sich also auf API-Ebene; das ist das bekannte Restrisiko des Supabase-Standards (R6). Die App zeigt nach jeder 200-Antwort denselben Bildschirm „Prüfe dein Postfach“ und wertetuser.identitiesausdrücklich nicht aus. - Krit. 6/7:
email_not_confirmedkommt erst nach erfolgreicher Passwortprüfung. Wer das Passwort nicht kennt, erfährt nichts über den Bestätigungsstatus. - Krit. 9:
UpdatePasswordmit Sitzungs-ID ruftLogoutAllExceptMeauf. Andere Refresh-Tokens werden ungültig, bereits ausgestellte Access-JWTs bleiben aber bis zujwt_expiry(3600 s) gültig, weil PostgREST keine Sitzungen prüft. Chris hat diese Verzögerung von bis zu 60 min akzeptiert (F1, Restrisiko R8). Texte in App und Hilfe versprechen deshalb nicht „sofort“. - Krit. 8 (einmalig): Mit
verifyOTPist der Token verbraucht. Eine neue Reset-Anforderung ersetzt den alten Token. NachupdateUser(password)löscht GoTrue alle Einmal-Tokens des Nutzers.
4.4 Fehlerfälle → Domänenfehler (Mapping-Tabelle, verbindlich für flutter-dev)
GoTrue (AuthException.code / HTTP) |
Domänenfehler | Anzeige |
|---|---|---|
invalid_credentials |
AuthFailure.invalidCredentials |
allgemein: „E-Mail-Adresse oder Passwort ist nicht korrekt.“ (ein Text für beide Fälle) |
email_not_confirmed |
AuthFailure.emailNotConfirmed |
Hinweis „Bestätige zuerst deine E-Mail-Adresse“ + Aktion „E-Mail erneut senden“ |
over_request_rate_limit, over_email_send_rate_limit, HTTP 429 |
AuthFailure.rateLimited |
„Zu viele Versuche. Bitte warte einige Minuten und versuche es erneut.“ |
weak_password (reasons: length, characters; pwned tritt ohne HIBP nicht auf, wird aber defensiv mitgemappt) |
AuthFailure.weakPassword(reasons) |
am Passwortfeld, je Grund |
same_password |
AuthFailure.samePassword |
am Passwortfeld: „Wähle ein anderes als dein bisheriges Passwort.“ |
otp_expired, invalid / 403 bei verifyOTP |
AuthFailure.linkInvalidOrExpired |
„Der Link ist abgelaufen oder wurde schon benutzt.“ + Aktion „Neuen Link anfordern“ |
email_address_invalid, validation_failed |
AuthFailure.invalidEmail |
am E-Mail-Feld |
HTTP 500 bei signUp (Trigger) |
AuthFailure.registrationRejected |
allgemein: „Registrierung nicht möglich. Bitte prüfe deine Angaben.“ (Im Normalfall verhindert der Client das.) |
AuthRetryableFetchException, SocketException, ClientException, Timeout |
AuthFailure.network |
Offline-Hinweis „Keine Verbindung. Für diesen Schritt brauchst du Internet.“ |
| sonst | AuthFailure.unexpected |
allgemein + „Erneut versuchen“; Details nur ins Log (ohne E-Mail und Passwort) |
5. Frontend
5.1 Voraussetzung: Fundament-Issue #31 (siehe 8.1)
Vorhanden sein müssen: Projektgerüst, Riverpod mit Codegen, go_router mit usePathUrlStrategy(), l10n de/en mit englischem Fallback, Theme-Basis, Logger, Env-Konfiguration (--dart-define-from-file), Supabase-Client-Provider sowie appLocaleProvider (aktive Sprache: Gerätesprache de/en, sonst en).
Platzhalter (F8, F9, zentral und leicht änderbar):
| Wert | Platzhalter | Ort | Hinweis |
|---|---|---|---|
| App-Name | „Shuffle“ | Plattform-Manifeste, l10n | – |
| Bundle-/Application-ID | work.cshomelabs.shuffle |
iOS/Android-Projekt | vor dem Store-Release endgültig festlegen; nachträgliche Änderung ist nach Veröffentlichung nicht möglich |
| Web-Domain | shuffle.cshomelabs.work |
SITE_URL, AUTH-CFG-01/02 |
– |
| Rechtstexte | LEGAL_PRIVACY_URL, LEGAL_TERMS_URL → Platzhalter-URLs |
app/env/*.json |
Chris liefert die Texte vor dem Release (R11) |
| Version der Rechtstexte | TERMS_VERSION (z. B. 2026-11-draft) |
app/env/*.json → AppConfig.termsVersion |
wird bei jeder Registrierung als terms_version mitgeschickt (F5); bei neuen Rechtstexten hochzählen |
5.2 Struktur app/lib/features/auth/
domain/
auth_user.dart AuthUser (freezed): id, email, emailConfirmed
auth_status.dart sealed AuthStatus: signedOut | signedIn(AuthUser) | passwordRecovery(AuthUser)
auth_failure.dart sealed AuthFailure (siehe 4.4)
auth_repository.dart Interface (siehe unten)
validation/
display_name_rule.dart trim + runes.length 1..30 → DisplayNameError.empty | tooLong
password_policy.dart minLength 10, häufige Passwörter → PasswordError.tooShort(missing) | common
email_format.dart einfache Syntaxprüfung (Feinprüfung macht der Server)
common_passwords.dart Interface CommonPasswordList (Implementierung in data/)
data/
supabase_auth_repository.dart Implementierung + Fehler-Mapping 4.4
asset_common_password_list.dart lädt assets/security/common-passwords.txt (lazy)
presentation/
auth_status_provider.dart @riverpod Stream<AuthStatus> aus onAuthStateChange
register/ register_screen.dart, register_controller.dart
login/ login_screen.dart, login_controller.dart
check_email/ check_email_screen.dart (+ resend_controller.dart)
forgot_password/ forgot_password_screen.dart, forgot_password_controller.dart
email_link/ email_link_screen.dart, email_link_controller.dart
reset_password/ reset_password_screen.dart, reset_password_controller.dart
Die Domäne ist frei von Flutter- und Supabase-Imports. DisplayNameRule und PasswordPolicy werden in KTO-02 und KTO-11 wiederverwendet. Ein zweiter Anwendungsfall ist damit belegt, deshalb liegen beide in der Domäne von auth. Bei KTO-02 ist zu prüfen, ob DisplayNameRule nach features/profile/domain wandert.
AuthRepository:
Stream<AuthStatus> watchStatus();
AuthStatus get currentStatus;
Future<Result<void, AuthFailure>> signUp(RegistrationInput input); // email, password, displayName, ageConfirmed, termsAccepted, termsVersion, locale
Future<Result<void, AuthFailure>> resendConfirmation(String email);
Future<Result<void, AuthFailure>> signIn(String email, String password);
Future<Result<void, AuthFailure>> requestPasswordReset(String email);
Future<Result<void, AuthFailure>> verifyEmailLink(String tokenHash, EmailLinkType type); // confirmation | recovery
Future<Result<void, AuthFailure>> setNewPassword(String password);
Future<void> signOut(); // genutzt bei abgebrochener Recovery; das UI zum Abmelden kommt mit KTO-11
Result ist ein einfacher eigener sealed Typ in core/ (kein zusätzliches Paket).
5.3 Routen (go_router)
| Pfad | Screen | Zugang |
|---|---|---|
/ |
Startseite (Fundament: Platzhalter; später QUE-09 bzw. öffentliche Katalogsuche) | öffentlich |
/login |
LoginScreen | nur abgemeldet; angemeldet → / |
/register |
RegisterScreen | nur abgemeldet |
/register/check-email |
CheckEmailScreen (E-Mail als extra, nicht in der URL) |
öffentlich |
/forgot-password |
ForgotPasswordScreen → danach CheckEmailScreen (Modus Reset) | nur abgemeldet |
/auth/confirm?token_hash&type |
EmailLinkScreen | öffentlich |
/reset-password |
ResetPasswordScreen | nur im Status passwordRecovery |
Redirect-Regeln (zentral im Router, refreshListenable aus authStatusProvider):
- Status
passwordRecovery→ jede Route außer/reset-passwordleitet nach/reset-passwordum. - Status
signedInauf/login,/registeroder/forgot-password→/. - Private Routen (kommen mit späteren Stories) → bei
signedOutnach/login?from=<pfad>. - Status wechselt zu
signedOut, etwa durch einen fehlgeschlagenen Refresh nach Krit. 9 → private Provider werden invalidiert (ref.invalidateüber einen zentralenonSignedOut-Hook), Navigation zu/login.
5.4 Screens und Zustände
Alle Formulare: Eingaben bleiben bei jedem Fehler erhalten (Querschnitt Zustände 3). Senden ist während loading deaktiviert; der Knopf zeigt den Ladezustand. Leer- und Datenzustände im Listensinn gibt es hier nicht; die Zustände sind idle / submitting / error(AuthFailure) / success.
RegisterScreen (01a, Krit. 1, 2, 3, 4, 10; QUE-04 Krit. 1)
- Felder: E-Mail, Passwort (mit Sichtbarkeitsschalter), Anzeigename, Checkbox „Ich bin mindestens 16 Jahre alt“, Checkbox „Ich habe die [Datenschutzerklärung] gelesen und stimme den [Nutzungsbedingungen] zu“. Die Links öffnen die Rechtstexte (Platzhalter-URLs aus Env,
url_launcher; F9). - Validierung: beim Verlassen des Feldes und beim Absenden. Das Passwort wird zusätzlich beim Tippen geprüft (Krit. 3: „wenn ich es eingebe“). Fehler stehen am Feld:
- Passwort zu kurz: „Mindestens 10 Zeichen – es fehlen noch n.“
- Passwort häufig: „Dieses Passwort ist sehr verbreitet und leicht zu erraten. Wähle ein anderes.“
- Anzeigename leer bzw. zu lang (
n/30-Zähler sichtbar). - Alter nicht bestätigt: „Shuffle ist erst ab 16 Jahren nutzbar.“ (Krit. 2: Grund sichtbar)
- Zustimmung fehlt: „Bitte stimme Datenschutzerklärung und Nutzungsbedingungen zu.“
- Absenden mit
locale = appLocaleProvider(aktive Oberflächensprache = Gerätesprache de/en, sonst en; KTO-11 Krit. 11, F6) undterms_version = AppConfig.termsVersion(F5). - Erfolg (jede 200-Antwort) →
/register/check-email. Text: „Wir haben dir eine E-Mail geschickt, falls die Adresse noch nicht registriert ist …“. Dieselbe Formulierung gilt auch für bereits registrierte Adressen (Krit. 4). weak_passwordvom Server wird ans Passwortfeld gemappt (praktisch nur die Mindestlänge, da kein HIBP).
CheckEmailScreen (Krit. 1, 6, 8): Die E-Mail-Adresse kommt per extra und steht nicht in der URL. Fehlt sie, etwa nach einem Reload im Web, zeigt der Screen ein E-Mail-Feld für das erneute Senden. Weiter: Hinweis, Gültigkeit „1 Stunde“ (Wert aus AUTH-CFG-07 als Konstante in core/config, mit Verweis auf die Doku), Aktion „E-Mail erneut senden“ (Modus Bestätigung) mit Rückmeldung und Sperre gegen Doppelklick; 429 → Drosselungshinweis. Im Modus Reset gibt es keine Resend-Aktion, sondern einen Link zurück zu /forgot-password.
LoginScreen (01b, Krit. 5, 6, 7)
- Felder E-Mail und Passwort, Links „Passwort vergessen?“ und „Konto erstellen“.
- Fehler:
invalidCredentials→ allgemeiner Fehler als Banner über dem Formular (nicht an einem Feld, um nichts zu verraten);emailNotConfirmed→ Info-Banner mit Aktion „Bestätigungs-E-Mail erneut senden“;rateLimited→ Drosselungshinweis. - Erfolg →
fromoder/. Autofill:AutofillGroupplusTextInput.finishAutofillContext()bei Erfolg.
ForgotPasswordScreen (01c, Krit. 8): Feld E-Mail, danach immer CheckEmailScreen (Modus Reset), unabhängig davon, ob die Adresse existiert.
EmailLinkScreen /auth/confirm (Krit. 1, 8)
- Liest
token_hashundtype. Ohne gültige Parameter → Fehlerzustand. - Zeigt einen Knopf „E-Mail-Adresse bestätigen“ bzw. „Weiter zum neuen Passwort“. Erst der Klick löst
verifyEmailLinkaus (Schutz vor Mail-Scannern, E3). - Bestätigung erfolgreich → Hinweis „E-Mail bestätigt“ →
/. Der Nutzer ist auf diesem Gerät angemeldet. Auf Mobile-Web zusätzlich: „Du kannst dich jetzt auch in der App anmelden.“ - Recovery erfolgreich → Status
passwordRecovery→/reset-password. linkInvalidOrExpired→ Fehlerzustand mit Aktion „Neuen Link anfordern“ (→/loginbzw./forgot-password).
ResetPasswordScreen (01c, Krit. 3, 8, 9): neues Passwort mit Live-Regeln aus PasswordPolicy, Server-Fehler weak_password/same_password am Feld. Erfolg → Hinweis „Passwort geändert. Andere Geräte werden abgemeldet; das kann bis zu einer Stunde dauern.“ → / (Formulierung passend zu F1). „Abbrechen“ → signOut(). Wird der Tab geschlossen oder die App neu geladen, stellt supabase_flutter die Sitzung als normale Anmeldung wieder her; das Ereignis passwordRecovery kommt dann nicht erneut. B0 prüft deshalb, ob das Recovery-JWT einen amr-Eintrag mit Methode recovery (bzw. otp) trägt:
- Wenn ja:
authStatusProviderleitetpasswordRecoveryauch aus demamr-Claim der wiederhergestellten Sitzung ab. Der Guard überlebt dann den Reload. - Wenn nein: Die Recovery-Sitzung bleibt nach einem Reload eine normale Anmeldung. Das ist vertretbar, weil sie den Besitz des Postfachs nachweist, wie ein Magic Link. Es ist als Risiko R12 festgehalten. Das alte Passwort bleibt dann gültig, bis der Nutzer es ändert (KTO-11).
5.5 Provider / Notifier
supabaseClientProvider(Fundament),authRepositoryProvider,commonPasswordListProvider(FutureProvider, lazy).authStatusProvider(Stream) mit Abbildung derAuthChangeEvents:signedIn/tokenRefreshed/userUpdated→signedIn,passwordRecovery→passwordRecovery,signedOut→signedOut.- Je Screen ein
@riverpod-Notifier (RegisterControllerusw.) mit ZustandAsyncValue<void>bzw. freezed-Formzustand. Keine Logik in Widgets.
5.6 Liste häufiger Passwörter (Krit. 3)
- Asset
app/assets/security/common-passwords.txt, ca. 10.000 Einträge (Größenordnung 100 KB), in Kleinschreibung, mit deutschen Einträgen (z. B.passwort1234,hallo12345). Nur Einträge mit mindestens 10 Zeichen sind relevant, kürzere fängt schon die Längenregel. - Quelle und Lizenz prüft
test-engineerin Aufgabe A1 und dokumentiert sie inapp/assets/security/README.md. Kandidat: SecLists (MIT); deutsche Ergänzung mit Quellenangabe. Keine Liste ohne klare Lizenz. - Vergleich ohne Groß-/Kleinschreibung, nach Trim. Die Liste wird lazy beim ersten Öffnen eines Passwortfeldes geladen; im Web fällt sie damit nicht beim Start ins Gewicht.
5.7 Neue Pakete (pub.dev geprüft 2026-11-02)
Stand der pub.dev-Suche (MCP pub_dev_search) am 2026-11-02:
| Paket | Version (pub.dev) | Zweck | Lizenz | Herausgeber | Hinweis | Issue |
|---|---|---|---|---|---|---|
supabase_flutter |
2.18.3 | Auth, Sitzung, Client | MIT | supabase.io (verifiziert) | 150/160 Punkte, sehr verbreitet | #31 |
flutter_riverpod / riverpod_annotation / riverpod_generator |
3.4.3 / 4.0.7 / 4.0.9 | State mit Codegen | MIT | dash-overflow.net | Versionen von Annotation und Generator gemeinsam pinnen | #31 |
go_router |
18.1.2 | Navigation, Web-URLs | BSD-3 (flutter/packages) | flutter.dev | Die Suche zeigt für die aktuelle Version keine Punkte/Lizenz an (vermutlich frisch veröffentlicht); Lizenz und Plattformen im PR auf der Paketseite bestätigen | #31 |
freezed / freezed_annotation |
4.0.3 / 3.1.0 | Modelle | MIT | dash-overflow.net | – | #31 |
json_serializable / json_annotation |
6.15.2 / 4.12.0 | JSON | BSD-3 | google.dev | – | #31 |
build_runner |
aktuell (dart.dev) | Codegen | BSD-3 | tools.dart.dev | im PR pinnen | #31 |
logging |
1.3.0 | Logger-Basis | BSD-3 | dart.dev | 160/160 | #31 |
flutter_localizations, intl |
SDK / passend zum SDK | l10n | BSD-3 | flutter.dev / dart.dev | – | #31 |
mocktail (dev) |
1.0.5 | Mocks | MIT | felangel.dev | 160/160 | #31 |
url_launcher |
6.3.3 | Rechtstexte öffnen | BSD-3 | flutter.dev | iOS, Android, Web | #1 (01a) |
Alle Pakete unterstützen iOS, Android und Web. flutter-dev pinnt die Versionen im Fundament-PR und vermerkt Abweichungen dort.
6. Plattform-Besonderheiten
| Thema | Web | iOS / Android |
|---|---|---|
| Sitzung (Krit. 5) | localStorage; bleibt nach Reload und Browser-Neustart erhalten. Kein Abgleich zwischen Tabs: ein zweiter Tab merkt die Abmeldung erst beim nächsten Refresh. |
SharedPreferences (App-Sandbox); bleibt nach Neustart erhalten. Bei Neuinstallation ist die Sitzung weg. |
| Mail-Links (E3, E4) | /auth/confirm braucht usePathUrlStrategy und eine SPA-Rewrite-Regel im Webserver (alle Pfade → index.html, Aufgabe D1). |
Links öffnen in R1 den Browser (Web-App). Universal/App Links kommen in einem Folge-Issue. |
| Autofill / Passwortmanager | AutofillHints.email, newPassword, password; Flutter Web unterstützt Passwortmanager nur eingeschränkt (bekannt, im PR vermerken) |
Autofill iOS/Android inkl. „Starkes Passwort vorschlagen“; Associated Domains erst mit Universal Links |
| Tastatur | Tab-Reihenfolge = Lesereihenfolge, Enter sendet ab, sichtbarer Fokus (QUE-06 Krit. 4) | TextInputType.emailAddress, TextInputAction.next/done |
| Offline | Login/Registrierung/Reset brauchen Verbindung → Hinweis statt technischem Fehler (Querschnitt Zustände 2). Kein Connectivity-Paket; der Fehler wird beim Aufruf erkannt. | gleich |
| Layout (Plattformen Krit. 2) | ab 600 dp zentrierte Karte, max. 480 dp breit | Vollbild, scrollbar (200 % Schrift) |
| Push, Kamera, Dateien | nicht betroffen | nicht betroffen |
7. Teststrategie
Werkzeuge: flutter_test + mocktail (Unit/Widget), pgTAP (supabase test db), Playwright in e2e/ mit zwei Projekten:
api: HTTP direkt gegen das lokale GoTrue, plus Helfer für den CLI-Mailfänger (Inbucket bzw. Mailpit, siehe B0). Liest Mails, Betreff, Sprache und Links.web: Browser gegen den lokalen Web-Build (flutter build web, ausgeliefert auf Port 3000).
Testnamen und Beschreibungen enthalten die Referenz („KTO-01 Kriterium n“). Drosselungs- und Gültigkeitstests referenzieren AUTH-CFG-xx und lesen die Zahlen aus einer gemeinsamen Konstantendatei e2e/support/auth-config.ts, die mit docs/auth-konfiguration.md übereinstimmen muss.
7.1 Abdeckungsmatrix (Entwurf)
| AK | Unit | Widget | Integration/E2E (Playwright) | RLS/pgTAP | Task |
|---|---|---|---|---|---|
| KTO-01 Krit. 1 | RegisterController ruft signUp mit allen Metadaten inkl. locale |
gültiges Formular → Navigation zu CheckEmail | api: Signup → Nutzer + Profil existieren (inkl. terms_version), Bestätigungsmail im Fänger; web: kompletter Ablauf bis „E-Mail bestätigt“ |
05_auth_registration: gültige Metadaten → Profil mit getrimmtem Namen, locale, terms_version, terms_accepted_at gesetzt |
B1, B2, A2, T1–T3 |
| Krit. 2 | Formular-Validierung ohne Altersbestätigung → Fehler | Checkbox nicht gesetzt → Fehlertext sichtbar, kein Aufruf | api: Signup ohne age_confirmed → kein Nutzer angelegt (500) |
bestehend 01_profiles (age_confirmation_required) |
B1, A2 |
| Krit. 3 | PasswordPolicy: 9/10 Zeichen, Liste (Groß/Klein, Trim), Codepoints; Mapping weak_password |
Live-Fehler beim Tippen am Feld (beide Gründe); Server-weak_password erscheint am Feld |
api: Signup mit 9 Zeichen → 422 weak_password (AUTH-CFG-05) |
– | B2, A1, A2, A4 |
| Krit. 4 | Controller wertet identities nicht aus (Test: Antwort mit leeren identities → gleicher Erfolgspfad) |
gleicher Bildschirm für neue und vorhandene Adresse | api: Signup mit bestätigter Adresse → 200, Anzahl auth.users unverändert, keine Mail; mit unbestätigter Adresse → 200, Passwort unverändert (Login mit altem Passwort nach Bestätigung möglich) |
– | B2, A2, T3 |
| Krit. 5 | authStatusProvider mappt Ereignisse |
App-Start mit gespeicherter Sitzung → angemeldeter Zustand | web: Login → Reload und neuer Browser-Kontext mit gleichem Storage → weiterhin angemeldet; api: sessions.timebox nicht gesetzt (Konfig-Check) |
– | A3, T3 |
| Krit. 6 | Mapping email_not_confirmed |
Hinweis + Resend-Aktion; Resend-Erfolg/429 | api: unbestätigtes Konto + richtiges Passwort → email_not_confirmed; falsches Passwort → invalid_credentials; Resend → zweite Mail |
– | A3, T3 |
| Krit. 7 | Mapping invalid_credentials/429 → genau ein Fehlertyp |
falsche E-Mail und falsches Passwort zeigen denselben Text (Golden-String-Vergleich) | api: n+1 Logins mit falschem Passwort von einer IP → 429 nach dem Burst aus AUTH-CFG-10; web: Drosselungshinweis sichtbar | – | B0, B2, A3, T3 |
| Krit. 8 | EmailLinkController: Parameter-Parsing, Mapping otp_expired |
Link-Screen: Knopf nötig; Fehlerzustand bei ungültigem Link | api: Reset anfordern → Mail → verifyOTP ok → zweites Einlösen 403; abgelaufener Link: Helfer e2e/support/db.ts verbindet sich direkt mit dem lokalen Postgres (Port 54322) und setzt den maßgeblichen Zeitstempel um otp_expiry + 1 s zurück. Welche Spalte das ist, klärt B0. Wert aus AUTH-CFG-07.; Reset für unbekannte Adresse → 200, keine Mail |
– | B2, A4, T3 |
| Krit. 9 | onSignedOut-Hook invalidiert private Provider |
fehlgeschlagener Refresh → Navigation zu /login |
api: Sitzungen A und B; Reset über A → Refresh-Token von B ungültig (refresh_token_not_found/session_not_found); web: zweiter Browser-Kontext ist nach dem nächsten Refresh abgemeldet (Test erzwingt den Refresh; die Wartezeit bis 60 min wird bewusst nicht getestet, F1) |
– | A4, T3 |
| Krit. 10 | DisplayNameRule: leer, nur Leerzeichen, 30/31 Codepoints, Emoji |
Fehler am Feld, Zähler | api: Signup mit display_name = 31 Zeichen → kein Nutzer |
05_auth_registration: fehlend/leer/Leerzeichen/31 → display_name_invalid; Trim; neuer Check bei Update ' ' → 23514 |
B1, A1, A2 |
| Krit. 11 | – | – | api: Signup mit locale=en → englischer Betreff und Text; de → deutsch; Profilsprache per Update auf en ändern → Reset-Mail englisch; nach Reset und Login keine weitere Mail (Benachrichtigungen aus) |
05_auth_registration: Update profiles.locale → auth.users.raw_user_meta_data.locale folgt; fremdes Profil nicht betroffen; Signup ohne locale → Profil 'en' (F6); Signup mit ungültigem locale → 'en' |
B1, B2, T3 |
| QUE-04 Krit. 1 (+ Nachweis F5) | Validierung Zustimmung; RegisterController sendet terms_version aus AppConfig |
Links zu Datenschutzerklärung/Nutzungsbedingungen vorhanden und bedienbar; ohne Haken kein Absenden | – | bestehend (terms_acceptance_required); neu 05_auth_registration: fehlende/leere/zu lange terms_version → terms_version_required; authenticated kann terms_version nicht ändern (42501) |
B1, A2 |
| Sicherheit Krit. 2 | Router-Redirect-Logik | private Route abgemeldet → /login |
– | bestehend (anon ohne Zugriff) |
A1 |
| QUE-05 Krit. 2, 4 | – | Alle Screens in de und en ohne fehlende Schlüssel; Test „fehlende Übersetzung → Englisch“ im Fundament | – | – | F5, A2–A4 |
| QUE-06 Krit. 1–4 | – | meetsGuideline(androidTapTargetGuideline, labeledTapTargetGuideline, textContrastGuideline) je Screen; textScaler 2.0 ohne Overflow; Fehler als Live-Region |
web: Ablauf nur per Tastatur | – | U1, T2 |
| Zustände Krit. 2, 3 | Mapping Netzfehler | Offline-Hinweis; Eingaben bleiben nach Fehler erhalten | – | – | A1–A4, T2 |
Nicht automatisiert testbar, manuelle Prüfung im PR: echter Versand über den Staging-SMTP (sobald F7 entschieden ist), HIBP im Prod-Env aus (AUTH-CFG-21), IP-Erkennung hinter dem Produktions-Gateway (AUTH-CFG-13), Sperre /otp am Gateway (AUTH-CFG-20). Diese Punkte stehen als Checkliste in docs/auth-konfiguration.md und sind vor dem Release abzuhaken.
8. Aufgaben
8.1 Fundament-Issue #31
Angelegt am 2026-11-02: #31 „chore: App-Fundament – Flutter-Gerüst und Supabase-Konfiguration“. Milestone R1, Etappe R1.1, Labels type/tech, epic/querschnitt, prio/must, size/l. #1 hängt in Forgejo davon ab (blockierende Abhängigkeit); danach braucht es auch #3, #4 und #28. Branch: chore/31-app-fundament. Die Tasks F1–F8 stehen dort als Checkliste.
| # | Agent | Aufgabe | abhängig von |
|---|---|---|---|
| F1 | devops | Repo-Hygiene: supabase init → supabase/config.toml committen (zunächst ohne Auth-Anpassungen); .gitignore um app/env/*.json (Ausnahme !app/env/*.example.json), supabase/.temp/, supabase/.branches/ ergänzen. Hinweis: env/*.json greift nur im Repo-Root. CI-Pfade prüfen. |
– |
| F2 | flutter-dev | flutter create app (Plattformen ios, android, web; Platzhalter laut 5.1: App-Name „Shuffle“, ID work.cshomelabs.shuffle, zentral änderbar); pubspec.yaml mit den Paketen aus 5.7 (gepinnt); analysis_options.yaml (flutter_lints, strikt); Ordnerstruktur core/, features/ |
F1 |
| F3 | flutter-dev | core/config: AppConfig aus --dart-define-from-file (SUPABASE_URL, SUPABASE_ANON_KEY, SITE_URL, LEGAL_PRIVACY_URL, LEGAL_TERMS_URL, TERMS_VERSION; Platzhalterwerte laut 5.1); app/env/dev.example.json, app/env/ci.example.json; core/logging (Wrapper um package:logging, kein print, keine personenbezogenen Daten im Log); globale Fehlerbehandlung; supabaseClientProvider; Result-Typ |
F2 |
| F4 | flutter-dev | core/routing: go_router-Provider, usePathUrlStrategy, Redirect-Hook mit austauschbarer Auth-Quelle, Startseite / als Platzhalter, 404-Seite |
F2 |
| F5 | flutter-dev | core/l10n: gen-l10n, app_de.arb/app_en.arb, englischer Fallback (QUE-05 Krit. 4), appLocaleProvider (Gerätesprache de/en, sonst en; manuelle Wahl folgt in KTO-11) |
F2 |
| F6 | ui-designer | Theme-Basis Material 3: Farbschema mit geprüften AA-Kontrasten (hell; dunkel optional), Typografie, Abstände, Breakpoints 600/1024 dp, Fokus-Stil Web | F2 |
| F7 | test-engineer | Test-Grundlagen: test/helpers/pump_app.dart (l10n, ProviderScope-Overrides), Beispiel-Unit- und Widget-Test, Fallback-Test l10n |
F3–F5 |
| F8 | devops | CI: Flutter-Job läuft mit app/ (env/ci.example.json); supabase-tests läuft mit committeter config.toml |
F1, F2 |
8.2 Issue #1 – Backend-PR (vor 01a)
| # | Agent | Aufgabe | abhängig von |
|---|---|---|---|
| B0 | backend-dev | Spike (max. ½ Tag) gegen die lokale CLI. Zu klären: (a) Wertet die Betreffzeile .Data.locale aus? (b) Exakte type-Werte für token_hash (email/signup, recovery). (c) Welcher Limiter greift beim Passwort-Login, mit welchen Zahlen (AUTH-CFG-10)? (d) Ist email_sent instanzweit? (e) Welcher Mailfänger ist im Einsatz (Inbucket oder Mailpit, API)? (f) Wie erscheinen Trigger-Fehler bei signUp (500, Text)? (g) Welche reasons liefert weak_password? (h) Stimmen die GoTrue-Version der CLI und das geplante Docker-Image überein? (i) Welche Spalte prüft GoTrue für den Linkablauf: auth.users.confirmation_sent_at/recovery_sent_at oder auth.one_time_tokens.created_at? Das braucht der E2E-Ablauftest. (j) Trägt das JWT einer Recovery-Sitzung einen amr-Eintrag, der sie von einer normalen Anmeldung unterscheidet (5.4, R12)? Ergebnis als Abschnitt „Verifiziert“ in docs/auth-konfiguration.md. |
F1 |
| B1 | backend-dev | Migration <ts>_auth_registration_rules.sql (3.2): neues handle_new_user (Anzeigename streng, terms_version Pflicht, locale-Fallback 'en'), Spalte terms_version mit Check, Standard locale = 'en', Check profiles_display_name_not_blank, Sync-Trigger profiles.locale → auth.users. pgTAP: 01_profiles.test.sql anpassen (Erwartung „Fallback E-Mail-Präfix“ und „kürzen auf 30“ wird zu „wird abgelehnt“); Erwartung „ohne Sprache → 'de'“ wird zu 'en'. Fixtures in 01_profiles, 02_catalog, 03_collection und 04_plays um display_name (wo fehlend) und terms_version ergänzen (R9). Neu 05_auth_registration.test.sql (Krit. 10, 11, Trim, terms_version Pflicht und nicht änderbar, locale-Fallback 'en', Sync nur für eigenes Konto). Doku datenmodell-r1.md, supabase/README.md. |
F1 (nur für supabase start), sonst keine |
| B2 | backend-dev | config.toml Auth-Abschnitt gemäß 4.1 (alle Werte explizit); Mailvorlagen supabase/templates/confirmation.html, recovery.html (de/en, Links nach E3, barrierearmes HTML, nur Text und Links); Benachrichtigungen aus |
B0 |
| B3 | backend-dev | docs/auth-konfiguration.md: Tabelle 4.1 mit verifizierten Werten (ID, CLI-Schlüssel, Env, Wert lokal, Wert Staging/Prod, Quelle, Test-Referenz), Env-Block für Self-Hosting ohne Secrets, Release-Checkliste (manuelle Prüfungen aus 7.1) |
B0, B2 |
| D1 | devops | Betriebsanteil dokumentieren und vorbereiten (kein Eingriff in Server): Auslieferung der Mailvorlagen per URL für GoTrue; GOTRUE_RATE_LIMIT_HEADER + Proxy-Konfiguration (Header überschreiben, nicht anhängen); Gateway-Sperre /auth/v1/otp und /auth/v1/magiclink; SPA-Rewrite für die Web-App; GOTRUE_PASSWORD_HIBP_ENABLED=false und GOTRUE_MAILER_OTP_EXP=3600 explizit; SMTP, sobald Anbieter und Absender-Domain entschieden sind (F7, später). Blockiert das Release, nicht den Merge von #1 (Hosting-Setup nach ADR-0002 steht noch aus). |
B3 |
8.3 Issue #1 – UI und App (drei PRs)
| # | Agent | Aufgabe | PR | abhängig von |
|---|---|---|---|---|
| U1 | ui-designer | Komponenten in core/widgets/: AppTextField (Label, Hilfetext, errorText als Live-Region), AppPasswordField (Sichtbarkeitsschalter mit Semantics-Label, Regelhinweis), AppCheckboxField (Fehlerzustand, Label mit eingebetteten Links, ganzes Label als Touch-Ziel ≥ 48 dp), AppPrimaryButton (Ladezustand), AppFormScaffold (zentrierte Karte ab 600 dp, scrollbar), AppBanner (Info/Fehler). Layout-Skizzen der sechs Auth-Screens, Textvorschläge de/en für alle Meldungen aus 4.4 und 5.4. |
01a | F6 |
| A1 | flutter-dev | Auth-Domäne und -Daten: Modelle, AuthFailure, Validatoren (DisplayNameRule, PasswordPolicy, EmailFormat), AuthRepository + Supabase-Implementierung mit Mapping 4.4, authStatusProvider, Router-Anbindung (Redirect-Regeln 5.3, onSignedOut-Hook), Asset „häufige Passwörter“ (Lizenzprüfung gemeinsam mit test-engineer) |
01a | F2–F5 |
| A2 | flutter-dev | RegisterScreen, CheckEmailScreen (inkl. Resend), EmailLinkScreen (Typ Bestätigung), Rechtstext-Links (url_launcher), ARB-Texte |
01a | A1, U1, B1, B2 |
| A3 | flutter-dev | LoginScreen (allgemeiner Fehler, Hinweis bei unbestätigtem Konto + Resend, Drosselung, Offline), Autofill, Weiterleitung from |
01b | A1, U1 (parallel zu A2 möglich) |
| A4 | flutter-dev | ForgotPasswordScreen, EmailLinkScreen (Typ Recovery), ResetPasswordScreen mit Recovery-Guard und Abbruch → signOut, Erfolgsmeldung Krit. 9 |
01c | A2 (EmailLinkScreen), A1, U1 |
| T1 | test-engineer | Unit-Tests: Validatoren, Fehler-Mapping (jede Zeile aus 4.4), Controller (mocktail), authStatusProvider, Router-Redirects |
je PR | A1–A4 (begleitend) |
| T2 | test-engineer | Widget-Tests je Screen: Feldfehler, identische Meldung bei Krit. 7, Zustände, a11y-Guidelines, textScaler 2.0, de/en |
je PR | U1, A2–A4 |
| T3 | test-engineer | Playwright e2e/: Projekte api (GoTrue + Mailfänger-Helfer), web und throttle (Drosselungstests, läuft zuletzt, R13); Tests je Kriterium nach 7.1; e2e/support/auth-config.ts mit Bezug auf AUTH-CFG-IDs; e2e/support/db.ts für Zeitstempel-Manipulation (nur lokal/CI). Jeder Test nutzt eigene, zufällige E-Mail-Adressen. Der Teil api kann schon nach B2 starten, unabhängig von der App. |
api: 01a; web: je PR | B2 (api), A2–A4 (web) |
| D2 | devops | CI-Job e2e auf dem Runner docker-host: supabase start (mit Mailfänger), flutter build web --dart-define-from-file=env/ci.example.json, statisch auf Port 3000 ausliefern, npx playwright test; Artefakte (Report, Traces) bei Fehlern |
01a | F8, T3 |
8.4 Reihenfolge und Parallelität
Phase 0 F1 (devops, klein; Issue #31)
Phase 1 parallel: F2→F3/F4/F5 (flutter-dev) | F6 (ui-designer) | B0→B2→B3, B1 (backend-dev)
Phase 2 parallel: F7, F8 | U1 | A1 | T3-api (nach B2) | D1 (Doku, nach B3)
Phase 3 PR 01a: A2 + T1/T2/T3-web + D2
Phase 4 parallel: PR 01b: A3 (+Tests) | PR 01c: A4 (+Tests, braucht A2)
Der Backend-PR (B1–B3) wird als erster PR zu #1 gemergt. Die Merge-Reihenfolge ist Fundament (#31) → Backend → 01a → 01b/01c. Alle PRs: Refs #1, der letzte Closes #1. Branches: feature/1-auth-backend, feature/1-auth-registrierung, feature/1-auth-anmeldung, feature/1-auth-passwort-reset. Der aktuell ausgecheckte Branch feature/1-auth-registrierung-anmeldung trägt die Spec.
9. Risiken & offene Fragen
9.1 Risiken
| # | Risiko | Auswirkung | Gegenmaßnahme |
|---|---|---|---|
| R1 | Supabase-Standardwerte unterscheiden sich je nach Quelle (Binary, CLI, Docker) und Version | Tests sind grün, Produktion verhält sich anders (Krit. 7, 8) | Alle Werte pinnen (E8), B0 verifiziert gegen das geplante Image, Release-Checkliste |
| R2 | Ohne GOTRUE_RATE_LIMIT_HEADER und saubere Proxy-Konfiguration sieht GoTrue nur die Gateway-IP, oder Clients fälschen X-Forwarded-For |
Ein Angreifer drosselt alle Nutzer, oder die Drosselung lässt sich umgehen (Krit. 7) | D1; manuelle Prüfung vor dem Release |
| R3 | email_sent ist wahrscheinlich instanzweit (30 Mails/h) |
Ab der 31. Registrierung oder dem 31. Reset pro Stunde gehen keine Mails mehr raus; Angreifer können das billig auslösen | B0 prüft das; 30/h reicht laut Chris zum Start (F4); Monitoring auf 429 over_email_send_rate_limit, Wert bei Bedarf erhöhen |
| R4 | GoTrue hat keinen Schalter gegen Magic Link/OTP (/otp) |
Mails außerhalb von Krit. 11, Konten ohne Passwort | Gateway-Sperre (AUTH-CFG-20, D1). Lokal nicht absicherbar, deshalb nur dokumentiert |
| R5 | Self-hosted GoTrue lädt Mailvorlagen per URL; bei Fehlern still auf die englische Standardvorlage | Krit. 11 verletzt, ohne dass es auffällt | Vorlagen-URL im Health-Check, Release-Checkliste: Testmail de/en auf Staging |
| R6 | Enumeration auf API-Ebene: Signup mit unbestätigter Adresse gibt den echten Nutzer zurück, mit bestätigter Adresse einen erfundenen | Mit Skripten lässt sich die Existenz unbestätigter Konten erkennen | Supabase-Standardverhalten; die App verrät nichts (Krit. 4 bezieht sich auf das Erleben in der App). Bei Bedarf später Captcha oder eigener Proxy. |
| R7 | Sitzung im Web in localStorage, auf iOS in unverschlüsselten UserDefaults |
Bei XSS bzw. Gerätezugriff ist das Refresh-Token lesbar | CSP für den Web-Build (devops), kein Fremd-JS; Secure Storage auf Mobile als möglicher Folgeschritt |
| R8 | Krit. 9: Access-JWTs bleiben bis zu 3600 s gültig | Ein anderes Gerät kann bis zu 1 h weiter auf Daten zugreifen | Bewusst akzeptiertes Restrisiko (Chris, F1, 2026-11-02); jwt_expiry bleibt 3600 s. App reagiert sofort auf fehlgeschlagenen Refresh; Texte versprechen nicht „sofort“. |
| R9 | Der neue Trigger lehnt Registrierungen ohne Anzeigenamen oder ohne terms_version ab |
Bestehende pgTAP-Tests legen Nutzer ohne diese Angaben an und brechen: ohne display_name 01_profiles (ben), 02_catalog und 03_collection (anna, ben); ohne terms_version alle vier Test-Dateien 01–04. seed.sql legt keine Nutzer an. |
B1 ergänzt die Test-Fixtures (Tests sind keine Migrationen, Änderung zulässig) |
| R10 | user_metadata behält display_name aus der Registrierung, auch wenn KTO-02 ihn später ändert |
Veralteter Wert im JWT, keine fachliche Wirkung | Maßgeblich ist immer profiles; Hinweis in KTO-02 |
| R11 | Rechtliche Prüfung (Mindestalter per Selbstbestätigung) steht aus, Datenschutzerklärung und Nutzungsbedingungen existieren noch nicht (Klärung 21/47, F9) | Release-Blocker | Bleibt bei Chris; bis dahin Platzhalter-URLs und TERMS_VERSION-Platzhalter, alles per Env änderbar |
| R12 | Abgebrochene Recovery (Tab geschlossen) bleibt nach einem Reload als normale Anmeldung bestehen, falls der amr-Claim keine Unterscheidung erlaubt (B0) |
Nutzer ist angemeldet, ohne ein neues Passwort gesetzt zu haben | Vertretbar (Postfachbesitz nachgewiesen); bei vorhandenem amr wird der Guard daraus abgeleitet (5.4) |
| R13 | Drosselungstests leeren den gemeinsamen IP-Bucket des CI-Runners für bis zu 5 min | Nachfolgende Login-Tests scheitern zufällig | Drosselungstests (AUTH-CFG-10/11) in einem eigenen Playwright-Projekt, das zuletzt läuft, oder Neustart des Auth-Containers davor. Keine gefälschten X-Forwarded-For-Header zur Isolation. |
| R14 | Ohne HIBP prüft nur der Client die Liste häufiger Passwörter (F2) | Über die rohe API lassen sich häufige Passwörter mit mindestens 10 Zeichen setzen | Bewusst akzeptiert; der Server erzwingt die Mindestlänge 10, die Drosselung (AUTH-CFG-10) bremst Rateversuche |
| R15 | Bundle-/Application-ID ist ein Platzhalter (F8) | Nach der ersten Store-Veröffentlichung nicht mehr änderbar | Vor dem Store-Release festlegen (Release-Checkliste) |
| R16 | SMTP-Anbieter und Absender-Domain sind offen (F7) | Ohne SMTP keine Mails auf Staging/Prod | Lokal Mailpit/Inbucket; D1 ist Release-Blocker, kein Merge-Blocker |
9.2 Fragen an Chris (alle entschieden am 2026-11-02)
| # | Frage | Status | Entscheidung | Wirkung in der Spec |
|---|---|---|---|---|
| F1 | Reicht es, wenn andere Geräte nach einem Reset spätestens nach Ablauf des Access-Tokens (bis zu 60 min) abgemeldet sind? | entschieden | Ja. jwt_expiry bleibt 3600 s; bewusstes Restrisiko. |
AUTH-CFG-14, 4.3, 5.4 (Erfolgstext), 7.1 Krit. 9, R8. A4 ist nicht mehr blockiert. |
| F2 | Have I Been Pwned aktivieren? | entschieden | Nein. Nur die im Client gebündelte Liste häufiger Passwörter, dazu die Server-Mindestlänge 10. | E7, AUTH-CFG-21 (false explizit), 4.4, 5.4, R14 |
| F3 | Gültigkeit von Bestätigungs- und Reset-Link? | entschieden | 1 Stunde für beide | AUTH-CFG-07, D1 |
| F4 | Reichen 30 Mails pro Stunde zum Start? | entschieden | Ja | AUTH-CFG-09, R3 |
| F5 | Version der zugestimmten Rechtstexte speichern? | entschieden | Ja: Spalte terms_version, Zeitstempel über terms_accepted_at. Der Client übergibt die Version aus einer konfigurierbaren Konstante (TERMS_VERSION). |
3.1, 3.2, 4.2, 5.1, 5.4, 7.1, B1, F3, R9 |
| F6 | Profilsprache ohne Angabe? | entschieden | Gerätesprache, sonst Englisch (QUE-05). Der Client sendet immer die aktive Sprache; der Server fällt auf 'en' zurück (Standard und Trigger, neue Migration). |
3.1, 3.2, Mailvorlagen 4.1 (Else-Zweig Englisch), 5.4, 7.1, B1 |
| F7 | SMTP-Anbieter und Absender-Domain? | vertagt (Entscheidung später) | Lokal Mailpit/Inbucket; D1 bleibt Release-Blocker, kein Merge-Blocker | AUTH-CFG-22, D1, R16 |
| F8 | App-Identität? | entschieden (Platzhalter) | App-Name „Shuffle“, Bundle-ID work.cshomelabs.shuffle, Web-Domain shuffle.cshomelabs.work; leicht änderbar halten, Bundle-ID vor dem Store-Release festlegen |
5.1, AUTH-CFG-01, F2, R15 |
| F9 | Wo liegen die Rechtstexte? | entschieden | Sie existieren noch nicht; konfigurierbare Platzhalter-URLs, Chris liefert vor dem Release | 5.1, 5.4, R11 |
| F10 | Fundament als eigenes Issue? | entschieden, umgesetzt | Ja: Issue #31 angelegt, #1 hängt davon ab | E1, 5.1, 8.1, 8.4 |
Es gibt keine offenen fachlichen Fragen mehr, die die Umsetzung von #1 blockieren. F7 blockiert nur das Release.
9.3 Ausstehende Prozessschritte
- Diese Spec ist noch nicht committet. Nach dem Commit auf
feature/1-auth-registrierung-anmeldung: Kommentar mit Link zur Spec in Issue #1 und Labelstatus/spec-reviewsetzen. - Freigabe der Spec durch Chris, danach Umsetzung beginnend mit #31.
9.4 Hinweise für KTO-11 (#28), nicht Teil von #1
double_confirm_changes(AUTH-CFG-23) muss für N2 vermutlichfalsesein. Die Info-Mail an die alte Adresse kommt dann über die Benachrichtigungemail_changed(AUTH-CFG-19).- Passwort ändern mit aktuellem Passwort (N13): GoTrue-Einstellung
secure_password_changebzw.UpdatePasswordRequireCurrentPasswordprüfen; Recovery-Sitzungen sind davon ausgenommen (passt zu #1). - Der Sync-Trigger aus B1 deckt die Mailsprache nach einem Sprachwechsel in KTO-11 bereits ab.
- Abmelden-UI:
AuthRepository.signOut()existiert, das UI liefert KTO-11. - Erneute Zustimmung bei geänderten Rechtstexten (Vergleich
profiles.terms_versionmitTERMS_VERSION) ist nicht Teil von R1. Falls gewünscht, als eigene Story erfassen; dafür bräuchte es dann eine RPC zum Aktualisieren vonterms_version.