"""Authentication routes — token issuance only. POST /auth/token — exchange username + password for a JWT. GET /auth/me — return the current user identity (requires token). """ from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm from pydantic import BaseModel from app.api.dependencies.auth import get_current_user from app.config.settings import settings from app.domain.auth.models import UserClaims from app.shared.bootstrap import get_jwt_handler, get_user_store router = APIRouter(prefix="/auth", tags=["认证"]) class TokenResponse(BaseModel): """JWT token response body.""" access_token: str token_type: str = "bearer" expires_in: int @router.post("/token", response_model=TokenResponse) async def login(form: OAuth2PasswordRequestForm = Depends()): """Issue a JWT for valid username + password credentials. Uses standard OAuth2 password grant form fields — compatible with Swagger UI Authorize button. """ user = get_user_store().authenticate(form.username, form.password) if user is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", headers={"WWW-Authenticate": "Bearer"}, ) token = get_jwt_handler().create_access_token( user_id=user.id, username=user.username, role=user.role, ) return TokenResponse( access_token=token, token_type="bearer", expires_in=settings.auth_token_expire_minutes * 60, ) @router.get("/me") async def get_me(current_user: UserClaims = Depends(get_current_user)): """Return the identity of the currently authenticated user.""" return { "user_id": current_user.user_id, "username": current_user.username, "role": current_user.role.value, }