"""Эндпоинты аутентификации.""" from __future__ import annotations from fastapi import APIRouter, HTTPException, Request, Response, status from app.api.deps import CurrentUser, SessionDep from app.core.security import hash_password, verify_password from app.db.models.audit import AuditAction from app.schemas.auth import ( ChangePasswordRequest, LoginRequest, LoginResponse, RefreshRequest, TokenPair, UserOut, ) from app.services import audit, auth router = APIRouter(prefix="/auth", tags=["auth"]) @router.post("/login", response_model=LoginResponse) async def login(payload: LoginRequest, request: Request, session: SessionDep) -> LoginResponse: try: user = await auth.authenticate( session, email=payload.email, password=payload.password, request=request ) except auth.AuthError as exc: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(exc)) from exc pair = await auth.issue_token_pair(session, user, request=request) await audit.record(session, action=AuditAction.LOGIN_SUCCESS, user=user, request=request) await session.commit() return LoginResponse(**pair.model_dump(), user=UserOut.model_validate(user)) @router.post("/refresh", response_model=TokenPair) async def refresh(payload: RefreshRequest, request: Request, session: SessionDep) -> TokenPair: try: _, pair = await auth.rotate_refresh_token( session, raw_token=payload.refresh_token, request=request ) except auth.AuthError as exc: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(exc)) from exc await session.commit() return pair @router.post("/logout", status_code=status.HTTP_204_NO_CONTENT) async def logout( payload: RefreshRequest, request: Request, session: SessionDep, user: CurrentUser ) -> Response: await auth.revoke_refresh_token(session, raw_token=payload.refresh_token) await audit.record(session, action=AuditAction.LOGOUT, user=user, request=request) await session.commit() return Response(status_code=status.HTTP_204_NO_CONTENT) @router.get("/me", response_model=UserOut) async def me(user: CurrentUser) -> UserOut: return UserOut.model_validate(user) @router.post("/change-password", status_code=status.HTTP_204_NO_CONTENT) async def change_password( payload: ChangePasswordRequest, request: Request, session: SessionDep, user: CurrentUser ) -> Response: if not verify_password(payload.current_password, user.password_hash): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Текущий пароль указан неверно" ) user.password_hash = hash_password(payload.new_password) # Смена пароля завершает все остальные сессии: если пароль меняют из-за # подозрения на компрометацию, чужой refresh-токен обязан перестать работать. await auth.revoke_all_for_user(session, user.id) await audit.record( session, action=AuditAction.USER_UPDATED, user=user, entity_type="user", entity_id=user.id, payload={"change": "password"}, request=request, ) await session.commit() return Response(status_code=status.HTTP_204_NO_CONTENT)