Migrasjon 008 er live på ekte teecup_db, backend er redeployet, alt verifisert (teeoff.no upåvirket gjennom hele prosessen).
ADR-017 sin backend er nå komplett: registrerings-API-et og kontosammenkoblingen fungerer sammen — en spiller organisator la inn på forhånd, i én eller flere klubber, kobles automatisk til riktig konto første gang hen logger inn, uten å bli medlem av noe hun ikke ba om.
This commit is contained in:
parent
05a451ff75
commit
bb3b19560c
5 changed files with 70 additions and 11 deletions
|
|
@ -193,7 +193,8 @@
|
|||
"Bash(sed -i \"s#^TEECUP_DB_NAME=.*#TEECUP_DB_NAME=teecup_scratch#\" .env.scratch)",
|
||||
"Bash(python3 -c \"import sys,json;print\\(json.load\\(sys.stdin\\)['confirmed_count']\\)\")",
|
||||
"Bash(rm -f /opt/teecup/.env.scratch /tmp/owner_cookies.txt)",
|
||||
"Bash(curl -s https://teecup.teeoff.no/public/tournaments/00000000-0000-0000-0000-000000000000)"
|
||||
"Bash(curl -s https://teecup.teeoff.no/public/tournaments/00000000-0000-0000-0000-000000000000)",
|
||||
"Bash(docker exec -i teeoff_db psql -U teeoff_admin -d teecup_scratch -v ON_ERROR_STOP=1 < /opt/teecup/test_isolation.sql 2>&1 | tail -8 *)"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"/opt/teeoff/deploy",
|
||||
|
|
|
|||
35
008_link_player_by_email.sql
Normal file
35
008_link_player_by_email.sql
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
-- =====================================================================
|
||||
-- TeeCup — migrasjon 008
|
||||
-- E-post-basert kontosammenkobling ved innlogging (ADR-017 Beslutning B)
|
||||
-- =====================================================================
|
||||
-- Kjøres etter 001-007.
|
||||
--
|
||||
-- Kontekst: en spiller kan ha `player`-rader (organisator-opprettet eller
|
||||
-- selvregistrert) i FLERE organisasjoner, uten å være MEDLEM av noen av
|
||||
-- dem. Ved innlogging (magic-link -- e-postkontroll er allerede bevist)
|
||||
-- skal enhver `player`-rad med matchende e-post og `user_id IS NULL`
|
||||
-- kobles til den nå innloggede kontoen, uansett hvilken org den tilhører.
|
||||
--
|
||||
-- Samme "løs kontekst-problemet FØR RLS kan håndheve noe"-mønster som
|
||||
-- `public_tournament_org()` (migrasjon 007): en vanlig org-scopet
|
||||
-- UPDATE via org_connection() kan ikke krysse organisasjonsgrenser --
|
||||
-- brukeren er jo nettopp IKKE nødvendigvis medlem av noen av dem.
|
||||
-- SECURITY DEFINER kjører derfor med skaperens BYPASSRLS-rettigheter,
|
||||
-- akkurat som i 007. `SET search_path = public` hindrer search_path-
|
||||
-- kapring (samme herding).
|
||||
-- =====================================================================
|
||||
|
||||
\set ON_ERROR_STOP on
|
||||
|
||||
CREATE FUNCTION link_player_by_email(p_user_id uuid, p_email text)
|
||||
RETURNS void
|
||||
LANGUAGE sql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
UPDATE player
|
||||
SET user_id = p_user_id
|
||||
WHERE user_id IS NULL AND lower(email) = lower(p_email);
|
||||
$$;
|
||||
|
||||
GRANT EXECUTE ON FUNCTION link_player_by_email(uuid, text) TO teecup_app;
|
||||
34
CLAUDE.md
34
CLAUDE.md
|
|
@ -421,17 +421,33 @@ Ferdig og verifisert:
|
|||
offentlige endepunktet bekreftet nåbart over ekte https (ukjent
|
||||
turnering-id ga korrekt `404`/`NOT_FOUND`, ikke-destruktiv sjekk — selve
|
||||
påmeldingsflyten med ekte data ikke testet mot prod i denne runden).
|
||||
**Gjenstår:** e-post-basert `player.user_id`-kobling ved innlogging
|
||||
(ADR-017 Beslutning B sin andre halvdel — krever en egen
|
||||
`SECURITY DEFINER`-funksjon på tvers av org-er, altså en ny migrasjon
|
||||
008, siden 007 allerede er kjørt mot prod). Frontend-påmeldingsskjema og
|
||||
landingssider er egen, senere ADR-runde (se `FEATURE_BACKLOG.md`).
|
||||
**E-post-basert kontosammenkobling LIVE, samme dag:** ny migrasjon
|
||||
`008_link_player_by_email.sql` — `link_player_by_email(user_id, email)`,
|
||||
samme `SECURITY DEFINER`-mønster som `public_tournament_org()` (007),
|
||||
denne gangen for en tverr-org UPDATE i stedet for et lese-oppslag.
|
||||
`verify_magic_link` (`app/routers/auth.py`) kaller den på HVER
|
||||
innlogging (idempotent — funksjonens `WHERE user_id IS NULL` gjør
|
||||
gjentatte kall til en no-op), ikke bare ved førstegangsopprettelse.
|
||||
**Verifisert presist:** to separate org-er, hver med sin egen
|
||||
organisator-opprettede "Kari"-rad (samme e-post, ulik store/små
|
||||
bokstaver for å teste case-insensitivitet også) — ved Karis FØRSTE
|
||||
innlogging ble BEGGE radene koblet til kontoen hennes, i to org-er hun
|
||||
aldri har vært medlem av. Eksplisitt bekreftet: `organization_membership`
|
||||
har NULL rader for henne etterpå — ren identitetskobling, ingen
|
||||
privilegie-eskalering (å ha `player.user_id` satt gir ingen ny tilgang
|
||||
gjennom `get_authorized_org`, som fortsatt krever ekte org-medlemskap
|
||||
uavhengig av dette). Andre innlogging idempotent, ingen feil.
|
||||
`test_isolation.sql` fortsatt 12/12. **Kjørt mot ekte `teecup_db`
|
||||
2026-07-18**, bruker bekreftet eksplisitt, backend redeployet, live
|
||||
sjekker OK, `teeoff.no` upåvirket.
|
||||
**ADR-017s backend er dermed komplett** (registrering + kontokobling).
|
||||
Gjenstående: frontend-påmeldingsskjema og landingssider — egen, senere
|
||||
ADR-runde (se `FEATURE_BACKLOG.md`).
|
||||
|
||||
Neste steg:
|
||||
1. ADR-017: e-post-basert kontosammenkobling ved innlogging (migrasjon 008
|
||||
+ `verify_magic_link`-utvidelse), så landingssider (turnering/org) med
|
||||
påmeldingsskjema i
|
||||
frontend — se brukerens landingsside-spørsmål, samme økt.
|
||||
1. Landingssider (turnering/org) med påmeldingsskjema i frontend — egen
|
||||
ADR-runde (synlighetsvalg offentlig/org/deltakere, slug-URL for org,
|
||||
MinIO for bilder — se `FEATURE_BACKLOG.md`).
|
||||
2. Flere V0-skjermer (økt/program, blind draw, scorekort, leaderboard) —
|
||||
samme mønster: design i V0 (fortsett i samme prosjekt), FORVENT en full
|
||||
re-eksport hver gang — diff mot live-treet i et scratch-område før noe
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
| Frontend: innlogging + verifisering, LIVE | ✅ | ADR-016. Next.js på `teecup.teeoff.no`, ekte magic-link-flyt bevist med reell e-post. Se egen seksjon under. |
|
||||
| Frontend: dashboard (org-bytter/-opprettelse + turneringsliste), LIVE | ✅ | `/dashboard`. Kablet mot `/auth/me`, `/orgs`, `/orgs/{id}/tournaments`. Skrive-flyt bekreftet med ekte data (org "Tjøme Gents" + turnering opprettet av bruker). |
|
||||
| Frontend: lag/roster-skjerm, LIVE | ✅ | `/tournaments/[id]`. To nye backend-endepunkter bygget samtidig (`PATCH`/`DELETE` roster). Skrive-flyt ikke testet med ekte data ennå. |
|
||||
| Selvregistrering + utvidet spillerprofil (API) | ✅ | ADR-017. `app/routers/registration.py` (offentlig, uautentisert), utvidet `players.py`. Live. Frontend-påmeldingsskjema/landingssider gjenstår (egen ADR-runde). |
|
||||
| Selvregistrering + utvidet spillerprofil (API) | ✅ | ADR-017. `app/routers/registration.py` (offentlig, uautentisert), utvidet `players.py`, e-post-basert `player.user_id`-kobling ved innlogging. Backend komplett og live. Frontend-påmeldingsskjema/landingssider gjenstår (egen ADR-runde). |
|
||||
| Roster: endre kaptein / fjern spiller (`PATCH`/`DELETE`) | ✅ | `app/routers/tournaments.py`. Bevisst ingen "kun én kaptein"-håndhevelse ennå — se «Brukerroller»-punktet under. |
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -161,6 +161,13 @@ async def verify_magic_link(
|
|||
# her, kun ved førstegangsopprettelse (se modul-docstring).
|
||||
user_id = await conn.fetchval("SELECT id FROM app_user WHERE email = $1", email)
|
||||
|
||||
# Koble enhver player-rad (i HVILKEN SOM HELST org, uansett
|
||||
# medlemskap) med matchende e-post til denne nå-innloggede kontoen
|
||||
# (ADR-017 Beslutning B). Trygt å kjøre på HVER innlogging, ikke
|
||||
# bare førstegangsopprettelse -- funksjonens WHERE-ledd berører kun
|
||||
# rader som ennå ikke er koblet, så gjentatte kall er en no-op.
|
||||
await conn.execute("SELECT link_player_by_email($1, $2)", user_id, email)
|
||||
|
||||
user_row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT id::text AS id, email::text AS email, display_name, preferred_locale
|
||||
|
|
|
|||
Loading…
Reference in a new issue