Every authentik account that signs in to Audiobookshelf becomes a listener #134

Merged
dank merged 3 commits from feat/audiobookshelf-sso-default-listener into main 2026-11-03 02:19:14 -08:00
Owner

Every authentik account that signs in to Audiobookshelf becomes a listener

Problem

Household accounts authenticated at the IdP successfully and were then refused by
Audiobookshelf — which reads as a broken login, not as a policy decision.

Live census before the change:

Object State
authentik group audiobookshelf_admins akadmin, qa-e2e-admin
authentik group audiobookshelf_users empty
authentik accounts (active) 19, incl. alex, artem, debo, eric, freya, kassie, kirby, mitch, nic, qa-e2e
Access policy on the audiobookshelf application none (only an implicit-consent authorization flow)
The abs_groups claim for anyone in neither group [] → ABS denies the sign-in

So the claim was the only gate, and it answered "no role" for every real person. Frank's ask:
every user with SSO access gets Audiobookshelf access by default.

Change — one expression

scripts/authentik-audiobookshelf-sso.sh now provisions:

groups = {g.name for g in user.groups.all()}
roles = []
if "audiobookshelf_admins" in groups:
    roles.append("admin")
roles.append("user")
return {"abs_groups": roles}
  • Every signer gets user. Onboarding a listener is exactly "create the authentik account".
  • Only audiobookshelf_admins escalates, with admin emitted before user: an admin's claim
    stays ["admin","user"], byte-identical to what this mapping has always emitted, because ABS's
    group-tag matching has never been measured for order-sensitivity and the working admin login is
    not the place to find out.
  • audiobookshelf_users is deleted from the IdP, and the ABS_OIDC_USERS knob that fed it is
    gone. A group that decides nothing is a trap for the next reader, who will assume it gates
    something and start adding people to it.
  • Revocation moves to the ABS side (disable or delete the user there). Documented on the page,
    with the consequence spelled out: since nothing gates the application in authentik, the IdP login
    is the library gate — if that is ever too wide, the lever is an access policy on the
    audiobookshelf application.

Two defects found by actually running the script

  1. Two comments inside the unquoted PYEOF heredoc contained backticks. An unquoted heredoc
    command-substitutes its body, so bash ran audiobookshelf_users and user as commands on
    every execution of this script and spliced the (empty) results into the python program — one
    of the two backticks predates this PR. Comments are not exempt, because bash does not know what
    a python comment is. The body now names things plainly, with a note at the heredoc explaining
    why it cannot simply be quoted (the body interpolates the variables above it).
  2. The new default-grant probe picked the wrong witness — first authentik's literal
    AnonymousUser row, then its admin superuser. Proving the default on either says nothing
    about the friend who logs in, so it walks non-superuser accounts, preferring the fleet's
    plain-user marker group (Non-Admin Users, owned by Infrastructure/Authentik.md), and prints
    up to three real claims. (is_superuser is a python property on authentik's User, not a
    queryable field — a queryset filter on it raises FieldError.)

Verification (all live)

  • Red before — 24- against the un-changed mapping: the standard identity leaves /login and
    never receives a token → 1 failed. The test cannot pass without the grant.
  • Provision — scripts/authentik-audiobookshelf-sso.sh: group: retired audiobookshelf_users,
    mapping: scope/expression updated (scope_name abs_groups), then the probe on three real
    household accounts (alex, artem, debo — in Non-Admin Users, jellyfin_users,
    openwebui_users, in no Audiobookshelf group) → {'abs_groups': ['user']}; akadmin and
    qa-e2e-admin → {'abs_groups': ['admin', 'user']}. Second run is idempotent
    (mapping: already current, provider: asserted (nothing to change)) and shell-silent.
  • Green after — npx playwright test specs/23… specs/24… → 4 passed: qa-e2e (no group)
    lands with type: "user", qa-e2e-admin still lands with type: "admin".
  • Static suite — ./tests/run.sh on the tree as pushed → ALL TESTS PASSED (824), including
    the e2e_scaffold seam that fails when a spec's number prefix belongs to no project (that is
    what makes 24- part of the deploy gate rather than a file that silently never runs).
  • bash -n on both scripts; the stored expression compiled and evaluated offline for the
    no-group / admin / stray-group / unrelated-group cases before anything was written to the IdP.

