"""OIDC (OpenID Connect) client setup — used for Authentik SSO today, generic enough to register other providers later. Authlib handles the Authorization Code + PKCE flow, including discovery via the provider's `.well-known/openid-configuration` document. We keep a single registered client named "authentik" regardless of label, so the router code always knows where to find it. If OIDC isn't fully configured the module stays inert — `is_enabled()` returns False and the registry has no client. Callers must guard. """ from typing import Optional from authlib.integrations.starlette_client import OAuth from app.config import settings PROVIDER_NAME = "authentik" def is_configured() -> bool: """True when every required OIDC setting is present.""" return bool( settings.oidc_issuer and settings.oidc_client_id and settings.oidc_client_secret and settings.oidc_redirect_uri ) def is_enabled() -> bool: """True when OIDC is both configured and the admin flipped it on.""" return bool(settings.oidc_enabled) and is_configured() def provider_label() -> str: return settings.oidc_provider_label or "Authentik" def _build_oauth() -> OAuth: """Build the Authlib OAuth registry. Always safe to call; only registers the provider when credentials are present so importing this module never fails on a fresh install. """ registry = OAuth() if not is_configured(): return registry # Authentik exposes discovery at `{issuer}/.well-known/openid-configuration`. # Trailing slash handling varies by Authentik version, so normalise. issuer = settings.oidc_issuer.rstrip("/") discovery_url = f"{issuer}/.well-known/openid-configuration" registry.register( name=PROVIDER_NAME, client_id=settings.oidc_client_id, client_secret=settings.oidc_client_secret, server_metadata_url=discovery_url, client_kwargs={ "scope": settings.oidc_scopes, # Force PKCE — cheap win for public clients, harmless for # confidential ones. "code_challenge_method": "S256", }, ) return registry oauth: OAuth = _build_oauth() def get_client() -> Optional[object]: """Return the registered provider client, or None when not configured.""" if not is_configured(): return None return oauth.create_client(PROVIDER_NAME)