feat(db): reject U+200B in display names
Refs #1 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
fb0f4b9d3f
commit
9f36cd0c33
4 changed files with 51 additions and 12 deletions
|
|
@ -94,11 +94,11 @@ Der Trigger `handle_new_user` legt das Profil an und lehnt die Registrierung mit
|
|||
| Mindestalter (`age_confirmed = true`) | `age_confirmation_required` |
|
||||
| Zustimmung (`terms_accepted = true`) | `terms_acceptance_required` |
|
||||
| `terms_version` vorhanden, nach Trim 1–50 Zeichen (Nachweis der zugestimmten Rechtstexte) | `terms_version_required` |
|
||||
| `display_name` vorhanden, nach Trim 1–30 Unicode-Zeichen (Codepoints); kein E-Mail-Präfix als Ersatz, kein Kürzen | `display_name_invalid` |
|
||||
| `display_name` vorhanden, nach Trim 1–30 Unicode-Zeichen (Codepoints); kein E-Mail-Präfix als Ersatz, kein Kürzen; U+200B (Zero Width Space) ist an keiner Stelle erlaubt, U+200C/U+200D (ZWNJ/ZWJ, u. a. in Emoji-Sequenzen) schon | `display_name_invalid` |
|
||||
|
||||
- `terms_accepted_at`, `age_confirmed_at` setzt der Trigger auf `now()`. `terms_version` und beide Zeitstempel kann der Nutzer nicht ändern (Spalten-Grants).
|
||||
- `locale`: nur `de`/`en`, sonst und als Spalten-Standard `'en'`.
|
||||
- Check `profiles_display_name_not_blank` (`char_length(btrim(display_name)) between 1 and 30`) gilt auch für spätere Änderungen (KTO-02).
|
||||
- Checks `profiles_display_name_not_blank` (`char_length(trim_ws(display_name)) between 1 and 30`) und `profiles_display_name_no_zwsp` (kein U+200B) gelten auch für spätere Änderungen (KTO-02, SQLSTATE `23514`).
|
||||
- Trigger `sync_locale_to_auth`: Ändert sich `profiles.locale`, schreibt `sync_profile_locale_to_auth()` (security definer, leerer `search_path`) den Wert in `auth.users.raw_user_meta_data.locale`. GoTrue-Mailvorlagen lesen die Sprache von dort. Betroffen ist nur das eigene Konto.
|
||||
|
||||
## Dateien, Import, Offline, Statistik
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ Gleichzeitig ist #1 die **erste Story mit App-Code**. Das Flutter-Gerüst existi
|
|||
| 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. Spike B0 hat bestätigt, dass Betreff und Text `.Data.locale` auswerten (Spike-Dokument (a)); ein Rückfall ist nicht nötig. |
|
||||
| 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 als HTTP 500; der Body enthält zwar die Meldung des Triggers (z. B. `display_name_invalid`, Spike A4), das Format ist aber nicht vertraglich zugesichert. Der Client bleibt deshalb die nutzersichtbare Stelle. |
|
||||
| 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 als HTTP 500; der Body enthält zwar die Meldung des Triggers (z. B. `display_name_invalid`, Spike A4; gilt auch für U+200B im Anzeigenamen), das Format ist aber nicht vertraglich zugesichert. Der Client bleibt deshalb 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. |
|
||||
|
|
@ -65,7 +65,7 @@ Gleichzeitig ist #1 die **erste Story mit App-Code**. Das Flutter-Gerüst existi
|
|||
|---|---|---|---|
|
||||
| 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) |
|
||||
| Anzeigename 1–30 (Krit. 10; Entscheidung Chris: U+200B verboten) | 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 |
|
||||
|
|
@ -76,13 +76,13 @@ Nur additiv: `create or replace function` und neue Objekte, keine Änderung an b
|
|||
|
||||
| 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` | `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. **Zweiter Check** `profiles_display_name_no_zwsp`: `strpos(display_name, chr(8203)) = 0` (U+200B verboten, Update → 23514). Seeds und Bestand enthalten kein U+200B. |
|
||||
| `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.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, länger als 30 Codepoints oder enthält U+200B, 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.
|
||||
**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. **Verbotenes Zeichen (Entscheidung Chris):** Ein Anzeigename, der U+200B (Zero Width Space) an irgendeiner Stelle enthält, ist ungültig (Server: `display_name_invalid` bzw. Check `profiles_display_name_no_zwsp`, 23514). U+200B wird nicht getrimmt, sondern abgelehnt. U+200C/U+200D (ZWJ steckt in Emoji-Sequenzen) und U+FEFF (wird getrimmt) sind davon nicht betroffen.
|
||||
|
||||
**Keine neuen Tabellen.** `auth.users` und `auth.sessions` verwaltet GoTrue.
|
||||
|
||||
|
|
@ -258,7 +258,7 @@ Alle Formulare: Eingaben bleiben bei jedem Fehler erhalten (Querschnitt Zuständ
|
|||
- 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).
|
||||
- Anzeigename leer, zu lang (`n/30`-Zähler sichtbar) oder mit U+200B an beliebiger Stelle: `DisplayNameRule` meldet U+200B als Fehler am Feld, mit derselben Meldung wie „ungültig“ (kein eigener Text).
|
||||
- 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) und `terms_version = AppConfig.termsVersion` (F5).
|
||||
|
|
@ -491,3 +491,4 @@ Nach der Freigabe am 2026-11-02 eingearbeitet. Die Texte der Akzeptanzkriterien
|
|||
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`). Nachgezogen in B2: `secure_password_change` → AUTH-CFG-25 (erledigt).
|
||||
14. **Anzeigename: U+200B verboten (Chris, 2026-11-02):** Ein Name mit U+200B an beliebiger Stelle ist ungültig (Trigger `display_name_invalid`, zusätzlicher Check `profiles_display_name_no_zwsp`, Update → 23514; Migration `20261102212208_auth_registration_rules.sql`, pgTAP `05_auth_registration`). U+200C/U+200D/U+FEFF unverändert. Betroffen: E6, 3.1, 3.2 (Check, Trigger, Trim-Regel), RegisterScreen (Client-Regel für 01a: `DisplayNameRule` meldet U+200B als Feldfehler, gleiche Meldung wie „ungültig").
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
-- 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 trim_ws (Dart-kompatibel). Verstoß -> Registrierung scheitert mit 'display_name_invalid'.
|
||||
-- nach trim_ws (Dart-kompatibel); U+200B (Zero Width Space) ist nirgends erlaubt. 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).
|
||||
|
|
@ -11,7 +11,8 @@
|
|||
-- 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.
|
||||
-- wird hier nicht getrimmt (wie in Dart); im Anzeigenamen ist er stattdessen verboten (siehe
|
||||
-- profiles_display_name_no_zwsp).
|
||||
-- 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.
|
||||
|
|
@ -40,6 +41,15 @@ alter table public.profiles
|
|||
add constraint profiles_display_name_not_blank
|
||||
check (char_length(public.trim_ws(display_name)) between 1 and 30);
|
||||
|
||||
-- U+200B (Zero Width Space) ist im Anzeigenamen verboten, egal an welcher Stelle (Entscheidung
|
||||
-- Chris): Er ist unsichtbar und ermöglicht scheinbar leere oder verwechselbare Namen. U+200C/U+200D
|
||||
-- (ZWNJ/ZWJ) bleiben erlaubt (ZWJ steckt in Emoji-Sequenzen), U+FEFF wird bereits getrimmt.
|
||||
-- Eigener Constraint mit klarem Namen; gilt auch für spätere Änderungen (KTO-02).
|
||||
-- chr(8203) = U+200B; strpos statt Regex, damit nichts locale-abhängig ist.
|
||||
alter table public.profiles
|
||||
add constraint profiles_display_name_no_zwsp
|
||||
check (strpos(display_name, chr(8203)) = 0);
|
||||
|
||||
-- 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
|
||||
-- "update" von 20261102100100_profiles.sql: der Nutzer kann sie nicht ändern.
|
||||
|
|
@ -76,7 +86,7 @@ begin
|
|||
if version is null or char_length(version) not between 1 and 50 then
|
||||
raise exception 'terms_version_required';
|
||||
end if;
|
||||
if name is null or char_length(name) not between 1 and 30 then
|
||||
if name is null or char_length(name) not between 1 and 30 or strpos(name, chr(8203)) > 0 then
|
||||
raise exception 'display_name_invalid';
|
||||
end if;
|
||||
|
||||
|
|
|
|||
|
|
@ -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(60);
|
||||
select plan(67);
|
||||
|
||||
-- ---------------------------------------------------------------------------------------------
|
||||
-- KTO-01 Kriterium 1: gültige Registrierung legt ein Profil an
|
||||
|
|
@ -154,6 +154,30 @@ select lives_ok(
|
|||
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');
|
||||
|
||||
-- U+200B (Zero Width Space) ist im Anzeigenamen verboten; U+200C/U+200D bleiben erlaubt.
|
||||
select throws_ok(
|
||||
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'z1@example.com',
|
||||
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":""}')$$,
|
||||
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: Anzeigename nur aus U+200B wird abgelehnt');
|
||||
select throws_ok(
|
||||
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'z2@example.com',
|
||||
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"Anna"}')$$,
|
||||
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: U+200B mitten im Namen wird abgelehnt');
|
||||
select throws_ok(
|
||||
$$insert into auth.users (id, email, raw_user_meta_data) values (gen_random_uuid(), 'z3@example.com',
|
||||
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"Anna"}')$$,
|
||||
'P0001', 'display_name_invalid', 'KTO-01 Kriterium 10: U+200B am Ende wird abgelehnt (nicht getrimmt)');
|
||||
select is((select count(*) from auth.users where email ~ '^z[0-9]+@example.com$'), 0::bigint,
|
||||
'KTO-01 Kriterium 10: abgelehnte Namen mit U+200B legen kein Konto an');
|
||||
-- Familien-Emoji: Mann, ZWJ, Frau, ZWJ, Mädchen, ZWJ, Junge = 7 Codepoints; ZWJ (U+200D) bleibt erlaubt
|
||||
select lives_ok(
|
||||
$$insert into auth.users (id, email, raw_user_meta_data) values
|
||||
('abababab-abab-abab-abab-abababababab', 'z4@example.com',
|
||||
'{"age_confirmed":"true","terms_accepted":"true","terms_version":"v1","display_name":"👨👩👧👦"}')$$,
|
||||
'KTO-01 Kriterium 10: Familien-Emoji mit ZWJ (7 Codepoints) wird angenommen');
|
||||
select is((select char_length(display_name) from public.profiles where id = 'abababab-abab-abab-abab-abababababab'),
|
||||
7, 'KTO-01 Kriterium 10: ZWJ bleibt erhalten (7 Codepoints)');
|
||||
|
||||
-- Check-Constraint (gilt auch für spätere Änderungen, KTO-02)
|
||||
select ok(exists (select 1 from pg_constraint
|
||||
where conrelid = 'public.profiles'::regclass and conname = 'profiles_display_name_not_blank'
|
||||
|
|
@ -165,6 +189,10 @@ select throws_ok(
|
|||
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)');
|
||||
select throws_ok(
|
||||
$$update public.profiles set display_name = 'An' || chr(8203) || 'na' where id = '11111111-1111-1111-1111-111111111111'$$,
|
||||
'23514', 'new row for relation "profiles" violates check constraint "profiles_display_name_no_zwsp"',
|
||||
'KTO-02 Kriterium 1: Constraint lehnt Anzeigename mit U+200B ab (Update)');
|
||||
|
||||
-- ---------------------------------------------------------------------------------------------
|
||||
-- KTO-01 Kriterium 11: Sprache
|
||||
|
|
|
|||
Loading…
Reference in a new issue