Ordering note for the merge

The IdP is already carrying this mapping (that is where the green run above came from), so
merging makes main agree with the fleet; not merging it leaves the live claim ahead of the
repo, and the rollback is: revert this commit, re-run the script, which rewrites the expression
and recreates nothing. ./scripts/deploy.sh dankclaw --service authentik is not needed — the
script is the applicator, and it was already run.

Build worktree: /tmp/wt-ssogrant (branch feat/audiobookshelf-sso-default-listener); the fleet
state above was applied by running the script from that tree.


cwd: /home/blank/.omp/wt/wt-20261030-163847-6d1fc1f · session: 01a18f2e-18b1-7573-bcd5-ac99636f8bcb

# Every authentik account that signs in to Audiobookshelf becomes a listener ## Problem Household accounts authenticated at the IdP successfully and were then refused by Audiobookshelf — which reads as a broken login, not as a policy decision. Live census before the change: | Object | State | |---|---| | authentik group `audiobookshelf_admins` | `akadmin`, `qa-e2e-admin` | | authentik group `audiobookshelf_users` | **empty** | | authentik accounts (active) | 19, incl. alex, artem, debo, eric, freya, kassie, kirby, mitch, nic, qa-e2e | | Access policy on the `audiobookshelf` application | none (only an implicit-consent authorization flow) | | The `abs_groups` claim for anyone in neither group | `[]` → ABS denies the sign-in | So the claim was the *only* gate, and it answered "no role" for every real person. Frank's ask: every user with SSO access gets Audiobookshelf access by default. ## Change — one expression `scripts/authentik-audiobookshelf-sso.sh` now provisions: ```python groups = {g.name for g in user.groups.all()} roles = [] if "audiobookshelf_admins" in groups: roles.append("admin") roles.append("user") return {"abs_groups": roles} ``` - **Every signer gets `user`.** Onboarding a listener is exactly "create the authentik account". - **Only `audiobookshelf_admins` escalates**, with `admin` emitted *before* `user`: an admin's claim stays `["admin","user"]`, byte-identical to what this mapping has always emitted, because ABS's group-tag matching has never been measured for order-sensitivity and the working admin login is not the place to find out. - **`audiobookshelf_users` is deleted from the IdP**, and the `ABS_OIDC_USERS` knob that fed it is gone. A group that decides nothing is a trap for the next reader, who will assume it gates something and start adding people to it. - **Revocation moves to the ABS side** (disable or delete the user there). Documented on the page, with the consequence spelled out: since nothing gates the application in authentik, the IdP login *is* the library gate — if that is ever too wide, the lever is an access policy on the `audiobookshelf` application. ## Two defects found by actually running the script 1. **Two comments inside the unquoted `PYEOF` heredoc contained backticks.** An unquoted heredoc command-substitutes its body, so bash ran `audiobookshelf_users` and `user` as commands on **every execution** of this script and spliced the (empty) results into the python program — one of the two backticks predates this PR. Comments are not exempt, because bash does not know what a python comment is. The body now names things plainly, with a note at the heredoc explaining why it cannot simply be quoted (the body interpolates the variables above it). 2. **The new default-grant probe picked the wrong witness** — first authentik's literal `AnonymousUser` row, then its `admin` superuser. Proving the default on either says nothing about the friend who logs in, so it walks non-superuser accounts, preferring the fleet's plain-user marker group (`Non-Admin Users`, owned by *Infrastructure/Authentik.md*), and prints up to three real claims. (`is_superuser` is a python property on authentik's `User`, not a queryable field — a queryset filter on it raises `FieldError`.) ## Verification (all live) - **Red before** — `24-` against the un-changed mapping: the standard identity leaves `/login` and never receives a token → **1 failed**. The test cannot pass without the grant. - **Provision** — `scripts/authentik-audiobookshelf-sso.sh`: `group: retired audiobookshelf_users`, `mapping: scope/expression updated (scope_name abs_groups)`, then the probe on three real household accounts (`alex`, `artem`, `debo` — in `Non-Admin Users`, `jellyfin_users`, `openwebui_users`, in no Audiobookshelf group) → `{'abs_groups': ['user']}`; `akadmin` and `qa-e2e-admin` → `{'abs_groups': ['admin', 'user']}`. Second run is idempotent (`mapping: already current`, `provider: asserted (nothing to change)`) and shell-silent. - **Green after** — `npx playwright test specs/23… specs/24…` → **4 passed**: `qa-e2e` (no group) lands with `type: "user"`, `qa-e2e-admin` still lands with `type: "admin"`. - **Static suite** — `./tests/run.sh` on the tree as pushed → **ALL TESTS PASSED (824)**, including the `e2e_scaffold` seam that fails when a spec's number prefix belongs to no project (that is what makes `24-` part of the deploy gate rather than a file that silently never runs). - `bash -n` on both scripts; the stored expression compiled and evaluated offline for the no-group / admin / stray-group / unrelated-group cases before anything was written to the IdP. ## Ordering note for the merge The IdP is already carrying this mapping (that is where the green run above came from), so merging makes `main` agree with the fleet; **not** merging it leaves the live claim ahead of the repo, and the rollback is: revert this commit, re-run the script, which rewrites the expression and recreates nothing. `./scripts/deploy.sh dankclaw --service authentik` is not needed — the script is the applicator, and it was already run. Build worktree: `/tmp/wt-ssogrant` (branch `feat/audiobookshelf-sso-default-listener`); the fleet state above was applied by running the script from that tree. --- cwd: /home/blank/.omp/wt/wt-20261030-163847-6d1fc1f · session: 01a18f2e-18b1-7573-bcd5-ac99636f8bcb
dank added 3 commits 2026-11-03 02:10:35 -08:00
The abs_groups claim decided the listener role from membership of
audiobookshelf_users, a group nobody was ever added to, while the ABS
application carries no access policy of its own. The result: seventeen
household accounts authenticated at the IdP successfully and were then
refused by the app — a policy decision that reads as a broken login.

