feat(auth): KTO-01 Backend – Registrierungsregeln, Auth-Konfiguration, Mailvorlagen (1/4) #33
7 changed files with 119 additions and 22 deletions
|
|
@ -42,8 +42,6 @@ Spalte „Wirkung“: **lokal** = wirkt in der CLI über `config.toml`; **Env**
|
|||
| AUTH-CFG-24 | SMS-Autoconfirm aus | `SMS_AUTOCONFIRM=false` | lokal + Env | `auth.sms.enable_confirmations = true` / `GOTRUE_SMS_AUTOCONFIRM=false`; in der Self-Hosting-Vorlage `ENABLE_PHONE_AUTOCONFIRM=false` | SMS ist nicht im Einsatz, die Einstellung steuert aber den Enumerationsschutz beim E-Mail-Signup. Mit `true` (CLI-Standard und Vorlage `docker/.env.example`!) liefert Signup mit bestätigter Adresse 422 `user_already_exists` | 4 |
|
||||
| AUTH-CFG-25 | Passwortänderung ohne erneute Anmeldung | `false` (Standard, ausdrücklich gesetzt) | lokal + Env | `auth.email.secure_password_change` / `GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION` (Env-Name vor Release gegen das Image prüfen) | Recovery-Sitzungen sind ausgenommen; ob das aktuelle Passwort verlangt wird, entscheidet KTO-11 (N13) | (KTO-11) |
|
||||
|
||||
AUTH-CFG-25: `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.
|
||||
|
|
@ -64,6 +62,12 @@ GOTRUE_PASSWORD_MIN_LENGTH=10
|
|||
GOTRUE_PASSWORD_REQUIRED_CHARACTERS=
|
||||
GOTRUE_PASSWORD_HIBP_ENABLED=false
|
||||
|
||||
# AUTH-CFG-23 (Stagingwert; in #1 nicht festgelegt, CLI-Standard true, Entscheidung in KTO-11)
|
||||
GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED=true
|
||||
|
||||
# AUTH-CFG-25 (Env-Name vor Release gegen das Image prüfen)
|
||||
GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION=false
|
||||
|
||||
# AUTH-CFG-07, -08
|
||||
GOTRUE_MAILER_OTP_EXP=3600
|
||||
GOTRUE_SMTP_MAX_FREQUENCY=60s
|
||||
|
|
@ -83,8 +87,8 @@ 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 }}
|
||||
GOTRUE_MAILER_SUBJECTS_CONFIRMATION={{ if and .Data (eq (printf "%v" (index .Data "locale")) "de") }}Bestätige deine E-Mail-Adresse für Shuffle{{ else }}Confirm your email address for Shuffle{{ end }}
|
||||
GOTRUE_MAILER_SUBJECTS_RECOVERY={{ if and .Data (eq (printf "%v" (index .Data "locale")) "de") }}Passwort für Shuffle zurücksetzen{{ else }}Reset your Shuffle password{{ end }}
|
||||
|
||||
# AUTH-CFG-19 (Benachrichtigungen aus; KTO-11 schaltet email_changed ein)
|
||||
GOTRUE_MAILER_NOTIFICATIONS_PASSWORD_CHANGED_ENABLED=false
|
||||
|
|
@ -115,7 +119,7 @@ Getestet gegen CLI 3.0.0 und GoTrue `v2.197.0` über Kong (wie die App). Die Dro
|
|||
|
||||
| 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. |
|
||||
| (a) Betreff und Vorlage werten `.Data.locale` aus | Bestätigt. `de` → deutsch; `en` oder fehlend → englisch. `printf "%v" (index .Data "locale")` ist sicher, wenn der Schlüssel fehlt oder kein String ist (sonst HTTP 500 bei `eq`). Nach Profiländerung (Sync-Trigger) kommt die Mail in der neuen Sprache. |
|
||||
| (b) `type` für `token_hash` | Bestätigung `email` (auch `signup`), Reset `recovery`. Zweites Einlösen → 403 `otp_expired`. |
|
||||
| (c) Limiter Passwort-Login | `GOTRUE_RATE_LIMIT_TOKEN_REFRESH` (150 je 5 min, Burst 30); Signup/Recover/Resend `GOTRUE_RATE_LIMIT_OTP` (30 je 5 min). **Greifen nur mit `GOTRUE_RATE_LIMIT_HEADER` und Requests mit dem Header.** Die lokale CLI kann den Header nicht setzen, also lokal keine Drosselung. 429 ohne `Retry-After`. |
|
||||
| (d) `email_sent` | Instanzweit. In der CLI wirkungslos (`360000`). Bei erschöpftem Limit antwortet `/recover` für bestehende Adressen 429, für unbekannte 200 (Seitenkanal, akzeptiert F12). |
|
||||
|
|
|
|||
|
|
@ -490,4 +490,4 @@ Nach der Freigabe am 2026-11-02 eingearbeitet. Die Texte der Akzeptanzkriterien
|
|||
10. **9.2:** F11 (Q1, CI-Drosselungstest mit Extra-Container) und F12 (Q2) ergänzt; 9.3 aktualisiert; 9.4 um zwei KTO-11-Hinweise aus dem Spike ergänzt.
|
||||
11. **F12 entschieden (Chris, 2026-11-02):** Neuer Domänenfehler `AuthFailure.emailSendLimited` für `over_email_send_rate_limit` bei `signUp` und `resetPasswordForEmail`; `RegisterController` und `ForgotPasswordController` behandeln ihn wie Erfolg → neutraler CheckEmailScreen (4.3, 4.4, 5.4). CheckEmailScreen zeigt immer den Zusatz „Die E-Mail kann einige Minuten dauern …“, weil Nutzer eine Verzögerung sonst nicht erfahren. 7.1 Krit. 4 um Unit- und Widget-Tests ergänzt. R3/R6: Kanal in der App verdeckt, auf API-Ebene akzeptiert. Die Aussage „Krit. 4 gilt während einer Mail-Sperre nicht in der App“ ist entfallen.
|
||||
12. **F12 ergänzt (Chris, 2026-11-02):** Auch „E-Mail erneut senden“ auf dem CheckEmailScreen bestätigt `over_email_send_rate_limit` neutral; im LoginScreen nach `email_not_confirmed` (Krit. 6) bleibt der Drosselungshinweis. `ResendController` mit `ResendOrigin.checkEmail | login` (4.4, 5.4); Tests in 7.1 Krit. 6; R3, R6, F12 angepasst.
|
||||
13. **AUTH-CFG-25** neu: `auth.email.secure_password_change = false`, Entscheidung in KTO-11 (N13); 9.4 verweist darauf. **ID-Abgleich** mit dem B2-Stand (`supabase/config.toml`, `docs/auth-konfiguration.md`): IDs 01–24 stimmen überein. Angeglichen: AUTH-CFG-03 CLI-Schlüssel `auth.email.enabled` (statt `auth.email.enable_signup`). In B2 noch nachzuziehen: `secure_password_change` dort „ohne eigene ID“ → AUTH-CFG-25.
|
||||
13. **AUTH-CFG-25** neu: `auth.email.secure_password_change = false`, Entscheidung in KTO-11 (N13); 9.4 verweist darauf. **ID-Abgleich** mit dem B2-Stand (`supabase/config.toml`, `docs/auth-konfiguration.md`): IDs 01–24 stimmen überein. Angeglichen: AUTH-CFG-03 CLI-Schlüssel `auth.email.enabled` (statt `auth.email.enable_signup`). Nachgezogen in B2: `secure_password_change` → AUTH-CFG-25 (erledigt).
|
||||
|
|
|
|||
|
|
@ -275,13 +275,13 @@ otp_expiry = 3600
|
|||
# Betreff und Text verzweigen gleich (Spike B0 (a)). Staging/Prod: Vorlagen per URL
|
||||
# (GOTRUE_MAILER_TEMPLATES_CONFIRMATION, GOTRUE_MAILER_SUBJECTS_CONFIRMATION).
|
||||
[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 }}'
|
||||
subject = '{{ if and .Data (eq (printf "%v" (index .Data "locale")) "de") }}Bestätige deine E-Mail-Adresse für Shuffle{{ else }}Confirm your email address for Shuffle{{ end }}'
|
||||
content_path = "./supabase/templates/confirmation.html"
|
||||
|
||||
# AUTH-CFG-18 (wirkt lokal): Mailvorlage Passwort zurücksetzen, wie oben.
|
||||
# Staging/Prod: GOTRUE_MAILER_TEMPLATES_RECOVERY, GOTRUE_MAILER_SUBJECTS_RECOVERY.
|
||||
[auth.email.template.recovery]
|
||||
subject = '{{ if and .Data (eq (index .Data "locale") "de") }}Passwort für Shuffle zurücksetzen{{ else }}Reset your Shuffle password{{ end }}'
|
||||
subject = '{{ if and .Data (eq (printf "%v" (index .Data "locale")) "de") }}Passwort für Shuffle zurücksetzen{{ else }}Reset your Shuffle password{{ end }}'
|
||||
content_path = "./supabase/templates/recovery.html"
|
||||
|
||||
# AUTH-CFG-19 (wirkt lokal): Benachrichtigungs-Mails explizit aus (KTO-01 Kriterium 11, nur Bestätigung
|
||||
|
|
|
|||
|
|
@ -2,19 +2,43 @@
|
|||
-- Nur additiv: bestehende Migrationen bleiben unverändert.
|
||||
--
|
||||
-- 1. display_name: kein Fallback auf das E-Mail-Präfix, kein stilles Kürzen; Länge 1-30 Codepoints
|
||||
-- nach btrim. Verstoß -> Registrierung scheitert mit 'display_name_invalid'.
|
||||
-- nach trim_ws (Dart-kompatibel). Verstoß -> Registrierung scheitert mit 'display_name_invalid'.
|
||||
-- 2. terms_version: Version der zugestimmten Rechtstexte (Nachweis); Pflicht für neue Konten.
|
||||
-- 3. locale: Standard und Trigger-Fallback 'en' (nur 'de'/'en' erlaubt).
|
||||
-- 4. Sync-Trigger: profiles.locale -> auth.users.raw_user_meta_data.locale (Mailsprache).
|
||||
|
||||
-- Trim wie Dart String.trim(): btrim() entfernt nur U+0020, Tab, Zeilenumbruch, NBSP usw. blieben
|
||||
-- stehen. Explizite Liste (Unicode White_Space + BOM, identisch zur Dart-Doku):
|
||||
-- U+0009-000D, 0020, 0085, 00A0, 1680, 2000-200A, 2028, 2029, 202F, 205F, 3000, FEFF.
|
||||
-- Bewusst kein [[:space:]] (locale-abhängig). U+200B (Zero Width Space) ist kein Leerraum und
|
||||
-- bleibt erlaubt, wie in Dart.
|
||||
-- immutable + strict (NULL -> NULL), search_path leer, kein security definer. Execute bleibt für
|
||||
-- public: Der Check-Constraint unten wird mit den Rechten der schreibenden Rolle ausgewertet
|
||||
-- (authenticated beim Profil-Update); die Funktion ist rein und greift auf keine Daten zu.
|
||||
create or replace function public.trim_ws(value text)
|
||||
returns text
|
||||
language sql
|
||||
immutable
|
||||
strict
|
||||
parallel safe
|
||||
set search_path = ''
|
||||
as $$
|
||||
select pg_catalog.regexp_replace(
|
||||
value,
|
||||
'^[\u0009-\u000D\u0020\u0085\u00A0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]+'
|
||||
|| '|' ||
|
||||
'[\u0009-\u000D\u0020\u0085\u00A0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]+$',
|
||||
'',
|
||||
'g'
|
||||
);
|
||||
$$;
|
||||
|
||||
-- Anzeigename: Leerraum zählt nicht. char_length zählt Zeichen (Codepoints), nicht Bytes.
|
||||
-- NOT VALID + VALIDATE: der Constraint gilt sofort für neue Zeilen; VALIDATE prüft den Bestand
|
||||
-- (nur Entwicklungsdaten) mit schwächerem Sperrniveau und bricht die Migration ab, falls ein
|
||||
-- Bestandsname nur aus Leerzeichen besteht.
|
||||
-- Der Constraint wird direkt (ohne NOT VALID) angelegt: Er prüft sofort den Bestand und bricht die
|
||||
-- Migration ab, falls ein vorhandener (Entwicklungs-)Name nur aus Leerraum besteht.
|
||||
alter table public.profiles
|
||||
add constraint profiles_display_name_not_blank
|
||||
check (char_length(btrim(display_name)) between 1 and 30) not valid;
|
||||
alter table public.profiles validate constraint profiles_display_name_not_blank;
|
||||
check (char_length(public.trim_ws(display_name)) between 1 and 30);
|
||||
|
||||
-- Version der Rechtstexte. Nullable nur wegen vorhandener Entwicklungszeilen; für neue Konten
|
||||
-- erzwingt handle_new_user() einen Wert. Die Spalte steht bewusst NICHT im Spalten-Grant
|
||||
|
|
@ -38,8 +62,10 @@ set search_path = ''
|
|||
as $$
|
||||
declare
|
||||
meta jsonb := coalesce(new.raw_user_meta_data, '{}'::jsonb);
|
||||
name text := btrim(meta ->> 'display_name');
|
||||
version text := btrim(meta ->> 'terms_version');
|
||||
name text := case when jsonb_typeof(meta -> 'display_name') = 'string'
|
||||
then public.trim_ws(meta ->> 'display_name') end;
|
||||
version text := case when jsonb_typeof(meta -> 'terms_version') = 'string'
|
||||
then public.trim_ws(meta ->> 'terms_version') end;
|
||||
begin
|
||||
if coalesce(meta ->> 'age_confirmed', '') <> 'true' then
|
||||
raise exception 'age_confirmation_required';
|
||||
|
|
|
|||
|
|
@ -3,13 +3,15 @@
|
|||
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.
|
||||
Gültigkeit "1 Stunde" im Text entspricht AUTH-CFG-07 (GOTRUE_MAILER_OTP_EXP=3600); bei Änderung hier anpassen.
|
||||
Der Link (inkl. TokenHash) ist sichtbar, damit er kopiert werden kann; der Code {{ .Token }} wird bewusst nicht angezeigt.
|
||||
*/ -}}
|
||||
{{ if and .Data (eq (index .Data "locale") "de") }}
|
||||
{{ if and .Data (eq (printf "%v" (index .Data "locale")) "de") }}
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="light">
|
||||
<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;">
|
||||
|
|
@ -31,6 +33,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="light">
|
||||
<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;">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
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") }}
|
||||
{{ if and .Data (eq (printf "%v" (index .Data "locale")) "de") }}
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
-- KTO-01 Registrierung (Datenbankseite): Regeln des Triggers handle_new_user, Anzeigename,
|
||||
-- Nachweis der Zustimmung (terms_version), Sprache und Mailsprache-Sync.
|
||||
begin;
|
||||
select plan(46);
|
||||
select plan(60);
|
||||
|
||||
-- ---------------------------------------------------------------------------------------------
|
||||
-- KTO-01 Kriterium 1: gültige Registrierung legt ein Profil an
|
||||
|
|
@ -22,6 +22,17 @@ select ok((select terms_accepted_at is not null and age_confirmed_at is not null
|
|||
from public.profiles where id = '11111111-1111-1111-1111-111111111111'),
|
||||
'KTO-01 Kriterium 1: Zeitpunkte von Zustimmung und Altersbestätigung sind gesetzt');
|
||||
|
||||
-- Der Client sendet JSON-Booleans (kein String): beide Formen müssen gelten.
|
||||
select lives_ok(
|
||||
$$insert into auth.users (id, email, raw_user_meta_data) values
|
||||
('88888888-8888-8888-8888-888888888888', 'bool@example.com',
|
||||
'{"age_confirmed":true,"terms_accepted":true,"terms_version":"v1","display_name":"Bool"}')$$,
|
||||
'KTO-01 Kriterium 1: JSON-Booleans true werden angenommen');
|
||||
select throws_ok(
|
||||
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'k2c@example.com',
|
||||
'{"age_confirmed":false,"terms_accepted":true,"terms_version":"v1","display_name":"X"}')$$,
|
||||
'P0001', 'age_confirmation_required', 'KTO-01 Kriterium 2: JSON-Boolean age_confirmed=false wird abgelehnt');
|
||||
|
||||
-- ---------------------------------------------------------------------------------------------
|
||||
-- KTO-01 Kriterium 2: Mindestalter muss bestätigt sein
|
||||
-- ---------------------------------------------------------------------------------------------
|
||||
|
|
@ -61,6 +72,14 @@ select throws_ok(
|
|||
format($$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'tv4@example.com',
|
||||
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"%s","display_name":"X"}')$$, repeat('v', 51)),
|
||||
'P0001', 'terms_version_required', 'QUE-04 Nachweis: terms_version mit 51 Zeichen wird abgelehnt');
|
||||
select throws_ok(
|
||||
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'tv6@example.com',
|
||||
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"\u00a0","display_name":"X"}')$$,
|
||||
'P0001', 'terms_version_required', 'QUE-04 Nachweis: terms_version nur aus NBSP wird abgelehnt');
|
||||
select throws_ok(
|
||||
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'tv7@example.com',
|
||||
'{"age_confirmed":"true","terms_accepted":"true","terms_version":5,"display_name":"X"}')$$,
|
||||
'P0001', 'terms_version_required', 'QUE-04 Nachweis: terms_version als Zahl wird abgelehnt');
|
||||
select lives_ok(
|
||||
format($$insert into auth.users (id, email, raw_user_meta_data) values
|
||||
('55555555-5555-5555-5555-555555555555', 'tv5@example.com',
|
||||
|
|
@ -96,7 +115,23 @@ select throws_ok(
|
|||
format($$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'n6@example.com',
|
||||
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"%s"}')$$, repeat('😀', 31)),
|
||||
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: 31 Emoji (Codepoints) werden abgelehnt');
|
||||
select is((select count(*) from auth.users where email ~ '^n[0-9]@example.com$'), 0::bigint,
|
||||
select throws_ok(
|
||||
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'n9@example.com',
|
||||
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"\t"}')$$,
|
||||
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: Anzeigename nur aus Tab wird abgelehnt');
|
||||
select throws_ok(
|
||||
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'n10@example.com',
|
||||
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"\n"}')$$,
|
||||
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: Anzeigename nur aus Zeilenumbruch wird abgelehnt');
|
||||
select throws_ok(
|
||||
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'n11@example.com',
|
||||
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"\u00a0"}')$$,
|
||||
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: Anzeigename nur aus NBSP wird abgelehnt');
|
||||
select throws_ok(
|
||||
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'n12@example.com',
|
||||
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":5}')$$,
|
||||
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: Anzeigename als Zahl wird abgelehnt');
|
||||
select is((select count(*) from auth.users where email ~ '^n[0-9]+@example.com$'), 0::bigint,
|
||||
'KTO-01 Kriterium 10: abgelehnte Registrierungen legen kein Konto an');
|
||||
select lives_ok(
|
||||
format($$insert into auth.users (id, email, raw_user_meta_data) values
|
||||
|
|
@ -111,11 +146,25 @@ select lives_ok(
|
|||
select is((select display_name from public.profiles where id = '77777777-7777-7777-7777-777777777777'),
|
||||
repeat('b', 30), 'KTO-01 Kriterium 10: der Name wird ohne Leerraum gespeichert');
|
||||
|
||||
select lives_ok(
|
||||
$$insert into auth.users (id, email, raw_user_meta_data) values
|
||||
('99999999-9999-9999-9999-999999999999', 'n13@example.com',
|
||||
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"Anna\t"}')$$,
|
||||
'KTO-01 Kriterium 10: Anzeigename mit Tab am Ende wird angenommen');
|
||||
select is((select display_name from public.profiles where id = '99999999-9999-9999-9999-999999999999'),
|
||||
'Anna', 'KTO-01 Kriterium 10: Tab wird entfernt, gespeichert wird Anna');
|
||||
|
||||
-- Check-Constraint (gilt auch für spätere Änderungen, KTO-02)
|
||||
select col_has_check('public', 'profiles', 'display_name', 'KTO-01 Kriterium 10: Anzeigename hat Check-Constraints');
|
||||
select ok(exists (select 1 from pg_constraint
|
||||
where conrelid = 'public.profiles'::regclass and conname = 'profiles_display_name_not_blank'
|
||||
and contype = 'c' and convalidated),
|
||||
'KTO-01 Kriterium 10: Constraint profiles_display_name_not_blank existiert und ist validiert');
|
||||
select throws_ok(
|
||||
$$update public.profiles set display_name = ' ' where id = '11111111-1111-1111-1111-111111111111'$$,
|
||||
'23514', null, 'KTO-01 Kriterium 10: Constraint lehnt Anzeigename nur aus Leerzeichen ab (Update)');
|
||||
select throws_ok(
|
||||
$$update public.profiles set display_name = chr(160) where id = '11111111-1111-1111-1111-111111111111'$$,
|
||||
'23514', null, 'KTO-02 Kriterium 1: Constraint lehnt Anzeigename nur aus NBSP ab (Update)');
|
||||
|
||||
-- ---------------------------------------------------------------------------------------------
|
||||
-- KTO-01 Kriterium 11: Sprache
|
||||
|
|
@ -179,5 +228,20 @@ select is((select raw_user_meta_data ->> 'display_name' from auth.users where id
|
|||
select is((select raw_user_meta_data ->> 'locale' from auth.users where id = '22222222-2222-2222-2222-222222222222'),
|
||||
null, 'KTO-01 Kriterium 11: Metadaten fremder Konten bleiben unverändert');
|
||||
|
||||
-- anon darf die Trigger-Funktionen nicht ausführen
|
||||
set local role anon;
|
||||
select throws_ok($$select public.handle_new_user()$$,
|
||||
'42501', null, 'KTO-01 Sicherheit: anon darf handle_new_user nicht ausführen');
|
||||
select throws_ok($$select public.sync_profile_locale_to_auth()$$,
|
||||
'42501', null, 'KTO-01 Sicherheit: anon darf sync_profile_locale_to_auth nicht ausführen');
|
||||
reset role;
|
||||
|
||||
-- Update auf denselben Wert löst keinen Sync aus (Cara: locale en, Marker in den Metadaten)
|
||||
update auth.users set raw_user_meta_data = raw_user_meta_data || '{"locale":"xx"}'
|
||||
where id = '33333333-3333-3333-3333-333333333333';
|
||||
update public.profiles set locale = 'en' where id = '33333333-3333-3333-3333-333333333333';
|
||||
select is((select raw_user_meta_data ->> 'locale' from auth.users where id = '33333333-3333-3333-3333-333333333333'),
|
||||
'xx', 'KTO-01 Kriterium 11: Update auf denselben Wert löst den Sync nicht aus');
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
|
|
|
|||
Loading…
Reference in a new issue