`ConfigSources` tracks where each field's effective value came from (shown in the admin debug page).
**To add a new config field:**
1. Add the field to `AppConfig` struct
2. Set its default in `AppConfig::default()`
3. Add the field to `ConfigSources` struct and its `Default` impl
4. Add it to the `impl_env_overrides!(…)` invocation
5. Add an `apply_db_field!()` call in `apply_db_overrides`
6. Add an `entry!()` line in `admin/views.rs → config_display_entries()`
### Auth (`src/auth.rs`)
Session-based authentication with two roles:
- **`Role::Admin`** — full access to admin panel
- **`Role::User`** — standard user
Key functions:
-`login(session, user_id)` — sets session, cycles session ID
-`logout(session)` — flushes session
-`get_session_user(session, db)` — returns `AuthenticatedUser` if active
-`require_admin_or_redirect(session, db)` — guard that returns 403 or redirects to `/login`
### OIDC/SSO (`src/oidc.rs`)
Full OpenID Connect authorization code flow with PKCE:
1.`GET /auth/oidc/start` — discovers provider, builds auth URL, stores CSRF/nonce/PKCE in session, redirects to IdP
2.`GET /auth/oidc/callback` — validates CSRF, exchanges code for tokens, verifies ID token, provisions user
Provider metadata is cached for 1 hour and invalidated when OIDC config changes.
**Group-to-role mapping:** The `oidc_admin_groups` config field lists OIDC group names (comma-separated) that grant the admin role. Groups are extracted from the `groups` claim in the ID token JWT payload.
**User provisioning order:**
1. Find existing `OidcLink` by issuer+sub → update claims, update role
2. Find existing `User` by email → create OidcLink, update role
3. Create new user (no password) + OidcLink
Stale links (pointing to deleted users) are cleaned up automatically.
-`translations!` macro in `phrases.rs` generates a `Translations` struct with static `EN` and `RU` instances
- Language resolution: `furu_lang` cookie → `Accept-Language` header → English default
-`I18n` is a cot request extractor — handlers receive it automatically
-`set_lang` endpoint (`/set-lang?lang=ru&next=/`) sets the cookie
### API (`src/api/`)
JSON API mounted at `/api`. Uses the same session cookie as HTML pages — works automatically for same-origin frontend requests (no CORS, no tokens needed).
Helpers in `api/mod.rs`:
-`json_ok(value)` — 200 with `application/json`
-`json_error(status, message)` — error response as `{"error": "..."}`
| Route | Method | Description |
|-------|--------|-------------|
| `/api/me` | GET | Current user (id, name, role) or 401 |