vinindkoeb/app/routers/auth.py
carsten 0ee6ef2e68 Add sudo-style elevation for superadmin actions
CurrentSuperuser now requires a short-lived (5 min) elevated JWT
claim in addition to the is_superadmin flag, obtained via the new
POST /auth/elevate (no re-authentication — the user is already the
only superadmin in practice; this is a deliberate-confirmation guard
against accidentally triggering a destructive action, not a defense
against a stolen session). Regular login tokens keep working unchanged
for all non-superadmin routes; DELETE /participants/{id} from task 5
now needs a fresh /auth/elevate call, no other code changes required
since CurrentSuperuser's meaning changed underneath it.

create_access_token gained an extra_claims param (backward compatible)
to carry the elevated flag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-28 13:19:35 +02:00

52 lines
1.7 KiB
Python

from datetime import timedelta
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlmodel import SQLModel, select
from app.core.config import get_settings
from app.core.security import create_access_token, verify_password
from app.db import SessionDep
from app.dependencies import CurrentUser
from app.models.user import User, UserPublic
router = APIRouter(prefix="/auth", tags=["auth"])
class Token(SQLModel):
access_token: str
token_type: str = "bearer"
@router.post("/login")
def login(
session: SessionDep,
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
) -> Token:
user = session.exec(select(User).where(User.email == form_data.username)).first()
if user is None or not user.is_active or not verify_password(form_data.password, user.hashed_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
headers={"WWW-Authenticate": "Bearer"},
)
return Token(access_token=create_access_token(subject=str(user.id)))
@router.get("/me", response_model=UserPublic)
def me(current_user: CurrentUser) -> User:
return current_user
@router.post("/elevate")
def elevate(current_user: CurrentUser) -> Token:
if not current_user.is_superadmin:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User is not a superadmin")
settings = get_settings()
token = create_access_token(
subject=str(current_user.id),
expires_delta=timedelta(minutes=settings.elevation_expire_minutes),
extra_claims={"elevated": True},
)
return Token(access_token=token)