""" Auth Service registry — generic, pluggable authentication for any service. Add a new service (Plex, Seerr, etc.) by: 1. Subclassing AuthService 2. Dropping the module in this package 3. Calling register_auth_service() at import time """ from __future__ import annotations from abc import ABC, abstractmethod from dataclasses import dataclass, field from typing import Optional # --------------------------------------------------------------------------- # AuthResult — returned by AuthService.authenticate() # --------------------------------------------------------------------------- @dataclass class AuthResult: """Outcome of a credential validation attempt.""" success: bool external_user_id: Optional[str] = None external_name: Optional[str] = None credentials: Optional[dict] = None error_message: Optional[str] = None # --------------------------------------------------------------------------- # AuthService — abstract base class # --------------------------------------------------------------------------- class AuthService(ABC): """A service that users can authenticate against (Jellyfin, Seerr, Plex, etc.) Subclasses must implement: - name : unique identifier used in URLs and DB keys - display_name : human-readable label shown in Discord - render_login_form(token, discord_id) → HTML string - authenticate(form_data) → AuthResult """ @property @abstractmethod def name(self) -> str: """Unique service name: "jellyfin", "seerr", etc.""" ... @property @abstractmethod def display_name(self) -> str: """Human-readable: "Jellyfin", "Seerr", "Plex" """ ... @abstractmethod def render_login_form(self, token: str, discord_id: int) -> str: """Return HTML string with a login form for this service. The form MUST include these hidden fields: """ ... @abstractmethod async def authenticate(self, form_data: dict) -> AuthResult: """Validate credentials against the service. Return AuthResult.""" ... # --------------------------------------------------------------------------- # Global registry # --------------------------------------------------------------------------- _registry: dict[str, AuthService] = {} def register_auth_service(svc: AuthService) -> None: """Register an AuthService so it can be looked up by name.""" _registry[svc.name] = svc def get_auth_service(name: str) -> AuthService | None: """Look up a registered AuthService by name.""" return _registry.get(name) def list_auth_services() -> list[str]: """Return names of all registered auth services.""" return list(_registry.keys())