35f46926a9
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
68 lines
2.2 KiB
Python
68 lines
2.2 KiB
Python
"""Authentication router - login, logout, profile."""
|
|
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from src.backend.database import get_db
|
|
from src.backend.api.middleware.api_key import get_current_user
|
|
from src.backend.models.orm.user import User
|
|
from src.backend.models.api.user import (
|
|
LoginRequest,
|
|
LoginResponse,
|
|
UserProfileUpdate,
|
|
UserResponse,
|
|
)
|
|
from src.backend.services.auth_service import authenticate_user, login_user, logout_user
|
|
|
|
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
|
|
|
|
|
@router.post("/login", response_model=LoginResponse)
|
|
async def login(data: LoginRequest, db: AsyncSession = Depends(get_db)):
|
|
"""Login with username and password, receive API key."""
|
|
user = await authenticate_user(db, data.username, data.password)
|
|
if user is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid username or password",
|
|
)
|
|
api_key = await login_user(db, user)
|
|
return LoginResponse(
|
|
user=UserResponse.model_validate(user),
|
|
api_key=api_key,
|
|
)
|
|
|
|
|
|
@router.post("/logout", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def logout(
|
|
request: Request,
|
|
all_sessions: bool = False,
|
|
user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Logout. Deletes this session's API key only; ?all_sessions=true
|
|
deletes every key of the user."""
|
|
await logout_user(
|
|
db, user, key=request.headers.get("X-API-Key"), all_sessions=all_sessions
|
|
)
|
|
|
|
|
|
@router.get("/me", response_model=UserResponse)
|
|
async def get_profile(user: User = Depends(get_current_user)):
|
|
"""Get current user profile."""
|
|
return UserResponse.model_validate(user)
|
|
|
|
|
|
@router.put("/me", response_model=UserResponse)
|
|
async def update_profile(
|
|
data: UserProfileUpdate,
|
|
user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Update own profile (display_name, language, theme)."""
|
|
update_data = data.model_dump(exclude_unset=True)
|
|
for field, value in update_data.items():
|
|
setattr(user, field, value)
|
|
await db.flush()
|
|
await db.refresh(user)
|
|
return UserResponse.model_validate(user)
|