duyduc
Fix HF retrieval source dependency closure
14cd803
Raw
History Blame Contribute Delete
4.63 kB
import logging
from typing import Annotated
from urllib.parse import urlencode
import httpx
from fastapi import APIRouter, Depends, Query
from fastapi.responses import RedirectResponse
from src.api.dependencies import CurrentUserDep, DbSessionDep
from src.models.auth import (
CurrentUserResponse,
LoginRequest,
LogoutRequest,
RefreshTokenRequest,
RegisterRequest,
TokenResponse,
)
from src.services.auth import AuthService, AuthServiceError
from src.services.security import (
SecurityError,
build_google_authorization_url,
create_oauth_state,
exchange_google_code,
verify_google_id_token,
verify_oauth_state,
)
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/auth", tags=["auth"])
def get_auth_service(db: DbSessionDep) -> AuthService:
return AuthService(db)
AuthServiceDep = Annotated[AuthService, Depends(get_auth_service)]
def build_frontend_auth_redirect(callback_url: str, token_response: TokenResponse) -> str:
fragment = urlencode(
{
"access_token": token_response.access_token,
"refresh_token": token_response.refresh_token,
"token_type": token_response.token_type,
"access_token_expires_in": token_response.access_token_expires_in,
}
)
separator = "&" if "#" in callback_url else "#"
return f"{callback_url}{separator}{fragment}"
@router.post("/register")
def register(payload: RegisterRequest, auth_service: AuthServiceDep) -> TokenResponse:
return auth_service.register(
email=payload.email,
password=payload.password,
full_name=payload.full_name,
)
@router.post("/login")
def login(payload: LoginRequest, auth_service: AuthServiceDep) -> TokenResponse:
return auth_service.login(email=payload.email, password=payload.password)
@router.post("/refresh")
def refresh(payload: RefreshTokenRequest, auth_service: AuthServiceDep) -> TokenResponse:
return auth_service.refresh(payload.refresh_token)
@router.post("/logout")
def logout(payload: LogoutRequest, auth_service: AuthServiceDep) -> dict[str, str]:
auth_service.logout(payload.refresh_token)
return {"status": "ok"}
@router.get("/me")
def me(current_user: CurrentUserDep, auth_service: AuthServiceDep) -> CurrentUserResponse:
return auth_service.current_user_response(current_user)
@router.get("/google/login")
def google_login(auth_service: AuthServiceDep) -> RedirectResponse:
settings = auth_service.settings
if not settings.google_client_id or not settings.google_client_secret:
raise AuthServiceError(503, "auth_unavailable", "Google OAuth is not configured.")
state = create_oauth_state(settings)
return RedirectResponse(build_google_authorization_url(settings, state))
@router.get("/google/callback")
async def google_callback(
code: Annotated[str, Query(min_length=1)],
state: Annotated[str, Query(min_length=1)],
auth_service: AuthServiceDep,
) -> RedirectResponse:
settings = auth_service.settings
try:
verify_oauth_state(state, settings)
token_payload = await exchange_google_code(code, settings)
claims = verify_google_id_token(str(token_payload.get("id_token") or ""), settings)
except httpx.HTTPStatusError as exc:
try:
google_error = exc.response.json()
except ValueError:
google_error = {"body": exc.response.text[:500]}
logger.exception(
"Google OAuth token exchange failed: status=%s error=%s redirect_uri=%s client_id_suffix=%s",
exc.response.status_code,
google_error,
settings.google_redirect_uri,
settings.google_client_id[-12:],
)
raise AuthServiceError(401, "unauthorized", "Google OAuth failed.") from exc
except (SecurityError, ValueError, httpx.HTTPError) as exc:
logger.exception(
"Google OAuth failed after callback: error_type=%s message=%s redirect_uri=%s client_id_suffix=%s",
type(exc).__name__,
str(exc),
settings.google_redirect_uri,
settings.google_client_id[-12:],
)
raise AuthServiceError(401, "unauthorized", "Google OAuth failed.") from exc
token_response = auth_service.upsert_google_user(claims)
# MVP/dev handoff: URL fragments are not sent to the backend in follow-up
# requests. A future production flow should prefer HttpOnly cookies.
return RedirectResponse(
build_frontend_auth_redirect(
settings.frontend_auth_callback_url,
token_response,
)
)