teecup/077_account_merge.sql
Erol Haagenrud 7d28727cb7 Kontosammenslåing, selvbetjent (ADR-080, "Del 2" av flere e-postadresser)
Selvbetjent sammenslåing av to TeeCup-kontoer: keeper (initiativtaker)
ber om sammenslåing, bekreftelseslenke sendt til taperens e-post beviser
eierskap, taperens data (org-medlemskap/spillerkoblinger/runder/venner)
flyttes over og taperens konto slettes. Ny migrasjon 077 (account_merge_
token, ikke kjørt mot ekte teecup_db ennå), ny app/account_merge.py
(N+1-transaksjoner per RLS-grensen, fullt konfliktkart), nye endepunkter
i app/routers/account_merge.py, ny frontend-seksjon i kontoinnstillinger
+ egen bekreftelsesside.

Fant og fikset en reell RLS-relatert bug via testsuiten før produksjon:
seks RLS-beskyttede tabeller var feilaktig plassert i den globale
"trygt å re-peke"-løkken, forårsaket et krasj pga. en Postgres GUC-kvirk
på pooled forbindelser. Se ADR-080 for full begrunnelse.

12 nye tester (95/95 backend totalt), full scratch-verifisert
ende-til-ende inkl. lys/mørk, ekte teecup_db urørt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 07:56:41 +02:00

42 lines
2.2 KiB
SQL

-- =====================================================================
-- TeeCup — kontosammenslåing, "Del 2" av flere-e-postadresser (migrasjon 077)
-- =====================================================================
-- Oppfølging av ADR-032/migrasjon 017 (sekundær e-post) -- "Del 1" dekket
-- KUN det enkle tilfellet (fri, ukrevd adresse). Del 2: hva skjer når
-- adressen som legges til ALLEREDE er en annen, eksisterende kontos
-- primær- eller sekundæradresse -- samme reelle person har endt opp med
-- to separate TeeCup-kontoer. Selvbetjent (bruker bekreftet eksplisitt).
--
-- Samme token-hash-og-utløp-mønster som magic_link_token (004) ->
-- email_change_token (016) -> secondary_email_token (017): bevis
-- eierskap av MÅL-kontoens e-post FØR selve sammenslåingen utføres.
-- Lenken sendes til MÅL-kontoens (taperens) e-post -- beviser eierskap av
-- DEN kontoen, ikke bare at initiativtakeren (keeperen) fortsatt er
-- innlogget, samme prinsipp som ADR-032 Beslutning B.
--
-- `initiator_user_id`/`target_user_id` er BEVISST asymmetriske roller,
-- ikke to likeverdige parter: initiator (keeper) er den som ber om
-- sammenslåingen fra en aktiv økt, target (taper) er kontoen som slås
-- INN i keeperen og til slutt slettes (se app/account_merge.py -- ingen
-- sesjons-tilbakekallingsmekanisme finnes i denne appen, sletting av
-- app_user-raden er eneste måte å tvinge ut en gjenlevende
-- øktinformasjonskapsel for taper-kontoen).
-- =====================================================================
\set ON_ERROR_STOP on
CREATE TABLE account_merge_token (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
initiator_user_id uuid NOT NULL REFERENCES app_user(id) ON DELETE CASCADE,
target_user_id uuid NOT NULL REFERENCES app_user(id) ON DELETE CASCADE,
token_hash text NOT NULL UNIQUE,
expires_at timestamptz NOT NULL,
consumed_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now(),
CHECK (initiator_user_id <> target_user_id)
);
CREATE INDEX ON account_merge_token (initiator_user_id);
CREATE INDEX ON account_merge_token (target_user_id);
GRANT SELECT, INSERT, UPDATE ON account_merge_token TO teecup_app;