Source code for litestar_oauth.providers.discord

"""Discord OAuth2 provider implementation."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from litestar_oauth.base import BaseOAuthProvider

if TYPE_CHECKING:
    from litestar_oauth.types import OAuthUserInfo


[docs] class DiscordOAuthProvider(BaseOAuthProvider): """Discord OAuth2 provider. Implements OAuth2 authentication flow for Discord. Default scopes: - identify: Access basic user information - email: Access user email address User Info Mapping: - oauth_id: Discord user ID (snowflake) - email: User email address - email_verified: Email verification status - username: Discord username (with discriminator if applicable) - first_name: Discord username (no first/last name separation) - last_name: Empty (Discord doesn't separate names) - avatar_url: Discord CDN avatar URL - profile_url: Not provided by Discord Args: client_id: Discord OAuth application client ID. client_secret: Discord OAuth application client secret. scope: Optional custom scopes. Defaults to ["identify", "email"]. """ @property def provider_name(self) -> str: """Return provider identifier. Returns: Provider name 'discord'. """ return "discord" @property def authorize_url(self) -> str: """Return Discord authorization endpoint. Returns: Discord OAuth authorization URL. """ return "https://discord.com/api/oauth2/authorize" @property def token_url(self) -> str: """Return Discord token exchange endpoint. Returns: Discord OAuth token URL. """ return "https://discord.com/api/oauth2/token" @property def user_info_url(self) -> str: """Return Discord user info endpoint. Returns: Discord API user endpoint URL. """ return "https://discord.com/api/users/@me" def _default_scope(self) -> list[str]: """Return default scopes for Discord OAuth. Returns: List of default OAuth scopes. """ return ["identify", "email"]
[docs] async def get_user_info( self, access_token: str, **kwargs: Any, ) -> OAuthUserInfo: """Fetch and normalize Discord user information. Retrieves user profile from Discord API and normalizes it to OAuthUserInfo format. Handles Discord's avatar hash and CDN URL construction. Args: access_token: Discord OAuth access token. **kwargs: Additional parameters (unused). Returns: Normalized user information. Raises: ImportError: If httpx is not installed. Exception: If user info fetch fails. """ try: import httpx except ImportError as e: msg = "httpx is required for Discord OAuth. Install it with: pip install httpx" raise ImportError(msg) from e from litestar_oauth.types import OAuthUserInfo headers = { "Authorization": f"Bearer {access_token}", } async with httpx.AsyncClient() as client: response = await client.get(self.user_info_url, headers=headers) response.raise_for_status() user_data = response.json() # Build Discord avatar URL from hash # Format: https://cdn.discordapp.com/avatars/{user_id}/{avatar_hash}.png avatar_url = "" if user_data.get("avatar"): user_id = user_data.get("id") avatar_hash = user_data.get("avatar") # Check if avatar is animated (hash starts with 'a_') extension = "gif" if avatar_hash.startswith("a_") else "png" avatar_url = f"https://cdn.discordapp.com/avatars/{user_id}/{avatar_hash}.{extension}" # Discord username format changed - discriminator is now optional username = user_data.get("username", "") discriminator = user_data.get("discriminator") if discriminator and discriminator != "0": username = f"{username}#{discriminator}" return OAuthUserInfo( provider=self.provider_name, oauth_id=str(user_data.get("id", "")), email=user_data.get("email"), email_verified=user_data.get("verified", False), username=username, first_name=user_data.get("username", ""), # Discord only has username last_name="", # Discord doesn't have separate first/last names avatar_url=avatar_url, profile_url="", # Discord doesn't provide profile URLs via API raw_data=user_data, )