The claim now answers ["user"] for every signer and appends "admin" only
for audiobookshelf_admins, so the IdP login is the gate and onboarding a
listener is exactly "create the authentik account". Admins keep the exact
claim they always received, ["admin","user"], in that order: ABS's
group-tag matching has never been measured for order-sensitivity and the
working admin login is not where to find out. audiobookshelf_users is
deleted rather than left empty — a group that decides nothing is a trap
for the next reader, who will assume it gates something.

Two side fixes found by running the script:
 - Two comments inside the unquoted PYEOF heredoc carried backticks, so
   bash command-substituted them locally on EVERY run ("audiobookshelf_users:
   command not found", "user: command not found") and spliced the output
   into the python program. Names in that body are plain now, with a note
   on why. A quoted heredoc is not an option: the body interpolates.
 - The default-grant probe picked authentik's AnonymousUser row, then its
   admin superuser. It now walks real non-superuser accounts, preferring
   the fleet's plain-user marker group, and prints up to three claims.
spec 24- signs in through SSO as qa-e2e — an identity in no Audiobookshelf
group — and must land inside the app with type "user". It is the counterpart
to 23-, which pins the admin escalation: together they catch both directions
of a scope-mapping regression (household locked out, or household made site
admin). Verified red against the pre-change mapping — the identity never
received a token — and green after re-provisioning: 4 passed.

Joined to the standard project, which is the only project whose identity
observes the default.
Rewrite the listener onboarding, the role row, and the fail-closed law: the
claim grants every signer, revocation happens on the ABS side (disable or
delete the user), and what actually guards the library is who can reach the
audiobookshelf application in authentik. Name both e2e gates in Verification.
dank merged commit d3aedd1837 into main 2026-11-03 02:19:14 -08:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dank/serverAppConfigs#134