teecup/frontend/app/logg-inn/page.tsx
Erol Haagenrud b0c65d983b Gjør /logg-inn til den ekte innloggingssiden
/logg-inn (clubhouse-palett, TeeCupAuth) var en foreldreløs V0-
utforskning -- ingenting i appen lenket eller redirectet dit, og
TeeCupAuth var 100% mock (sleep()-baserte stubber, hardkodet
demo-passord, falske invitasjonskoder, en falsk "2FA-kode sendes"-tekst
som aldri sendte noe). Flagget eksplisitt til bruker før arbeid startet,
siden dette er sikkerhetskritisk kode og omfanget var langt større enn
en ren redirect-ombytting.

Full port: ekte POST /auth/request-link, POST /auth/login-password
(identisk LoginResult-statushåndtering som den gamle LoginForm), GET
/public/tournaments/by-code/{code}. TwoFactorVerifyForm/
TwoFactorSetupForm gjenbrukt UENDRET (lavest mulig risiko for
sikkerhetskritisk 2FA-kode). app/page.tsx redusert til en tynn
videresending (autentisert -> /dashboard//account, uautentisert ->
/logg-inn), beholdt for gamle bokmerker. Gammel LoginForm slettet.
Alle ~11 uautentisert-redirect-steder (committet i forrige commit)
pekte allerede til /logg-inn.

Én reell bug funnet og rettet under scratch-verifisering:
app/logg-inn/page.tsx kalte redirect() INNI en try/catch som fanget
Next.js sin egen NEXT_REDIRECT-kontrollflyt-exception stille -- en
allerede innlogget bruker fikk se innloggingsskjemaet på nytt i stedet
for å bli sendt videre. Rettet ved å flytte redirect()-kallet utenfor
try/catch, samme mønster som den opprinnelige /-siden alltid brukte.

Se CHANGELOG.md punkt 42 for full verifiseringsdetalj.
2026-08-08 12:34:50 +02:00

84 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { Metadata } from "next"
import { cookies } from "next/headers"
import { redirect } from "next/navigation"
import { TeeCupWordmark } from "@/components/teecup/wordmark"
import { TeeCupAuth } from "@/components/teecup/teecup-auth"
export const metadata: Metadata = {
title: "Logg inn TeeCup",
description: "Logg inn på TeeCup for live-scoring og golfturneringer med vennegjengen, klubben eller bedriften.",
}
// 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 og den forrige app/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 LoggInnPage() {
// Samme allerede-innlogget-sjekk som /-siden hadde (2026-08-08: /logg-inn
// overtok rollen som ekte innloggingsside, se CHANGELOG.md) -- en bruker
// med gyldig sesjonscookie skal aldri se innloggingsskjemaet på nytt.
const cookieStore = await cookies()
const session = cookieStore.get(SESSION_COOKIE_NAME)
let authenticated = false
let profileComplete = true
if (session) {
try {
const res = await fetch(`${API_ORIGIN}/auth/me`, {
headers: { Cookie: `${SESSION_COOKIE_NAME}=${session.value}` },
cache: "no-store",
})
authenticated = res.ok
if (res.ok) {
const data = await res.json()
profileComplete = Boolean(data.profile_complete)
}
} catch {
// API utilgjengelig -- vis innloggingsskjemaet i stedet for å henge.
authenticated = false
}
}
// redirect() kaster en egen NEXT_REDIRECT-kontrollflyt-exception som MÅ
// boble videre til Next.js -- må derfor stå UTENFOR try/catch-en over
// (en catch der ville slukt den stille og latt innloggingsskjemaet vises
// i stedet for å omdirigere, reell bug funnet under scratch-verifisering
// 2026-08-08, se CHANGELOG.md).
if (authenticated) {
redirect(profileComplete ? "/dashboard" : "/account")
}
return (
<main className="relative flex min-h-dvh flex-col items-center justify-center overflow-hidden bg-clubhouse-bg px-4 py-8 text-clubhouse-ink">
{/* Quiet turf-horizon backdrop — the one bit of atmosphere, kept subtle. */}
<div aria-hidden="true" className="pointer-events-none absolute inset-x-0 bottom-0 h-1/3 bg-gradient-to-t from-tee/15 to-transparent" />
<div className="relative z-10 w-full max-w-md">
{/* Header */}
<header className="mb-6 flex flex-col items-center text-center">
<TeeCupWordmark className="h-11" />
<p className="mt-3 text-pretty text-[0.9375rem] font-medium text-clubhouse-muted">
Live-scoring og turneringer fra første tee til pokalen.
</p>
</header>
{/* Card — the scorecard. Continuation screens (2FA, link confirm) can
reuse this exact container. */}
<section className="overflow-hidden rounded-3xl border border-clubhouse-border bg-clubhouse-card shadow-[0_1px_0_rgba(23,32,15,0.04),0_18px_40px_-24px_rgba(23,32,15,0.35)]">
{/* Signature element: a fairway-green scorecard header stripe. */}
<div className="h-2 w-full bg-gradient-to-r from-tee-strong via-tee to-cup" />
<div className="p-6 sm:p-8">
<TeeCupAuth />
</div>
</section>
<p className="mt-6 text-center text-[0.875rem] leading-relaxed text-clubhouse-muted">
Ved å logge inn godtar du vilkårene våre. Trenger du hjelp? Ta kontakt med klubben din.
</p>
</div>
</main>
)
}