Adds display name validation, terms_version, locale sync trigger and pgTAP tests (KTO-01 criteria 1, 2, 10, 11). Includes GoTrue spike results. Refs #1 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
96 lines
4 KiB
PL/PgSQL
96 lines
4 KiB
PL/PgSQL
-- KTO-01 Registrierung: strengere Regeln beim Anlegen des Profils (Spec #1, § 3.2).
|
|
-- 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'.
|
|
-- 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).
|
|
|
|
-- 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.
|
|
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;
|
|
|
|
-- 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.
|
|
alter table public.profiles
|
|
add column terms_version text
|
|
check (terms_version is null or char_length(terms_version) between 1 and 50);
|
|
|
|
-- Ohne Angabe gilt Englisch (QUE-05); der Client sendet immer die aktive Sprache.
|
|
alter table public.profiles alter column locale set default 'en';
|
|
|
|
-- Profil beim Registrieren anlegen (ersetzt die Funktion aus 20261102100100_profiles.sql).
|
|
-- security definer nötig: Der Trigger läuft im Kontext von auth.users (Rolle supabase_auth_admin),
|
|
-- die Rolle hat keine Rechte auf public.profiles (RLS, keine Insert-Policy für Clients).
|
|
-- search_path ist leer, alle Objekte sind schema-qualifiziert.
|
|
create or replace function public.handle_new_user()
|
|
returns trigger
|
|
language plpgsql
|
|
security definer
|
|
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');
|
|
begin
|
|
if coalesce(meta ->> 'age_confirmed', '') <> 'true' then
|
|
raise exception 'age_confirmation_required';
|
|
end if;
|
|
if coalesce(meta ->> 'terms_accepted', '') <> 'true' then
|
|
raise exception 'terms_acceptance_required';
|
|
end if;
|
|
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
|
|
raise exception 'display_name_invalid';
|
|
end if;
|
|
|
|
insert into public.profiles
|
|
(id, display_name, locale, terms_version, terms_accepted_at, age_confirmed_at)
|
|
values (
|
|
new.id,
|
|
name,
|
|
case when meta ->> 'locale' in ('de', 'en') then meta ->> 'locale' else 'en' end,
|
|
version,
|
|
now(),
|
|
now()
|
|
);
|
|
return new;
|
|
end;
|
|
$$;
|
|
|
|
-- Mailsprache folgt dem Profil: GoTrue-Vorlagen sehen nur user_metadata (.Data.locale).
|
|
-- security definer nötig: Schreiben auf auth.users ist dem Client verwehrt. Der Trigger
|
|
-- ändert ausschließlich die Zeile des eigenen Nutzers (where id = new.id) und nur den Schlüssel
|
|
-- 'locale'. search_path ist leer.
|
|
create or replace function public.sync_profile_locale_to_auth()
|
|
returns trigger
|
|
language plpgsql
|
|
security definer
|
|
set search_path = ''
|
|
as $$
|
|
begin
|
|
update auth.users
|
|
set raw_user_meta_data =
|
|
coalesce(raw_user_meta_data, '{}'::jsonb) || jsonb_build_object('locale', new.locale)
|
|
where id = new.id;
|
|
return new;
|
|
end;
|
|
$$;
|
|
|
|
revoke all on function public.sync_profile_locale_to_auth() from public, anon, authenticated;
|
|
|
|
create trigger sync_locale_to_auth
|
|
after update of locale on public.profiles
|
|
for each row
|
|
when (old.locale is distinct from new.locale)
|
|
execute function public.sync_profile_locale_to_auth();
|