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>
52 lines
1.7 KiB
Python
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)
|