Source code for derp.auth.providers.base

"""Base OAuth provider interface."""

from __future__ import annotations

import abc
import dataclasses
from typing import Any


[docs] @dataclasses.dataclass class OAuthUserInfo: """User information retrieved from OAuth provider.""" id: str email: str email_verified: bool = False name: str | None = None picture: str | None = None raw_data: dict[str, Any] | None = None
@dataclasses.dataclass class OAuthTokens: """Tokens received from OAuth provider.""" access_token: str token_type: str = "bearer" refresh_token: str | None = None expires_in: int | None = None scope: str | None = None id_token: str | None = None
[docs] class BaseOAuthProvider[ConfigT](abc.ABC): """Abstract base class for OAuth providers.""" provider_name: str
[docs] def __init__(self, config: ConfigT): self._config = config
[docs] @abc.abstractmethod def get_authorization_url( self, state: str, scopes: list[str] | None = None, redirect_uri: str | None = None, ) -> str: """Generate the OAuth authorization URL. Args: state: CSRF protection state token scopes: Optional list of scopes to request redirect_uri: Optional override for redirect URI Returns: Authorization URL to redirect the user to """
[docs] @abc.abstractmethod async def exchange_code( self, code: str, redirect_uri: str | None = None, ) -> OAuthTokens | None: """Exchange authorization code for tokens. Args: code: Authorization code from callback redirect_uri: Redirect URI (must match authorization request) Returns: OAuthTokens with access token and optional refresh token """
[docs] @abc.abstractmethod async def get_user_info(self, access_token: str) -> OAuthUserInfo | None: """Get user information from the provider. Args: access_token: Access token from token exchange Returns: OAuthUserInfo with user details """
[docs] async def authenticate( self, code: str, redirect_uri: str | None = None, ) -> OAuthUserInfo | None: """Complete OAuth flow: exchange code and get user info. Returns ``None`` if the provider request fails. """ tokens = await self.exchange_code(code, redirect_uri) if tokens is None: return None return await self.get_user_info(tokens.access_token)