--- name: authentik-provision-fleet-user description: "Provision or audit an Authentik user on Dankclaw (auth.franksdank.cloud): ak shell ORM drive, group set, behavioral login + entitlement verify, docs census record, stale-claim caveat." --- # Provision an Authentik user on Dankclaw Source of truth: `serverAppConfigs-docs/Infrastructure/Authentik.md` ("User provisioning playbook"). Authentik objects are NOT repo-managed — no deploy, no file edits. 1. Inspect first, mirror the newest human user's groups: ```bash ssh dankclaw 'podman exec -i authentik-server ak shell' <<'EOF' 2>&1 | sed -n '/objects imported/,$p' from authentik.core.models import User, Group for u in User.objects.all().order_by("username"): print(u.username, u.email, sorted(u.groups.values_list("name", flat=True))) print(sorted(Group.objects.values_list("name", flat=True))) EOF ``` 2. Create (idempotent guard; password goes in the heredoc = stdin, never argv, so it never lands in `ps`): ```python from authentik.core.models import User, Group u = User.objects.filter(username="X").first() if not u: u = User(username="X", email="X@franksdank.cloud", name="X", is_active=True) u.set_password("...") u.save() for g in ("Non-Admin Users", "jellyfin_users", "openwebui_users"): # human-user precedent: ridley u.groups.add(Group.objects.get(name=g)) ``` 3. Verify through the exact PasswordStage code path: ```python from django.contrib.auth import authenticate assert authenticate(request=None, username="X", password="...") assert authenticate(request=None, username="X", password="nope") is None ``` 4. Verify the downstream claims resolve — `app_entitlements` needs an `Application` instance (look it up by slug, `open-webui`), and compare against the precedent user: ```python from authentik.core.models import Application, User app = Application.objects.get(slug="open-webui") for n in ("ridley", "X"): u = User.objects.get(username=n) print(n, sorted(e.name for e in u.app_entitlements(app)), u.is_superuser, u.path) ``` 5. Stale-claim check + docs record (repo law: the vault is the source of truth): ```bash ssh dankclaw "podman exec authentik-postgres psql -U authentik -d authentik -tAc \ \"SELECT count(*) FROM authentik_providers_proxy_proxysession WHERE session_data->'claims'->>'preferred_username' IN ('X')\"" ``` Then commit two doc edits (NEVER write the password into any file): the census sentence in `Infrastructure/Authentik.md` (human-user template bullet — append the new `pk`s + creation date) and a dated entry in `serverAppConfigs-docs/log.md` (what was created, groups, how it was verified, proxysession count, downstream lag). `./tests/run.sh` must pass before commit (docs completeness + link-integrity seams). ## Rules - No `User.create_user`; `is_superuser` is a group-derived property, not a constructor kwarg (raises `TypeError`). `path` defaults to `users`. - Guarded-vhost access = any authenticated user (all proxy providers use implicit-consent); groups are consumed downstream: `homepage-admins` (Caddy `/admin` header gate), `openwebui_users`/`_admins` (roles claim), `jellyfin_users`/`_admins` (LDAP outpost). EXCLUDE admin groups from a plain user; ask before granting `homepage-admins`. - Group add after a login → Caddy RBAC can 403 on the outpost's cached claims; purge that user's rows: `DELETE FROM authentik_providers_proxy_proxysession WHERE session_data->'claims'->>'preferred_username' = 'X';` (in the `authentik-postgres` container) — next visit re-authorizes silently. Groups set BEFORE any login → nothing to purge (count is 0). - Jellyfin visibility rides the LDAP outpost cache; expect a lag. OpenWebUI mints a first-time SSO login as role `pending`. - Look up flows by slug, not name. Do not rotate `AUTHENTIK_SECRET_KEY` to fix claim issues.