"""
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())