2026-07-16 07:26:04 +02:00
|
|
|
"""
|
2026-07-16 09:16:22 +02:00
|
|
|
TeeCup API.
|
2026-07-16 07:26:04 +02:00
|
|
|
|
2026-07-16 09:16:22 +02:00
|
|
|
App-factory + livssyklus. All forretningslogikk ligger i app/routers/ — hver
|
|
|
|
|
rute der følger samme mønster: Depends(get_authorized_org) verifiserer
|
|
|
|
|
medlemskap, org_connection(organization_id) setter RLS-konteksten lokalt for
|
2026-07-16 14:38:42 +02:00
|
|
|
transaksjonen. Se ARCHITECTURE_DECISIONS.md (ADR-001…014) for reglene bak.
|
2026-07-16 07:26:04 +02:00
|
|
|
|
|
|
|
|
Kjøres i app-containeren (FastAPI + asyncpg + uvicorn):
|
|
|
|
|
uvicorn app.main:app --host 0.0.0.0 --port 8000
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
|
|
2026-07-16 09:16:22 +02:00
|
|
|
from fastapi import FastAPI
|
2026-07-16 07:26:04 +02:00
|
|
|
|
2026-07-16 09:16:22 +02:00
|
|
|
from .db import init_pool, close_pool
|
2026-07-16 20:41:34 +02:00
|
|
|
from .routers import auth, matches, organizations, players, scoring, tournaments
|
2026-07-16 07:26:04 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@asynccontextmanager
|
|
|
|
|
async def lifespan(app: FastAPI):
|
|
|
|
|
await init_pool()
|
|
|
|
|
try:
|
|
|
|
|
yield
|
|
|
|
|
finally:
|
|
|
|
|
await close_pool()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app = FastAPI(title="TeeCup API", lifespan=lifespan)
|
|
|
|
|
|
Ekte autentisering er bygget og verifisert. X-Debug-User-Id-stubben er helt fjernet, ingen fallback beholdt.
Ny flyt: magic-link (POST /auth/request-link → POST /auth/verify-link) + JWT-sesjon i HttpOnly/SameSite=Lax/dynamisk-Secure-cookie (30 dager), pluss /auth/logout og /auth/me. Ny migrasjon 004_auth.sql (unik e-post-indeks + magic_link_token-tabell).
Sikkerhetsdesignet fra Plan-agent-gjennomgangen holdt gjennom testing:
Token: secrets.token_urlsafe(32), kun SHA-256-hash lagres
Atomisk forbruk (UPDATE...RETURNING, ikke les-sjekk-skriv) — hindrer replay
Generisk respons uansett om e-posten finnes — hindrer enumerering
app_user opprettes først ved vellykket verifisering, ikke ved forespørsel — hindrer massopprettelse
Gamle uforbrukte lenker ugyldiggjøres når en ny utstedes
PyJWT (byttet fra python-jose pga. bredere sårbarhetsflate) med eksplisitt algorithms=["HS256"]
Ekte eksistens-sjekk mot app_user på hvert kall — en slettet bruker mister tilgang umiddelbart, ikke etter 30 dager
Alle 12 planlagte tester bestått, inkludert cooldown, token-ugyldiggjøring, utløp, tuklet JWT, slettet bruker, og at debug-headeren nå er helt uten effekt.
To ting funnet og fikset/dokumentert underveis:
ON CONFLICT (email) matchet ikke den nye partielle unike indeksen uten eksplisitt WHERE-klausul — fikset.
En reell, dypere RLS-bug (dokumentert i FEATURE_BACKLOG.md, ikke fikset her): organization-tabellens RLS-policy kaster en 500 i stedet for "se ingenting" når app.current_org leses tilbake som tomstreng (ikke NULL) på en gjenbrukt pool-tilkobling. Berører trolig alle 15 RLS-policyer i skjemaet — for stort og sensitivt (ADR-003-grunnmuren) til å hastefikse her, så jeg mitigerte det lokalt i /auth/me og satte det som punkt 1 i neste-steg-listen.
2026-07-16 15:16:53 +02:00
|
|
|
app.include_router(auth.router)
|
2026-07-16 20:41:34 +02:00
|
|
|
app.include_router(organizations.router)
|
2026-07-16 09:16:22 +02:00
|
|
|
app.include_router(players.router)
|
|
|
|
|
app.include_router(tournaments.router)
|
|
|
|
|
app.include_router(matches.router)
|
2026-07-16 14:38:42 +02:00
|
|
|
app.include_router(scoring.router)
|
2026-07-16 07:26:04 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/health")
|
|
|
|
|
async def health() -> dict[str, str]:
|
|
|
|
|
return {"status": "ok"}
|