# TOML Configuration File Design **Date:** 2026-02-18 **Status:** Approved ## Goal Add a TOML configuration file to Porchlight so that server settings and OIDC client registrations can be defined in a file. Environment variables retain highest priority and override any file-based values. ## Decisions | Decision | Choice | Rationale | |---|---|---| | File format | TOML | Human-friendly, supports comments, stdlib `tomllib` on 3.11+ | | Integration | pydantic-settings `TomlConfigSettingsSource` | Already available in pydantic-settings 2.12, handles precedence natively | | Default path | `porchlight.toml` in CWD | Simple convention, overridable via `OIDC_OP_CONFIG_FILE` | | Missing file | Silently skip | Env vars and defaults still work — file is optional | | Scope | Server settings + client registrations in one file | Single source of truth | ## Precedence ``` env vars > TOML file > defaults ``` pydantic-settings handles this via `settings_customise_sources()`. ## Example porchlight.toml ```toml issuer = "https://auth.example.com" debug = false session_secret = "a-long-random-string" sqlite_path = "data/porchlight.db" signing_key_path = "data/keys" invite_ttl = 86400 [clients.my-webapp] client_secret = "super-secret-value" redirect_uris = ["https://app.example.com/callback"] response_types = ["code"] scope = ["openid", "profile", "email"] token_endpoint_auth_method = "client_secret_basic" [clients.another-app] client_secret = "another-secret" redirect_uris = ["https://other.example.com/oidc/callback"] response_types = ["code"] scope = ["openid", "profile"] token_endpoint_auth_method = "client_secret_basic" ``` ## Code Changes ### config.py - Add `ClientConfig(BaseModel)` with fields: `client_secret`, `redirect_uris`, `response_types` (default `["code"]`), `scope` (default `["openid"]`), `token_endpoint_auth_method` (default `"client_secret_basic"`). - Add `config_file: str = "porchlight.toml"` field to `Settings`. - Add `clients: dict[str, ClientConfig] = {}` field to `Settings`. - Override `settings_customise_sources()` to insert `TomlConfigSettingsSource` between env and defaults. Use `config_file` field as the TOML path. ### app.py - After creating the OIDC server, loop over `settings.clients` and register each client in `oidc_server.context.cdb`. - Keep the internal `manage-app` client registration as-is (always registered regardless of config file). ### cli.py - No changes. `Settings()` picks up the TOML file automatically. ## What Stays the Same - All existing `OIDC_OP_*` env vars work identically. - `Settings()` call sites unchanged. - Docker/compose env var configuration unchanged. - No new dependencies (pydantic-settings 2.12 already has TOML support, Python 3.13 has `tomllib` in stdlib).