from typing import Any from urllib.parse import urlencode import httpx from src.config import Settings def build_login_redirect_url( *, settings: Settings, redirect_uri: str, state: str, ) -> str: query = urlencode( { "response_type": "code", "client_id": settings.authentik_client_id, "redirect_uri": redirect_uri, "scope": "openid profile email", "state": state, } ) return f"{settings.resolved_authentik_authorize_url}?{query}" async def exchange_code_for_tokens( *, settings: Settings, code: str, redirect_uri: str, client: httpx.AsyncClient, ) -> dict[str, str]: response = await client.post( settings.resolved_authentik_token_url, data={ "grant_type": "authorization_code", "code": code, "redirect_uri": redirect_uri, "client_id": settings.authentik_client_id, "client_secret": settings.authentik_client_secret, }, ) response.raise_for_status() payload = response.json() return { "access_token": payload["access_token"], "refresh_token": payload.get("refresh_token"), } async def fetch_user_info( *, settings: Settings, access_token: str, client: httpx.AsyncClient, ) -> dict[str, Any]: """Fetch user info from Authentik userinfo endpoint.""" response = await client.get( f"{settings.authentik_base_url}/application/o/userinfo/", headers={"Authorization": f"Bearer {access_token}"}, ) response.raise_for_status() return response.json()