Fikset og live. Rot-årsaken var altså en manglende sjekk i frontend, ikke noe galt med selve sesjonen eller cookien din — verifisert direkte med din ekte cookie mot produksjon (uten cookie → skjema, med din cookie → sendt rett til /dashboard).

.md-filene er oppdatert: sesjons-bugen er markert  fikset, og ADR-021 (passord/2FA) er notert som naturlig neste steg i den tråden når du vil sette i gang — jeg venter på ditt klarsignal før jeg begynner å bygge det.
This commit is contained in:
Erol Haagenrud 2026-07-19 10:05:42 +02:00
parent 317e24a800
commit 73f3c4d1ea
5 changed files with 295 additions and 2 deletions

View file

@ -277,7 +277,9 @@
"Bash(grep -rl \"POSTGRES_PASSWORD\" /opt/teeoff --include='*.yml' --include='*.env*' 2>/dev/null)", "Bash(grep -rl \"POSTGRES_PASSWORD\" /opt/teeoff --include='*.yml' --include='*.env*' 2>/dev/null)",
"Bash(grep -n \"^ [a-zA-Z0-9_-]*:$\" /opt/teeoff/docker-compose.prod.yml)", "Bash(grep -n \"^ [a-zA-Z0-9_-]*:$\" /opt/teeoff/docker-compose.prod.yml)",
"Bash(set -e)", "Bash(set -e)",
"Bash(curl -s -o /dev/null -w '%{http_code}\\\\n' https://teecup.teeoff.no/dashboard)" "Bash(curl -s -o /dev/null -w '%{http_code}\\\\n' https://teecup.teeoff.no/dashboard)",
"Bash(grep -n \"teecup\" -A 20 /opt/teeoff/deploy/Caddyfile 2>/dev/null | head -60)",
"Bash(curl -s -o /dev/null -w '%{http_code} -> %{redirect_url}\\\\n' -b \"teecup_session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4NDYzM2JlMS1iZWM1LTQ1ZDItOTI1ZC1lNzAzYTdjOWM1OGYiLCJpYXQiOjE3ODQ0NDc5NzIsImV4cCI6MTc4NzAzOTk3Mn0.wQAHP4qlo-zzHH7JrDWm_FyupW8g-_vMkr96szWmuXg\" https://teecup.teeoff.no/)"
], ],
"additionalDirectories": [ "additionalDirectories": [
"/opt/teeoff/deploy", "/opt/teeoff/deploy",

View file

@ -721,6 +721,195 @@ ny fargemodell innført.
--- ---
## ADR-021 — Passord (valgfritt tillegg) + valgfri 2FA (TOTP eller e-post)
**Kontekst:** Reist av brukeren 2026-07-19, sammen med et konkret sesjons-
problem: dagens magic-link-cookie (ADR-009, 30 dager) fungerte visstnok ikke
som tiltenkt — brukeren måtte be om ny innloggingskode ved HVERT besøk til
`teecup.teeoff.no`. **Diagnostisert og FIKSET samme dag** (ikke en del av
ADR-021s videre omfang, men verdt å nevne her siden det var starten på denne
tråden): kodegjennomgangen av `app/auth.py`/`app/routers/auth.py` fant ingen
feil i selve cookie-settingen, og brukerens ekte, ferske cookie (hentet fra
nettleseren på forespørsel) bekreftet 30 dagers levetid, `Secure`/`HttpOnly`/
`SameSite=Lax` alt korrekt. Rot-årsaken var derfor IKKE en cookie-/backend-
bug, men en manglende sjekk i frontend: `app/page.tsx` (rot-siden) viste
ALLTID innloggingsskjemaet uten noensinne å sjekke om en gyldig sesjon
allerede fantes — `Dashboard` sjekket `/auth/me` og sendte til `/` ved
MANGLENDE sesjon, men ingenting gjorde det motsatte. Fikset med en
server-side sesjonssjekk (leser cookien via `next/headers`, kaller
`/auth/me` direkte mot `TEECUP_API_ORIGIN` server-til-server, samme mønster
som `generateMetadata` i `app/t/[id]/page.tsx`) som sender en allerede
innlogget bruker rett til `/dashboard`. Verifisert med brukerens ekte cookie:
uten cookie → 200 (skjema), med gyldig cookie → 307 til `/dashboard`. Rullet
ut live, kun `teecup_frontend`, ingen migrasjon, `teeoff.no` upåvirket.
Utover selve bugen ønsket brukeren eksplisitt: e-post/brukernavn+passord
(må håndtere spesialtegn og mellomrom korrekt) SOM ET TILLEGG til
(ikke erstatning for) den passordløse innloggingen, samt topartsautentisering.
**Beslutning A — Passord er et valgfritt, sidestilt alternativ, ikke
påkrevd.** Login-skjermet får en tredje modus (ved siden av e-post-magic-link
og invitasjonskode, ADR-020) for e-post+passord. En bruker setter selv et
passord når hen ønsker det (egen "sett passord"-handling, krever en allerede
gyldig sesjon — samme "du må bevise identitet FØRST" som andre sensitive
endringer) — INGEN eksisterende eller ny bruker tvinges til å sette passord.
Magic-link fortsetter å fungere uendret for alle, uavhengig av om et passord
i tillegg er satt.
**Beslutning B — Passord-hashing: Argon2id, ikke bcrypt.** Bcrypt trunkerer
stille ved 72 BYTES (et kjent fallgruve-mønster — to ulike passord som deler
de første 72 bytene hasher likt) og har historiske NUL-byte-kvirker i enkelte
implementasjoner. Siden brukeren eksplisitt ber om korrekt håndtering av
spesialtegn/mellomrom (dvs. lengre, mer varierte passord/passfraser er
forventet brukt), velges Argon2id (`argon2-cffi`) — minnehardt, OWASPs
anbefalte standard i dag, ingen lengde-fallgruve. Lagres i ny
`app_user.password_hash` (nullable — NULL betyr "ikke satt", faller da
tilbake til kun magic-link).
**Beslutning C — 2FA: brukeren velger metode selv, TOTP ELLER e-post-
engangskode.** `app_user.two_factor_method` (nullable, `'totp'`/`'email'`).
- **TOTP:** `app_user.totp_secret` (bare satt når metoden er `'totp'`),
`pyotp` for generering/verifisering (RFC 6238-standard, virker med Google
Authenticator/Authy/1Password etc. uten videre). QR-kode for oppsett
generert server-side (`qrcode`-biblioteket + eksisterende Pillow-
avhengighet, samme mønster som AVIF-konverteringen) — ingen ny ekstern
tjeneste, ingen løpende kostnad.
- **E-post-engangskode:** gjenbruker eksisterende SMTP-oppsett (`app/email.py`,
ADR-009) — ny, kort 6-sifret kode, samme hash-og-utløp-mønster som
`magic_link_token` (ny tabell `two_factor_code`, 5 minutters gyldighet).
Svakere som eneste faktor (e-post-kontoen blir da reelt sett den ENESTE
hemmeligheten), men krever ingen ny infrastruktur og er brukerens eget,
informerte valg mellom de to metodene.
- **Bevisst UTENFOR omfang:** SMS-2FA — krever en betalt tredjeparts
SMS-leverandør (f.eks. Twilio), løpende kostnad per melding, ny ekstern
avhengighet. Ikke bygget denne runden; kan legges til som en tredje metode
senere uten å røre TOTP/e-post-sporene, siden `two_factor_method` allerede
er et åpent tekstfelt, ikke en hardkodet to-verdi-enum i skjemaet.
**Beslutning D — 2FA er PÅKREVD for org-eier/admin, valgfritt ellers.**
Ved innlogging (uansett om via magic-link eller passord): har brukeren
`organization_membership.role IN ('owner','admin')` i MINST én organisasjon
OG `two_factor_method IS NULL`, gis IKKE en full sesjon — brukeren tvinges
inn i et "sett opp 2FA nå"-steg først. En vanlig `member`-rolle (eller en
bruker uten noe org-medlemskap ennå) kan fortsette å bruke appen helt uten
2FA om hen ønsker det. **Begrunnelse:** organisator-roller har skrivetilgang
til andre spilleres personopplysninger (ADR-017) og kontroll over hele
turneringer — et kompromittert organisator-passord/magic-link er en langt
alvorligere hendelse enn en kompromittert spillerkonto. Håndheves ved HVER
innlogging (ikke bare første gang), siden en bruker kan BLI eier av en ny
org (`POST /orgs`, ADR ingen restriksjon — se avklaringen under) etter at
kontoen allerede eksisterer uten 2FA.
**Beslutning E — To-stegs innlogging via en `stage`-claim i sesjons-JWT-en,
ikke en egen tabell for "pågående innlogging".** Når 2FA kreves (enten fordi
brukeren selv har slått det på, eller fordi Beslutning D tvinger det),
utsteder primær-autentisering (magic-link-verifisering ELLER passord-innlogging)
et KORTLEVD (5 min) JWT med `stage: "pending_2fa"` i stedet for en full
30-dagers sesjon. En ny avhengighet (`get_pending_2fa_user`, speiler
`get_current_user`) godtar KUN denne mellomtilstanden, og eksponeres bare på
2FA-verifiserings-/oppsett-endepunktene — `get_current_user` (brukt av ALLE
andre endepunkter) avviser eksplisitt en `pending_2fa`-claim som ugyldig,
slik at en ufullstendig innlogging ALDRI gir reell tilgang til noe. Først når
riktig TOTP-/e-post-kode verifiseres, byttes denne inn mot den vanlige fulle
sesjonscookien (samme 30-dagers levetid som i dag).
**Avklaring underveis, ikke en ny beslutning:** brukeren spurte samtidig om
«kan en bruker eie flere organisasjoner» er tenkt gjennom. Bekreftet: JA,
dette var alltid en del av modellen (ADR-002 — bruker og org-medlemskap er
bevisst atskilt nettopp for at én bruker skal kunne krysse flere
organisasjoner). `POST /orgs` (`app/routers/organizations.py`) har INGEN
begrensning på hvor mange organisasjoner én bruker kan opprette/eie —
hver ny org gir automatisk `role='owner'` for oppretteren, uavhengig av
eksisterende medlemskap. Allerede testet i praksis: dashbordets org-bytter,
kryss-org-isolasjonstestene, og `/auth/me` sin N+1-oppslagsstrategi
(«N = antall organisasjoner brukeren tilhører») forutsetter alle nettopp
dette. Ingen kodeendring nødvendig — kun bekreftelse.
**Status:** design ferdig, IKKE bygget ennå. Venter på brukerens endelige
klarsignal før migrasjon/kode skrives (se FEATURE_BACKLOG.md for
gjenstående rekkefølge: sesjons-bug diagnostiseres FØRST, deretter denne
runden).
---
## ADR-022 — Organisasjonseierskap: dele, invitere, frasi seg, superadmin
**Kontekst:** Reist av brukeren 2026-07-19, rett etter ADR-021. Et reelt,
mer FUNDAMENTALT hull ble synlig under gjennomgangen: `organization_
membership` har i dag INGEN vei til å legge til et nytt medlem i det hele
tatt etter at organisasjonen er opprettet — den ENESTE raden som noensinne
settes inn er grunnleggerens egen `owner`-rad (`POST /orgs`,
`app/routers/organizations.py`). Det finnes ingen invitasjon, ingen
rollestyring, ingen måte å fjerne noen på. «Del eierskap»-ønsket er derfor
bare den mest synlige kanten av et bredere manglende felt: hele
medlemskaps-livssyklusen etter opprettelse.
**Beslutning A — Flere eiere er allerede støttet av SKJEMAET, kun API-et
mangler.** `organization_membership.role` har ingen unikhetsbegrensning som
hindrer flere `owner`-rader for samme organisasjon — dette var aldri en
sperre, bare et ubrukt hull. Ingen skjemaendring nødvendig for selve
flereeiere-støtten, kun nye endepunkter.
**Beslutning B — E-post-basert invitasjon, samme mønster som spiller-
sammenkobling (ADR-017 Beslutning B), ikke et helt nytt konsept.** Ny
`POST /orgs/{id}/invitations {email, role}` oppretter en
`organization_invitation`-rad (e-post, rolle, token-hash, utløper, hvem som
inviterte) og sender en e-post via eksisterende SMTP-infrastruktur
(`app/email.py`). Den inviterte trenger IKKE ha en konto fra før — lenken
tar dem til innlogging (magic-link, evt. passord etter ADR-021), og
`verify_magic_link` utvides til å sjekke ventende invitasjoner på e-posten
sin (samme "kjør på hver innlogging, idempotent" mønster som
`link_player_by_email`) og sette inn `organization_membership`-raden da.
**Rolle-grense ved invitasjon:** en `owner` kan invitere til ENHVER rolle
(owner/admin/member); en `admin` kan KUN invitere til `member` — å la en
admin invitere en ny eier ville vært en reell privilegie-eskaleringsvei
(en admin gir seg selv/en alliert eierskap). Dette er en bevisst, ikke
åpen, avgrensning — minste-privilegium-prinsippet, samme resonnement som
`team_authz.py` sin eksisterende owner/admin-splitt.
**Beslutning C — Rollestyring og fjerning, med et «siste eier»-vern.**
Ny `PATCH /orgs/{id}/memberships/{membership_id} {role}` (kun `owner`,
UNNTATT at en bruker alltid kan senke SIN EGEN rolle selv — «frasi seg
eierskapet» er en selvbetjent handling, ikke noe som må be en annen eier om
lov) og `DELETE /orgs/{id}/memberships/{membership_id}` (kun `owner`, eller
selv for å forlate organisasjonen). Begge avviser handlingen med en klar
409 (`LAST_OWNER`) hvis den ville latt organisasjonen stå igjen med NULL
eiere — samme «TOCTOU-trygg med `FOR UPDATE`»-mønster som ADR-011s
to-lags-grense. En enslig eier må altså enten forfremme noen andre til
eier FØRST, eller be en superadmin om hjelp (Beslutning D) hvis
organisasjonen skal forlates helt.
**Beslutning D — Superadmin er et manuelt tildelt, ikke selvbetjent, flagg.**
Ny `app_user.is_super_admin` (boolean, default `false`). INGEN API-endepunkt
lar noen sette dette flagget på seg selv ELLER andre — det settes kun
direkte i databasen av en driftsansvarlig (samme tillitsnivå som å kjøre en
migrasjon), bevisst utenfor appens eget autorisasjonssystem. Årsak: et
selvbetjent «bli superadmin»-endepunkt ville vært selve
sikkerhetshullet det er ment å ikke være. Med flagget satt får brukeren en
NY autorisasjonssti (`get_current_user_or_superadmin`, parallell til
`get_authorized_org` — sjekker `is_super_admin` FØR det vanlige
org-medlemskaps-kravet) som lar dem kalle de samme rolle-/medlemskaps-
endepunktene på ENHVER organisasjon, ikke bare de de selv er medlem av.
**Bevisst avgrenset:** superadmin-stien dekker KUN medlemskap/rolle-
styring i denne runden (nøyaktig det brukeren spurte om — «sette hvem som
helst som eiere av hvilken som helst organisasjon»), ikke generell
skriveadgang til turnering-/spiller-data i andres organisasjoner. En
bredere «support/drift kan se alt»-rolle er en egen, senere beslutning om
den blir etterspurt.
**Beslutning E — Fjernet/frasigende eiers roster-/spillerdata forblir
URØRT.** Bekreftet av bruker 2026-07-19. Kun `organization_membership`-raden
endres/fjernes ved rollestyring eller fjerning — `team_roster`/`player`-rader
(deltakelse-historikk) røres aldri av disse handlingene. Organisasjons-
STYRING og DELTAKELSE er allerede modellert som separate ting, og forblir
det.
**Status:** design ferdig, IKKE bygget ennå. Bygges naturlig sammen med
eller rett etter ADR-021, siden begge utvider samme auth-/autorisasjons-
lag og invitasjons-e-posten gjenbruker samme infrastruktur.
---
## Åpne spørsmål (ikke besluttet ennå) ## Åpne spørsmål (ikke besluttet ennå)
Disse må avklares før eller under de relevante fasene: Disse må avklares før eller under de relevante fasene:

View file

@ -1192,6 +1192,42 @@ Ferdig og verifisert:
umiddelbart, brukeren valgte å rotere — se detaljene under umiddelbart, brukeren valgte å rotere — se detaljene under
backend-avsnittet over for hele hendelsen og hvordan roteringen ble backend-avsnittet over for hele hendelsen og hvordan roteringen ble
gjennomført uten å noensinne re-eksponere gammel eller ny verdi. gjennomført uten å noensinne re-eksponere gammel eller ny verdi.
- **Sesjons-bug diagnostisert og FIKSET, LIVE (2026-07-19):** brukeren
rapporterte at hen måtte be om ny magic-link-kode ved HVERT besøk til
`teecup.teeoff.no`, til tross for ADR-009s 30-dagers sesjonscookie. Bad
brukeren sjekke den EKTE cookien i nettleseren fremfor å gjette — kom
tilbake korrekt satt i alle henseender (`Expires` 30 dager frem,
`Secure`/`HttpOnly`/`SameSite=Lax`). Rot-årsaken var derfor IKKE cookien
eller backend-en: `frontend/app/page.tsx` (rot-siden) viste ALLTID
innloggingsskjemaet uten noensinne å sjekke om en gyldig sesjon allerede
fantes. `Dashboard`-komponenten sjekker `/auth/me` og sender til `/` ved
MANGLENDE sesjon, men ingen kode gjorde det motsatte — en bruker som
besøkte roten direkte (i stedet for å navigere til `/dashboard`) så
derfor alltid innloggingsskjemaet uansett sesjonsstatus.
**Fikset:** `page.tsx` gjort om til en async server-komponent som leser
sesjonscookien via `next/headers`, kaller `/auth/me` server-til-server
direkte mot `TEECUP_API_ORIGIN` (samme mønster som `generateMetadata` i
`app/t/[id]/page.tsx` — IKKE gjennom `next.config.mjs` sin `rewrites()`,
som kun gjelder nettleser-trafikk), og sender en allerede innlogget
bruker videre til `/dashboard` med `redirect()` FØR innloggingsskjemaet
når rendres.
**Verifisert presist mot den ekte, live stacken, med brukerens EGEN
ekte sesjonscookie** (ikke en syntetisk test): `curl` uten cookie mot
`https://teecup.teeoff.no/` ga `200` (skjemaet vises, riktig for en
anonym besøkende); samme kall MED den ekte cookien ga `307` til
`/dashboard` (riktig — sender en allerede innlogget bruker rett videre).
Ekte typesjekket produksjonsbuild kjørt FØR utrulling (build-outputet
viste selv at `/` nå er `ƒ` dynamisk i stedet for `○` statisk — bekrefter
at server-sjekken faktisk ble tatt i bruk). Rullet ut live, kun
`teecup_frontend`, ingen migrasjon, `teeoff.no` upåvirket.
**Samme runde:** brukeren stilte to oppfølgingsspørsmål om
autentisering/autorisasjon — «er flere-organisasjoner-eierskap tenkt
gjennom» (bekreftet: ja, ADR-002 fra dag én, ingen kodeendring nødvendig)
og et ønske om passord (valgfritt tillegg)+2FA. Fullt design skrevet som
**ADR-021** (passord/2FA) og **ADR-022** (dele/invitere/frasi seg
eierskap + superadmin) — se ARCHITECTURE_DECISIONS.md. Begge er
**kun design, IKKE bygget ennå** — venter på brukerens klarsignal til å
starte selve byggingen.
Neste steg: Neste steg:
1. PWA-egenskaper (manifest, service worker, offline-cache) — ikke startet. 1. PWA-egenskaper (manifest, service worker, offline-cache) — ikke startet.

View file

@ -668,6 +668,36 @@ reell (og transparent håndtert) passord-eksponeringshendelse underveis.
--- ---
## Innlogging: sesjon-bug + passord/2FA — ADR-021 (reist 2026-07-19)
| Del | Status | Notat |
|---|---|---|
| Flere organisasjoner per bruker | ✅ bekreftet allerede dekket | ADR-002 fra dag én. `POST /orgs` har ingen begrensning på antall org-er samme bruker kan eie. Ingen kodeendring — kun bekreftet ved gjennomgang 2026-07-19. |
| Sesjon holder seg ikke — ny magic-link-kode kreves ved hvert besøk | ✅ FIKSET og LIVE 2026-07-19 | Ikke en cookie-bug (brukerens ekte cookie var korrekt satt, 30 dager, `Secure`/`HttpOnly`). Root cause: `app/page.tsx` sjekket aldri om en gyldig sesjon allerede fantes før den viste innloggingsskjemaet. Fikset med server-side sesjonssjekk + redirect til `/dashboard`. Se CLAUDE.md-status for full diagnose og verifisering. |
| Passord som valgfritt tillegg til magic-link | 📋 designet (ADR-021), ikke bygget | Argon2id-hashing (ikke bcrypt — unngår 72-byte-trunkering, viktig siden spesialtegn/mellomrom skal fungere korrekt). Passord er ALDRI påkrevd. |
| 2FA: TOTP eller e-post-engangskode, brukerens eget valg | 📋 designet (ADR-021), ikke bygget | SMS bevisst utenfor omfang (krever betalt leverandør). |
| 2FA påkrevd for org-eier/admin, valgfritt for medlemmer | 📋 designet (ADR-021), ikke bygget | Håndheves ved hver innlogging via en `stage: "pending_2fa"`-mellomtilstand i sesjons-JWT-en. |
**Sesjons-bugen er nå fikset** (se raden over) — ADR-021 sitt passord/2FA-løp
er dermed neste naturlige steg i denne tråden, når det er ønskelig.
### Organisasjonseierskap: dele, invitere, frasi seg, superadmin — ADR-022
Reist rett etter ADR-021. Avdekket et bredere, mer fundamentalt hull enn
bare "del eierskap": det finnes i dag INGEN vei til å legge til et
organisasjonsmedlem etter opprettelse i det hele tatt — kun grunnleggerens
egen `owner`-rad settes noensinne inn.
| Del | Status | Notat |
|---|---|---|
| Flere eiere per organisasjon | ✅ skjemaet støtter det allerede | Ingen unikhetssperre hindrer flere `owner`-rader. Kun API mangler. |
| E-post-invitasjon (owner→hvilken som helst rolle, admin→kun member) | 📋 designet (ADR-022), ikke bygget | Gjenbruker SMTP + samme mønster som spiller-e-post-kobling (ADR-017). |
| Rollestyring + frasi seg eierskap (selvbetjent) | 📋 designet (ADR-022), ikke bygget | «Siste eier»-vern (409 `LAST_OWNER`), `FOR UPDATE`-lås mot race. |
| Superadmin (manuelt DB-tildelt, ikke selvbetjent) | 📋 designet (ADR-022), ikke bygget | Kan sette eiere på enhver org. Bevisst avgrenset til akkurat medlemskap/rolle denne runden, ikke generell tilgang til andres data. |
| Fjernet eiers roster-/spillerdata | ✅ avgjort (Beslutning E) | Forblir urørt — organisasjons-styring ≠ deltakelse-historikk. Bekreftet av bruker 2026-07-19. |
---
## UX / frontend (senere fase) ## UX / frontend (senere fase)
- 📋 Høy kontrast, dark/light, store +/- knapper, stor «Neste hull»-knapp - 📋 Høy kontrast, dark/light, store +/- knapper, stor «Neste hull»-knapp

View file

@ -1,7 +1,43 @@
import { cookies } from "next/headers"
import { redirect } from "next/navigation"
import { LoginForm } from "@/components/login-form" import { LoginForm } from "@/components/login-form"
import { Wordmark } from "@/components/wordmark" import { Wordmark } from "@/components/wordmark"
export default function Page() { // Server-side, IKKE nettleser-fetch -- går derfor IKKE gjennom
// next.config.mjs sin rewrites() (samme mønster som generateMetadata i
// app/t/[id]/page.tsx). Peker direkte på API-et.
const API_ORIGIN = process.env.TEECUP_API_ORIGIN || "http://localhost:8000"
// Må matche SESSION_COOKIE_NAME i app/auth.py -- ingen delt konstant på
// tvers av Python/TypeScript, samme mønster som andre API-kontrakt-felt
// som dupliseres bevisst i frontend-koden.
const SESSION_COOKIE_NAME = "teecup_session"
export default async function Page() {
// Reell bug funnet 2026-07-19: denne siden viste ALLTID innloggingsskjemaet,
// uansett om brukeren allerede hadde en helt gyldig sesjonscookie (30 dager,
// bekreftet riktig satt) -- Dashboard sjekker /auth/me og sender deg HIT ved
// manglende sesjon, men ingenting gjorde det motsatte. En bruker som besøkte
// teecup.teeoff.no direkte (i stedet for å navigere til /dashboard) så derfor
// alltid innloggingsskjemaet og ba unødvendig om en ny magic-link hver gang.
const cookieStore = await cookies()
const session = cookieStore.get(SESSION_COOKIE_NAME)
let authenticated = false
if (session) {
try {
const res = await fetch(`${API_ORIGIN}/auth/me`, {
headers: { Cookie: `${SESSION_COOKIE_NAME}=${session.value}` },
cache: "no-store",
})
authenticated = res.ok
} catch {
// API utilgjengelig -- vis innloggingsskjemaet i stedet for å henge.
authenticated = false
}
}
if (authenticated) {
redirect("/dashboard")
}
return ( return (
<main className="flex min-h-[100dvh] flex-col items-center justify-center bg-background px-5 py-10"> <main className="flex min-h-[100dvh] flex-col items-center justify-center bg-background px-5 py-10">
<div className="flex w-full max-w-sm flex-col gap-8"> <div className="flex w-full max-w-sm flex-col gap-8">