feat(auth): configure Supabase Auth and add de/en mail templates
Adds AUTH-CFG settings, confirmation/recovery templates and docs/auth-konfiguration.md (KTO-01 criteria 1, 3, 4, 7, 8, 11). Refs #1 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
395816609f
commit
59a47c5a5d
5 changed files with 364 additions and 40 deletions
173
docs/auth-konfiguration.md
Normal file
173
docs/auth-konfiguration.md
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
# 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 |
|
||||||
|
|
||||||
|
Ohne eigene ID: `auth.email.secure_password_change = false` (Standard, ausdrücklich gesetzt). Recovery-Sitzungen sind davon ausgenommen; ob für die Passwortänderung das aktuelle Passwort verlangt wird, entscheidet KTO-11 (N13).
|
||||||
|
|
||||||
|
## 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-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 (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 (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. `index .Data "locale"` ist sicher, wenn der Schlüssel fehlt. 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.
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
|---|---|
|
|---|---|
|
||||||
| `migrations/` | Schema, Trigger und RLS-Policies für R1 (17 Tabellen, 2 Views). Nur additiv ändern, nie bestehende Dateien editieren. |
|
| `migrations/` | Schema, Trigger und RLS-Policies für R1 (17 Tabellen, 2 Views). Nur additiv ändern, nie bestehende Dateien editieren. |
|
||||||
| `tests/database/` | pgTAP-Tests: Struktur-Regeln und Zugriffsrechte je Bereich. |
|
| `tests/database/` | pgTAP-Tests: Struktur-Regeln und Zugriffsrechte je Bereich. |
|
||||||
|
| `templates/` | Auth-Mailvorlagen (HTML, Go-Template) für Bestätigung und Passwort-Reset. |
|
||||||
| `seed.sql` | Beispieldaten nur für die lokale Entwicklung. |
|
| `seed.sql` | Beispieldaten nur für die lokale Entwicklung. |
|
||||||
|
|
||||||
## Lokal starten und testen
|
## Lokal starten und testen
|
||||||
|
|
@ -18,6 +19,8 @@ supabase test db # alle pgTAP-Tests ausführen
|
||||||
- Der Trigger `handle_new_user` erzwingt Altersbestätigung, Zustimmung, `terms_version` und Anzeigename (1–30 Zeichen). Fehlertexte: siehe `docs/datenmodell-r1.md`. Clients senden diese Werte als `user_metadata` bei `signUp`.
|
- Der Trigger `handle_new_user` erzwingt Altersbestätigung, Zustimmung, `terms_version` und Anzeigename (1–30 Zeichen). Fehlertexte: siehe `docs/datenmodell-r1.md`. Clients senden diese Werte als `user_metadata` bei `signUp`.
|
||||||
- pgTAP-Fixtures, die `auth.users` direkt füllen, brauchen daher `age_confirmed`, `terms_accepted`, `terms_version` und `display_name` in `raw_user_meta_data`.
|
- pgTAP-Fixtures, die `auth.users` direkt füllen, brauchen daher `age_confirmed`, `terms_accepted`, `terms_version` und `display_name` in `raw_user_meta_data`.
|
||||||
- Ergebnisse der GoTrue-Prüfung: `docs/specs/1-auth-spike-ergebnisse.md`.
|
- Ergebnisse der GoTrue-Prüfung: `docs/specs/1-auth-spike-ergebnisse.md`.
|
||||||
|
- Auth-Einstellungen (`AUTH-CFG-nn` in `config.toml`), Env-Block für Staging/Produktion, Release-Checkliste und bekannte Grenzen: [`docs/auth-konfiguration.md`](../docs/auth-konfiguration.md).
|
||||||
|
- Mailvorlagen (Bestätigung, Passwort-Reset; de/en nach `user_metadata.locale`): `templates/`. Lokal landen Mails in Mailpit (`http://127.0.0.1:54324`).
|
||||||
|
|
||||||
## Regeln
|
## Regeln
|
||||||
- Jede Tabelle hat `id` (uuid, PK), `created_at`, `updated_at` und Row Level Security.
|
- Jede Tabelle hat `id` (uuid, PK), `created_at`, `updated_at` und Row Level Security.
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,9 @@ openai_api_key = "env(OPENAI_API_KEY)"
|
||||||
|
|
||||||
# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
|
# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
|
||||||
# are monitored, and you can view the emails that would have been sent from the web interface.
|
# are monitored, and you can view the emails that would have been sent from the web interface.
|
||||||
|
# AUTH-CFG-22 (wirkt lokal): Mailfänger ist Mailpit (Container heißt historisch supabase_inbucket_*).
|
||||||
|
# Web-Oberfläche und API: http://127.0.0.1:54324 (API: /api/v1/messages, /api/v1/message/{id}).
|
||||||
|
# Staging/Prod: SMTP über GOTRUE_SMTP_* (Anbieter offen, siehe docs/auth-konfiguration.md).
|
||||||
[local_smtp]
|
[local_smtp]
|
||||||
enabled = true
|
enabled = true
|
||||||
# Port to use for the email testing server web interface.
|
# Port to use for the email testing server web interface.
|
||||||
|
|
@ -155,36 +158,44 @@ max_indexes = 5
|
||||||
|
|
||||||
[auth]
|
[auth]
|
||||||
enabled = true
|
enabled = true
|
||||||
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
|
# AUTH-CFG-01 (wirkt lokal): Site-URL, Basis der Mail-Links ({{ .SiteURL }}).
|
||||||
# in emails.
|
# Staging/Prod: GOTRUE_SITE_URL (Prod-Platzhalter https://shuffle.cshomelabs.work).
|
||||||
site_url = "http://localhost:3000"
|
site_url = "http://localhost:3000"
|
||||||
# The public URL that Auth serves on. Defaults to the API external URL with `/auth/v1` appended.
|
# The public URL that Auth serves on. Defaults to the API external URL with `/auth/v1` appended.
|
||||||
# external_url = ""
|
# external_url = ""
|
||||||
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
|
# AUTH-CFG-02 (wirkt lokal): Redirect-Allow-List, nur die eigene Web-App-Seite für Mail-Links.
|
||||||
additional_redirect_urls = ["http://localhost:3000"]
|
# Staging/Prod: GOTRUE_URI_ALLOW_LIST.
|
||||||
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
|
additional_redirect_urls = ["http://localhost:3000/auth/confirm"]
|
||||||
|
# AUTH-CFG-14 (wirkt lokal): JWT-Laufzeit 3600 s. Bestimmt die Verzögerung bei KTO-01 Kriterium 9
|
||||||
|
# (andere Geräte spätestens nach 60 min abgemeldet, Restrisiko R8). Staging/Prod: GOTRUE_JWT_EXP.
|
||||||
jwt_expiry = 3600
|
jwt_expiry = 3600
|
||||||
# JWT issuer URL. If not set, defaults to auth.external_url.
|
# JWT issuer URL. If not set, defaults to auth.external_url.
|
||||||
# jwt_issuer = ""
|
# jwt_issuer = ""
|
||||||
# Path to JWT signing key. DO NOT commit your signing keys file to git.
|
# Path to JWT signing key. DO NOT commit your signing keys file to git.
|
||||||
# signing_keys_path = "./signing_keys.json"
|
# signing_keys_path = "./signing_keys.json"
|
||||||
# If disabled, the refresh token will never expire.
|
# AUTH-CFG-15 (wirkt lokal): Refresh-Token-Rotation an, Wiederverwendungsfenster 10 s.
|
||||||
|
# Staging/Prod: GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED, GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL.
|
||||||
enable_refresh_token_rotation = true
|
enable_refresh_token_rotation = true
|
||||||
# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.
|
|
||||||
# Requires enable_refresh_token_rotation = true.
|
|
||||||
refresh_token_reuse_interval = 10
|
refresh_token_reuse_interval = 10
|
||||||
# Allow/disallow new user signups to your project. Existing users can still sign in.
|
# AUTH-CFG-03 (wirkt lokal): Registrierung erlaubt. Staging/Prod: GOTRUE_DISABLE_SIGNUP=false.
|
||||||
enable_signup = true
|
enable_signup = true
|
||||||
# Allow/disallow anonymous sign-ins to your project.
|
# Allow/disallow anonymous sign-ins to your project.
|
||||||
enable_anonymous_sign_ins = false
|
enable_anonymous_sign_ins = false
|
||||||
# Allow/disallow testing manual linking of accounts
|
# Allow/disallow testing manual linking of accounts
|
||||||
enable_manual_linking = false
|
enable_manual_linking = false
|
||||||
# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more.
|
# AUTH-CFG-05 (wirkt lokal): Passwort-Mindestlänge 10 (O10). Staging/Prod: GOTRUE_PASSWORD_MIN_LENGTH.
|
||||||
minimum_password_length = 6
|
minimum_password_length = 10
|
||||||
# Passwords that do not meet the following requirements will be rejected as weak. Supported values
|
# AUTH-CFG-06 (wirkt lokal): keine Zeichenklassen gefordert. Staging/Prod: GOTRUE_PASSWORD_REQUIRED_CHARACTERS (leer).
|
||||||
# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols`
|
|
||||||
password_requirements = ""
|
password_requirements = ""
|
||||||
|
|
||||||
|
# AUTH-CFG-21 (nur Env / nur Self-Hosting): Leaked-Password-Prüfung (HIBP) ist aus (Entscheidung F2).
|
||||||
|
# Die CLI kennt dafür keinen Schlüssel und führt keine Prüfung aus. Staging/Prod muss
|
||||||
|
# GOTRUE_PASSWORD_HIBP_ENABLED=false explizit setzen.
|
||||||
|
|
||||||
|
# AUTH-CFG-20 (nur Self-Hosting / Gateway): Magic Link und OTP-Login (/auth/v1/otp, /auth/v1/magiclink)
|
||||||
|
# lassen sich in GoTrue nicht abschalten (Spike A7). Staging/Prod: am Gateway sperren.
|
||||||
|
# Lokal bewusst nicht gesperrt.
|
||||||
|
|
||||||
# Configure passkey sign-ins.
|
# Configure passkey sign-ins.
|
||||||
# [auth.passkey]
|
# [auth.passkey]
|
||||||
# enabled = false
|
# enabled = false
|
||||||
|
|
@ -195,18 +206,27 @@ password_requirements = ""
|
||||||
# rp_id = "localhost"
|
# rp_id = "localhost"
|
||||||
# rp_origins = ["http://127.0.0.1:3000"]
|
# rp_origins = ["http://127.0.0.1:3000"]
|
||||||
|
|
||||||
|
# Rate Limits. ACHTUNG: Die lokale CLI kann GOTRUE_RATE_LIMIT_HEADER nicht setzen (AUTH-CFG-13).
|
||||||
|
# Ohne diesen Header gibt es keine IP-Drosselung; lokal und in der CI-CLI wird nichts gedrosselt.
|
||||||
|
# Die Werte unten sind daher Dokumentation der Zielwerte. Wirksam sind sie nur im Self-Hosting mit
|
||||||
|
# GOTRUE_RATE_LIMIT_HEADER und Requests, die den Header tragen (Spike B0, A2).
|
||||||
[auth.rate_limit]
|
[auth.rate_limit]
|
||||||
# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled.
|
# AUTH-CFG-09 (nur Env, lokal OHNE Wirkung): Mails pro Stunde, instanzweit. Die CLI setzt intern
|
||||||
email_sent = 2
|
# GOTRUE_RATE_LIMIT_EMAIL_SENT=360000 (Spike A3). Lokal/CI-Zielwert 100, Staging/Prod: 30 über
|
||||||
|
# GOTRUE_RATE_LIMIT_EMAIL_SENT=30.
|
||||||
|
email_sent = 100
|
||||||
# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled.
|
# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled.
|
||||||
sms_sent = 30
|
sms_sent = 30
|
||||||
# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true.
|
# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true.
|
||||||
anonymous_users = 30
|
anonymous_users = 30
|
||||||
# Number of sessions that can be refreshed in a 5 minute interval per IP address.
|
# AUTH-CFG-10 (nur mit AUTH-CFG-13 wirksam, lokal ohne Wirkung): Passwort-Login und Token-Refresh,
|
||||||
|
# 150 je 5 min je IP. Env: GOTRUE_RATE_LIMIT_TOKEN_REFRESH.
|
||||||
token_refresh = 150
|
token_refresh = 150
|
||||||
# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users).
|
# AUTH-CFG-11 (nur mit AUTH-CFG-13 wirksam, lokal ohne Wirkung): Registrierung, Reset, erneut senden,
|
||||||
|
# 30 je 5 min je IP. Env: GOTRUE_RATE_LIMIT_OTP.
|
||||||
sign_in_sign_ups = 30
|
sign_in_sign_ups = 30
|
||||||
# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address.
|
# AUTH-CFG-12 (nur mit AUTH-CFG-13 wirksam, lokal ohne Wirkung): Link-Prüfung, 30 je 5 min je IP.
|
||||||
|
# Env: GOTRUE_RATE_LIMIT_VERIFY.
|
||||||
token_verifications = 30
|
token_verifications = 30
|
||||||
# Number of Web3 logins that can be made in a 5 minute interval per IP address.
|
# Number of Web3 logins that can be made in a 5 minute interval per IP address.
|
||||||
web3 = 30
|
web3 = 30
|
||||||
|
|
@ -217,22 +237,28 @@ web3 = 30
|
||||||
# provider = "hcaptcha"
|
# provider = "hcaptcha"
|
||||||
# secret = ""
|
# secret = ""
|
||||||
|
|
||||||
|
# AUTH-CFG-13 (nur Env / nur Self-Hosting): Client-IP hinter Proxy, GOTRUE_RATE_LIMIT_HEADER
|
||||||
|
# (z. B. X-Forwarded-For). Pflicht für Staging/Prod, in der CLI nicht setzbar.
|
||||||
|
|
||||||
[auth.email]
|
[auth.email]
|
||||||
# Allow/disallow the email provider. When false, users can't sign up or sign in with email.
|
# AUTH-CFG-03 (wirkt lokal): E-Mail-Anmeldung erlaubt. Staging/Prod: GOTRUE_EXTERNAL_EMAIL_ENABLED=true.
|
||||||
# To block new signups only, set enable_signup = false under [auth] instead.
|
|
||||||
enabled = true
|
enabled = true
|
||||||
# If enabled, a user will be required to confirm any email change on both the old, and new email
|
# AUTH-CFG-23 (wirkt lokal, in #1 NICHT festgelegt): CLI-Standard bleibt unverändert; Entscheidung in
|
||||||
# addresses. If disabled, only the new email is required to confirm.
|
# KTO-11 (Info-Mail an die alte Adresse, N2). Staging/Prod: GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED.
|
||||||
double_confirm_changes = true
|
double_confirm_changes = true
|
||||||
# If enabled, users need to confirm their email address before signing in.
|
# AUTH-CFG-04 (wirkt lokal): E-Mail-Bestätigung Pflicht (CLI-Standard wäre false). Voraussetzung für den
|
||||||
enable_confirmations = false
|
# Enumerationsschutz zusammen mit AUTH-CFG-24. Staging/Prod: GOTRUE_MAILER_AUTOCONFIRM=false.
|
||||||
# If enabled, users will need to reauthenticate or have logged in recently to change their password.
|
enable_confirmations = true
|
||||||
|
# Ohne AUTH-CFG-ID (Spec 9.4): Passwortänderung ohne erneute Anmeldung bleibt wie im Standard.
|
||||||
|
# Recovery-Sitzungen sind ohnehin ausgenommen; die Entscheidung "aktuelles Passwort verlangen" fällt in KTO-11 (N13).
|
||||||
secure_password_change = false
|
secure_password_change = false
|
||||||
# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.
|
# AUTH-CFG-08 (wirkt lokal): Mindestabstand zwischen Mails je Nutzer. Lokal/CI 1 s (sonst scheitert der
|
||||||
|
# Resend-Test direkt nach dem Signup mit 429). Staging/Prod: 60 s über GOTRUE_SMTP_MAX_FREQUENCY=60s.
|
||||||
max_frequency = "1s"
|
max_frequency = "1s"
|
||||||
# Number of characters used in the email OTP.
|
# Länge des E-Mail-OTP-Codes (nur der Vollständigkeit halber gesetzt; Shuffle nutzt token_hash-Links).
|
||||||
otp_length = 6
|
otp_length = 6
|
||||||
# Number of seconds before the email OTP expires (defaults to 1 hour).
|
# AUTH-CFG-07 (wirkt lokal): Gültigkeit von Bestätigungs- und Reset-Link, 3600 s (1 h, Entscheidung F3).
|
||||||
|
# Staging/Prod: GOTRUE_MAILER_OTP_EXP=3600 explizit setzen (das Binary hätte 86400).
|
||||||
otp_expiry = 3600
|
otp_expiry = 3600
|
||||||
|
|
||||||
# Use a production-ready SMTP server
|
# Use a production-ready SMTP server
|
||||||
|
|
@ -245,23 +271,45 @@ otp_expiry = 3600
|
||||||
# admin_email = "admin@email.com"
|
# admin_email = "admin@email.com"
|
||||||
# sender_name = "Admin"
|
# sender_name = "Admin"
|
||||||
|
|
||||||
# Uncomment to customize email template
|
# AUTH-CFG-17 (wirkt lokal): Mailvorlage Bestätigung, de/en per user_metadata.locale (Else-Zweig Englisch).
|
||||||
# [auth.email.template.invite]
|
# Betreff und Text verzweigen gleich (Spike B0 (a)). Staging/Prod: Vorlagen per URL
|
||||||
# subject = "You have been invited"
|
# (GOTRUE_MAILER_TEMPLATES_CONFIRMATION, GOTRUE_MAILER_SUBJECTS_CONFIRMATION).
|
||||||
# content_path = "./supabase/templates/invite.html"
|
[auth.email.template.confirmation]
|
||||||
|
subject = '{{ if and .Data (eq (index .Data "locale") "de") }}Bestätige deine E-Mail-Adresse für Shuffle{{ else }}Confirm your email address for Shuffle{{ end }}'
|
||||||
|
content_path = "./supabase/templates/confirmation.html"
|
||||||
|
|
||||||
# Uncomment to customize notification email template
|
# AUTH-CFG-18 (wirkt lokal): Mailvorlage Passwort zurücksetzen, wie oben.
|
||||||
# [auth.email.notification.password_changed]
|
# Staging/Prod: GOTRUE_MAILER_TEMPLATES_RECOVERY, GOTRUE_MAILER_SUBJECTS_RECOVERY.
|
||||||
# enabled = true
|
[auth.email.template.recovery]
|
||||||
# subject = "Your password has been changed"
|
subject = '{{ if and .Data (eq (index .Data "locale") "de") }}Passwort für Shuffle zurücksetzen{{ else }}Reset your Shuffle password{{ end }}'
|
||||||
# content_path = "./supabase/templates/password_changed_notification.html"
|
content_path = "./supabase/templates/recovery.html"
|
||||||
|
|
||||||
|
# AUTH-CFG-19 (wirkt lokal): Benachrichtigungs-Mails explizit aus (KTO-01 Kriterium 11, nur Bestätigung
|
||||||
|
# und Reset). email_changed schaltet KTO-11 ein (N2). Staging/Prod: GOTRUE_MAILER_NOTIFICATIONS_<TYP>_ENABLED=false.
|
||||||
|
[auth.email.notification.password_changed]
|
||||||
|
enabled = false
|
||||||
|
[auth.email.notification.email_changed]
|
||||||
|
enabled = false
|
||||||
|
[auth.email.notification.phone_changed]
|
||||||
|
enabled = false
|
||||||
|
[auth.email.notification.identity_linked]
|
||||||
|
enabled = false
|
||||||
|
[auth.email.notification.identity_unlinked]
|
||||||
|
enabled = false
|
||||||
|
[auth.email.notification.mfa_factor_enrolled]
|
||||||
|
enabled = false
|
||||||
|
[auth.email.notification.mfa_factor_unenrolled]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
[auth.sms]
|
[auth.sms]
|
||||||
# Allow/disallow the phone provider. When false, users can't sign up or sign in with SMS.
|
# Allow/disallow the phone provider. When false, users can't sign up or sign in with SMS.
|
||||||
# To block new signups only, set enable_signup = false under [auth] instead.
|
# To block new signups only, set enable_signup = false under [auth] instead.
|
||||||
enabled = false
|
enabled = false
|
||||||
# If enabled, users need to confirm their phone number before signing in.
|
# AUTH-CFG-24 (wirkt lokal): SMS ist nicht im Einsatz, diese Einstellung steuert aber den
|
||||||
enable_confirmations = false
|
# Enumerationsschutz beim E-Mail-Signup. Mit false (= GOTRUE_SMS_AUTOCONFIRM=true, CLI-Standard) liefert
|
||||||
|
# Signup mit bestätigter Adresse 422 user_already_exists (Spike A1). MUSS true bleiben.
|
||||||
|
# Staging/Prod: GOTRUE_SMS_AUTOCONFIRM=false bzw. ENABLE_PHONE_AUTOCONFIRM=false (Vorlage setzt true!).
|
||||||
|
enable_confirmations = true
|
||||||
# Template for sending OTP to users
|
# Template for sending OTP to users
|
||||||
template = "Your code is {{ .Code }}"
|
template = "Your code is {{ .Code }}"
|
||||||
# Controls the minimum amount of time that must pass before sending another sms otp.
|
# Controls the minimum amount of time that must pass before sending another sms otp.
|
||||||
|
|
@ -272,7 +320,8 @@ max_frequency = "5s"
|
||||||
# [auth.sms.test_otp]
|
# [auth.sms.test_otp]
|
||||||
# 14152127777 = "123456"
|
# 14152127777 = "123456"
|
||||||
|
|
||||||
# Configure logged in session timeouts.
|
# AUTH-CFG-16 (wirkt lokal): Sitzungsbegrenzung bewusst NICHT gesetzt (KTO-01 Kriterium 5: angemeldet
|
||||||
|
# bis zum Abmelden). Nicht auskommentieren.
|
||||||
# [auth.sessions]
|
# [auth.sessions]
|
||||||
# Force log out after the specified duration.
|
# Force log out after the specified duration.
|
||||||
# timebox = "24h"
|
# timebox = "24h"
|
||||||
|
|
|
||||||
50
supabase/templates/confirmation.html
Normal file
50
supabase/templates/confirmation.html
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
{{- /*
|
||||||
|
AUTH-CFG-17: Bestätigungs-Mail (KTO-01 Kriterium 1, 11).
|
||||||
|
Sprache aus user_metadata.locale ("de"), sonst Englisch. Keine externen Ressourcen, keine Bilder.
|
||||||
|
Der Link führt auf die App-Seite /auth/confirm; erst der Klick dort löst die Prüfung aus (Spec E3).
|
||||||
|
Das Token (TokenHash) wird bewusst nicht als Klartext angezeigt, nur der Link.
|
||||||
|
*/ -}}
|
||||||
|
{{ if and .Data (eq (index .Data "locale") "de") }}
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Bestätige deine E-Mail-Adresse für Shuffle</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin:0;padding:24px;background-color:#f4f4f5;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:1.5;color:#1a1a1a;">
|
||||||
|
<div style="max-width:560px;margin:0 auto;padding:24px;background-color:#ffffff;">
|
||||||
|
<h1 style="margin:0 0 16px 0;font-size:22px;line-height:1.3;color:#1a1a1a;">Bestätige deine E-Mail-Adresse</h1>
|
||||||
|
<p style="margin:0 0 16px 0;">Willkommen bei Shuffle! Bitte bestätige deine E-Mail-Adresse, damit dein Konto genutzt werden kann.</p>
|
||||||
|
<p style="margin:0 0 24px 0;">
|
||||||
|
<a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email" style="display:inline-block;padding:14px 24px;background-color:#1d4ed8;color:#ffffff;font-weight:bold;text-decoration:none;border-radius:6px;">E-Mail-Adresse bestätigen</a>
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 8px 0;">Der Link ist 1 Stunde gültig und kann nur einmal verwendet werden.</p>
|
||||||
|
<p style="margin:0 0 16px 0;">Falls der Knopf nicht funktioniert, kopiere diese Adresse in die Adresszeile deines Browsers:</p>
|
||||||
|
<p style="margin:0 0 24px 0;word-break:break-all;"><a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email" style="color:#1d4ed8;">{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email</a></p>
|
||||||
|
<p style="margin:0;color:#4b5563;">Wenn du das nicht warst, ignoriere diese E-Mail. Es wird dann nichts geändert.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{{ else }}
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Confirm your email address for Shuffle</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin:0;padding:24px;background-color:#f4f4f5;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:1.5;color:#1a1a1a;">
|
||||||
|
<div style="max-width:560px;margin:0 auto;padding:24px;background-color:#ffffff;">
|
||||||
|
<h1 style="margin:0 0 16px 0;font-size:22px;line-height:1.3;color:#1a1a1a;">Confirm your email address</h1>
|
||||||
|
<p style="margin:0 0 16px 0;">Welcome to Shuffle! Please confirm your email address so you can use your account.</p>
|
||||||
|
<p style="margin:0 0 24px 0;">
|
||||||
|
<a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email" style="display:inline-block;padding:14px 24px;background-color:#1d4ed8;color:#ffffff;font-weight:bold;text-decoration:none;border-radius:6px;">Confirm email address</a>
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 8px 0;">The link is valid for 1 hour and can only be used once.</p>
|
||||||
|
<p style="margin:0 0 16px 0;">If the button does not work, copy this address into your browser's address bar:</p>
|
||||||
|
<p style="margin:0 0 24px 0;word-break:break-all;"><a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email" style="color:#1d4ed8;">{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email</a></p>
|
||||||
|
<p style="margin:0;color:#4b5563;">If this wasn't you, you can ignore this email. Nothing will be changed.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{{ end }}
|
||||||
49
supabase/templates/recovery.html
Normal file
49
supabase/templates/recovery.html
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
{{- /*
|
||||||
|
AUTH-CFG-18: Passwort-zurücksetzen-Mail (KTO-01 Kriterium 8, 11).
|
||||||
|
Sprache aus user_metadata.locale ("de"), sonst Englisch. Keine externen Ressourcen, keine Bilder.
|
||||||
|
Der Link führt auf die App-Seite /auth/confirm; erst der Klick dort löst die Prüfung aus (Spec E3).
|
||||||
|
*/ -}}
|
||||||
|
{{ if and .Data (eq (index .Data "locale") "de") }}
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Passwort für Shuffle zurücksetzen</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin:0;padding:24px;background-color:#f4f4f5;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:1.5;color:#1a1a1a;">
|
||||||
|
<div style="max-width:560px;margin:0 auto;padding:24px;background-color:#ffffff;">
|
||||||
|
<h1 style="margin:0 0 16px 0;font-size:22px;line-height:1.3;color:#1a1a1a;">Passwort zurücksetzen</h1>
|
||||||
|
<p style="margin:0 0 16px 0;">Für dein Shuffle-Konto wurde ein neues Passwort angefordert. Mit dem folgenden Knopf legst du ein neues Passwort fest.</p>
|
||||||
|
<p style="margin:0 0 24px 0;">
|
||||||
|
<a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=recovery" style="display:inline-block;padding:14px 24px;background-color:#1d4ed8;color:#ffffff;font-weight:bold;text-decoration:none;border-radius:6px;">Neues Passwort festlegen</a>
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 8px 0;">Der Link ist 1 Stunde gültig und kann nur einmal verwendet werden.</p>
|
||||||
|
<p style="margin:0 0 16px 0;">Falls der Knopf nicht funktioniert, kopiere diese Adresse in die Adresszeile deines Browsers:</p>
|
||||||
|
<p style="margin:0 0 24px 0;word-break:break-all;"><a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=recovery" style="color:#1d4ed8;">{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=recovery</a></p>
|
||||||
|
<p style="margin:0;color:#4b5563;">Wenn du das nicht warst, ignoriere diese E-Mail. Dein Passwort bleibt dann unverändert.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{{ else }}
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Reset your Shuffle password</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin:0;padding:24px;background-color:#f4f4f5;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:1.5;color:#1a1a1a;">
|
||||||
|
<div style="max-width:560px;margin:0 auto;padding:24px;background-color:#ffffff;">
|
||||||
|
<h1 style="margin:0 0 16px 0;font-size:22px;line-height:1.3;color:#1a1a1a;">Reset your password</h1>
|
||||||
|
<p style="margin:0 0 16px 0;">A new password was requested for your Shuffle account. Use the button below to choose a new password.</p>
|
||||||
|
<p style="margin:0 0 24px 0;">
|
||||||
|
<a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=recovery" style="display:inline-block;padding:14px 24px;background-color:#1d4ed8;color:#ffffff;font-weight:bold;text-decoration:none;border-radius:6px;">Set a new password</a>
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 8px 0;">The link is valid for 1 hour and can only be used once.</p>
|
||||||
|
<p style="margin:0 0 16px 0;">If the button does not work, copy this address into your browser's address bar:</p>
|
||||||
|
<p style="margin:0 0 24px 0;word-break:break-all;"><a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=recovery" style="color:#1d4ed8;">{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=recovery</a></p>
|
||||||
|
<p style="margin:0;color:#4b5563;">If this wasn't you, you can ignore this email. Your password will stay the same.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{{ end }}
|
||||||
Loading…
Reference in a new issue