"""OIDC / SSO authentication configuration models.""" from __future__ import annotations from typing import Literal from pydantic import BaseModel, Field class OIDCProviderConfig(BaseModel): """Configuration for a single OIDC identity provider (Keycloak, Google, Azure AD, etc.).""" display_name: str = Field(description="Human-readable name shown on the login button") issuer: str = Field(description="OIDC issuer URL (e.g. https://keycloak.example.com/realms/deerflow)") client_id: str = Field(description="OAuth2 client ID assigned by the provider") client_secret: str | None = Field(default=None, description="OAuth2 client secret ($ENV_VAR references supported)") redirect_uri: str | None = Field(default=None, description="Callback URL the provider will redirect to after auth") scopes: list[str] = Field( default_factory=lambda: ["openid", "email", "profile"], description="OIDC scopes to request (must include openid)", ) token_endpoint_auth_method: Literal["client_secret_post", "client_secret_basic", "none"] = Field( default="client_secret_post", description="How the client authenticates at the token endpoint", ) # ── User provisioning ───────────────────────────────────────────── auto_create_users: bool = Field( default=True, description="Automatically create a DeerFlow user on first SSO login", ) require_verified_email: bool = Field( default=True, description="Reject authentication if the provider does not report the email as verified", ) allowed_email_domains: list[str] = Field( default_factory=list, description="If non-empty, only allow users whose email domain is in this list (e.g. ['example.com'])", ) admin_emails: list[str] = Field( default_factory=list, description="Users with these email addresses are automatically granted the admin role on first login", ) # ── PKCE / nonce ────────────────────────────────────────────────── pkce_enabled: bool = Field(default=True, description="Enable PKCE (S256) for the authorization code flow") nonce_enabled: bool = Field(default=True, description="Include and validate the nonce claim in ID tokens") # ── Endpoint overrides (for providers with non-standard discovery) ─ authorization_endpoint: str | None = Field(default=None) token_endpoint: str | None = Field(default=None) userinfo_endpoint: str | None = Field(default=None) jwks_uri: str | None = Field(default=None) class OIDCAuthConfig(BaseModel): """Top-level OIDC authentication configuration.""" enabled: bool = Field(default=False, description="Enable OIDC SSO authentication") frontend_base_url: str | None = Field( default=None, description="Base URL of the frontend (used for callback redirects when behind a reverse proxy)", ) providers: dict[str, OIDCProviderConfig] = Field( default_factory=dict, description="Map of provider IDs to their configuration (e.g. keycloak, google, azure)", ) class LocalAuthConfig(BaseModel): """Configuration for the built-in email/password authentication provider.""" allow_registration: bool = Field( default=True, description=( "Allow visitors to self-register a local account via POST /api/v1/auth/register. " "Set to false when accounts are provisioned exclusively through SSO — the OIDC " "provisioning policy (allowed_email_domains, require_verified_email, auto_create_users) " "does not apply to local registration." ), ) class AuthAppConfig(BaseModel): """Authentication configuration section for the DeerFlow app config.""" oidc: OIDCAuthConfig = Field(default_factory=OIDCAuthConfig, description="OIDC SSO authentication settings") local: LocalAuthConfig = Field(default_factory=LocalAuthConfig, description="Built-in email/password authentication settings")