"""Маршруты OAuth-флоу через gnexus-gauth.
Поток: /auth/login → redirect на auth.gnexus.space (PKCE) → /auth/callback →
обмен code на токены → сессия (подписанная cookie через SessionMiddleware) →
redirect на return_to.
InMemory state/pkce store из gnexus-gauth достаточно для одного пользователя;
TODO(M0): заменить на постоянные хранилища до прод-деплоя (см. M0-заметки).
"""
from typing import Any
from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import RedirectResponse
from app.auth.client import get_gauth_client
from app.config import get_settings
router = APIRouter(prefix="/auth", tags=["auth"])
SCOPES = ["openid", "email", "profile"]
@router.get("/login")
async def login(request: Request, return_to: str = "/") -> RedirectResponse:
client = get_gauth_client()
auth_request = client.build_authorization_request(return_to=return_to, scopes=SCOPES)
# return_to запоминаем в сессии и забираем в callback после обмена code.
request.session["return_to"] = return_to
return RedirectResponse(auth_request.authorization_url)
@router.get("/callback")
async def callback(request: Request, code: str, state: str) -> RedirectResponse:
client = get_gauth_client()
try:
token_set = client.exchange_authorization_code(code, state)
except Exception as exc: # gnexus_gauth.exceptions.* — общий обработчик на M0
raise HTTPException(status_code=400, detail=f"OAuth callback failed: {exc}") from exc
user = client.fetch_user(token_set.access_token)
request.session["access_token"] = token_set.access_token
request.session["refresh_token"] = token_set.refresh_token
request.session["user"] = {
"user_id": user.user_id,
"email": user.email,
"avatar_url": user.avatar_url,
# Язык UI: SSO отдаёт profile.locale (default 'ru'); '' в поле — нет значения
"locale": user.profile.get("locale") or "",
}
return_to = request.session.pop("return_to", "/")
return RedirectResponse(return_to)
@router.get("/logout")
async def logout(request: Request) -> dict[str, bool]:
request.session.clear()
return {"ok": True}
@router.get("/me")
async def me(request: Request) -> dict[str, Any]:
user = request.session.get("user")
if not user:
raise HTTPException(status_code=401, detail="Not authenticated")
# Раздел профиля в системе авторизации (SSO): {base}/account
account_url = get_settings().gauth_base_url.rstrip("/") + "/account"
return {"user": user, "account_url": account_url}