# CLAUDE.md — authelia Guidance for Claude Code in this repo. Read before editing. ## What this is A standalone [Authelia](https://www.authelia.com/) deployment — SSO portal, 2FA, and (optional) OIDC provider — as a Tailscale sidecar, plugged into the shared tailnet **Postgres** (storage) and **Redis** (sessions), with the shared **SMTP relay** for reset/2FA mail. Sibling to `tailwart`; same pattern, different job. Domain: `infinidim.net` (portal `auth.infinidim.net`). Self-contained and **outside** any upstream repo (own `.env`, compose, config, ACL snippet). Reads only the tailnet at runtime. ## Architecture ``` main box Caddy (tag:reverse-proxy) tailnet-only auth ┌────────────────────────────┐ ┌───────────────────────┐ │ auth.infinidim.net ───────┼──────────────▶│ ts-authelia sidecar │ │ reverse_proxy :9091 │ tailnet │ authelia (no WAN, no │ │ protected vhosts: │ │ host ports) :9091 │ │ forward_auth → :9091 │ └──────────┬─────────────┘ └────────────────────────────┘ │ ┌───────┴───────┐ ▼ ▼ ▼ Postgres Redis SMTP relay ``` - **Sidecar** (`tag:authelia`), `network_mode: service:ts-authelia`, no host ports. - Authelia listens `:9091` on the tailnet. The **main box Caddy** (layer 7, not a separate edge) fronts the portal and wires `forward_auth` for protected services — see `caddy-forward-auth.snippet`. This is ordinary L7, unlike tailwart's L4 mail edge. - Storage → Postgres (`authelia` role/db), sessions → Redis (logical DB `AUTHELIA_REDIS_DB`), mail → shared relay. ## The `.env` contract `.env` (gitignored) is the whole surface. Secrets and infra hostnames reach Authelia as `AUTHELIA_*` **env overrides** (set in `docker-compose.yml`), which take precedence over `config/configuration.yml`. That keeps the committed yml free of secrets and MagicDNS names. The yml holds only non-secret structure (access_control, session.cookies, totp, regulation). ## Prerequisites (shared tailnet infra) 1. Postgres role+db: `authelia` / `AUTHELIA_DB_NAME`. Create via a one-off `CREATE ROLE authelia LOGIN PASSWORD '…'; CREATE DATABASE authelia OWNER authelia;` against `the-record-prod` (the federatedSocial shared Postgres). 2. Redis: nothing to create — uses logical DB index `AUTHELIA_REDIS_DB` (4) so it won't collide with the fediverse apps or tailwart (which uses 3). 3. `config/users_database.yml`: at least one user with an argon2id hash: `docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password 'PASS'`. 4. Admin console: assign `tag:authelia` to the OAuth client (Devices/Core + Keys/AuthKeys) and add `acl-snippet.hujson` to the policy. 5. Main box Caddy: add `caddy-forward-auth.snippet` (portal vhost + the `(authelia)` import) and a cert for `auth.infinidim.net`. ## Pitfalls - **Authelia config schema drifts hard between minor versions** (4.37→4.38 moved session to `cookies[]`, added `identity_validation`, changed `storage.postgres` to `address`). The image is **pinned to 4.39.20** and the yml passes `authelia config validate` against it. Re-validate before bumping the tag: `docker run --rm -e ... -v ./config:/config:ro authelia/authelia: authelia config validate`. - **Don't put secrets in `configuration.yml`.** Use the `AUTHELIA_*` env path. - **Redis DB index collisions.** tailwart=3, authelia=4. Keep them distinct. - **Postgres password drift.** Role passwords only apply on `CREATE`/`ALTER`; test auth over the **tailnet** (scram), never `127.0.0.1` (that pg_hba line is `trust` and accepts any password — it'll lie to you). - **forward_auth is layer 7**, on the main Caddy. Don't confuse it with tailwart's L4 edge — different mechanism entirely. ## What not to do - Don't write into `/opt/federatedSocial` (read its `.env` if needed). - Don't add `ports:` to the Authelia container — the main Caddy is the only public path in. - Don't commit `.env`. - Don't break the sidecar netns boundary.