"""FlowDeck — Gitea OAuth2 client.""" from __future__ import annotations import logging from urllib.parse import urlencode from app.config import settings from app.services.http_client import shared_client logger = logging.getLogger(__name__) class GiteaOAuth: """Gitea OAuth2 client for user authentication.""" AUTHORIZE_URL = f"{settings.gitea_url}/login/oauth/authorize" TOKEN_URL = f"{settings.gitea_url}/login/oauth/access_token" USER_URL = f"{settings.gitea_url}/api/v1/user" def __init__(self): self.client_id = settings.gitea_oauth_client_id self.client_secret = settings.gitea_oauth_client_secret self.redirect_uri = settings.oauth_redirect_uri @property def enabled(self) -> bool: return bool(self.client_id and self.client_secret) def get_authorize_url(self, state: str) -> str: params = { "client_id": self.client_id, "redirect_uri": self.redirect_uri, "response_type": "code", "state": state, } return f"{self.AUTHORIZE_URL}?{urlencode(params)}" async def exchange_code(self, code: str) -> dict | None: """Exchange authorization code for access token.""" try: async with shared_client(timeout=15) as client: resp = await client.post( self.TOKEN_URL, data={ "client_id": self.client_id, "client_secret": self.client_secret, "code": code, "grant_type": "authorization_code", "redirect_uri": self.redirect_uri, }, headers={"Accept": "application/json"}, ) resp.raise_for_status() data = resp.json() return data except Exception as e: logger.error("OAuth token exchange failed: %s", e) return None async def get_user(self, access_token: str) -> dict | None: """Get user info from Gitea API.""" try: async with shared_client(timeout=10) as client: resp = await client.get( self.USER_URL, headers={"Authorization": f"token {access_token}"}, ) resp.raise_for_status() return resp.json() except Exception as e: logger.error("OAuth user fetch failed: %s", e) return None # Singleton gitea_oauth = GiteaOAuth()