72 lines
2.3 KiB
Python
72 lines
2.3 KiB
Python
|
|
"""
|
||
|
|
Auth-lag for TeeCup.
|
||
|
|
|
||
|
|
Selve innloggingen er en STUB her (erstattes med ekte sesjon/JWT senere). Det som
|
||
|
|
IKKE er en stub, og som er sikkerhetskritisk, er `get_authorized_org`: den
|
||
|
|
verifiserer at den innloggede brukeren faktisk er medlem av organisasjonen før
|
||
|
|
org-konteksten settes.
|
||
|
|
|
||
|
|
Hvorfor det er kritisk: RLS stoler blindt på `app.current_org`. Setter appen den
|
||
|
|
til en organisasjon brukeren ikke tilhører, gir RLS lydig tilgang til den
|
||
|
|
organisasjonens data. Isolasjonen står og faller altså på at denne sjekken skjer
|
||
|
|
FØR org_connection kalles — aldri sett konteksten fra en uverifisert kilde (som
|
||
|
|
en klient-oppgitt header).
|
||
|
|
"""
|
||
|
|
|
||
|
|
from dataclasses import dataclass
|
||
|
|
|
||
|
|
from fastapi import Depends, Header, HTTPException, status
|
||
|
|
|
||
|
|
from .db import plain_connection
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass(frozen=True)
|
||
|
|
class CurrentUser:
|
||
|
|
user_id: str
|
||
|
|
|
||
|
|
|
||
|
|
async def get_current_user(
|
||
|
|
x_debug_user_id: str | None = Header(default=None),
|
||
|
|
) -> CurrentUser:
|
||
|
|
"""STUB: hent brukeren fra en verifisert sesjon.
|
||
|
|
|
||
|
|
Midlertidig leses bruker-id fra en header for at skjelettet skal kunne kjøres.
|
||
|
|
ERSTATT dette med validering av sesjonscookie/JWT (signert med
|
||
|
|
settings.SESSION_SECRET) før noe settes i produksjon. En header er IKKE en
|
||
|
|
autentisert kilde.
|
||
|
|
"""
|
||
|
|
if not x_debug_user_id:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||
|
|
detail="Ikke autentisert (stub forventer X-Debug-User-Id).",
|
||
|
|
)
|
||
|
|
return CurrentUser(user_id=x_debug_user_id)
|
||
|
|
|
||
|
|
|
||
|
|
async def get_authorized_org(
|
||
|
|
organization_id: str,
|
||
|
|
user: CurrentUser = Depends(get_current_user),
|
||
|
|
) -> str:
|
||
|
|
"""Returnerer organization_id KUN hvis brukeren er medlem — ellers 403.
|
||
|
|
|
||
|
|
Medlemskapstabellen er ikke organisasjonsavgrenset, så oppslaget gjøres på en
|
||
|
|
tilkobling uten org-kontekst.
|
||
|
|
"""
|
||
|
|
async with plain_connection() as conn:
|
||
|
|
is_member = await conn.fetchval(
|
||
|
|
"""
|
||
|
|
SELECT EXISTS (
|
||
|
|
SELECT 1 FROM organization_membership
|
||
|
|
WHERE user_id = $1 AND organization_id = $2
|
||
|
|
)
|
||
|
|
""",
|
||
|
|
user.user_id,
|
||
|
|
organization_id,
|
||
|
|
)
|
||
|
|
if not is_member:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
||
|
|
detail="Brukeren er ikke medlem av denne organisasjonen.",
|
||
|
|
)
|
||
|
|
return organization_id
|