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.
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:
Every signer gets user. Onboarding a listener is exactly "create the authentik account".
Only audiobookshelf_admins escalates, with admin emitted beforeuser: 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
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).
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.
# 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
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 main2026-11-03 02:19:14 -08:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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:
audiobookshelf_adminsakadmin,qa-e2e-adminaudiobookshelf_usersaudiobookshelfapplicationabs_groupsclaim for anyone in neither group[]→ ABS denies the sign-inSo 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.shnow provisions:user. Onboarding a listener is exactly "create the authentik account".audiobookshelf_adminsescalates, withadminemitted beforeuser: an admin's claimstays
["admin","user"], byte-identical to what this mapping has always emitted, because ABS'sgroup-tag matching has never been measured for order-sensitivity and the working admin login is
not the place to find out.
audiobookshelf_usersis deleted from the IdP, and theABS_OIDC_USERSknob that fed it isgone. A group that decides nothing is a trap for the next reader, who will assume it gates
something and start adding people to it.
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
audiobookshelfapplication.Two defects found by actually running the script
PYEOFheredoc contained backticks. An unquoted heredoccommand-substitutes its body, so bash ran
audiobookshelf_usersanduseras commands onevery 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).
AnonymousUserrow, then itsadminsuperuser. Proving the default on either says nothingabout 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 printsup to three real claims. (
is_superuseris a python property on authentik'sUser, not aqueryable field — a queryset filter on it raises
FieldError.)Verification (all live)
24-against the un-changed mapping: the standard identity leaves/loginandnever receives a token → 1 failed. The test cannot pass without the grant.
scripts/authentik-audiobookshelf-sso.sh:group: retired audiobookshelf_users,mapping: scope/expression updated (scope_name abs_groups), then the probe on three realhousehold accounts (
alex,artem,debo— inNon-Admin Users,jellyfin_users,openwebui_users, in no Audiobookshelf group) →{'abs_groups': ['user']};akadminandqa-e2e-admin→{'abs_groups': ['admin', 'user']}. Second run is idempotent(
mapping: already current,provider: asserted (nothing to change)) and shell-silent.npx playwright test specs/23… specs/24…→ 4 passed:qa-e2e(no group)lands with
type: "user",qa-e2e-adminstill lands withtype: "admin"../tests/run.shon the tree as pushed → ALL TESTS PASSED (824), includingthe
e2e_scaffoldseam that fails when a spec's number prefix belongs to no project (that iswhat makes
24-part of the deploy gate rather than a file that silently never runs).bash -non both scripts; the stored expression compiled and evaluated offline for theno-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
mainagree with the fleet; not merging it leaves the live claim ahead of therepo, and the rollback is: revert this commit, re-run the script, which rewrites the expression
and recreates nothing.
./scripts/deploy.sh dankclaw --service authentikis not needed — thescript is the applicator, and it was already run.
Build worktree:
/tmp/wt-ssogrant(branchfeat/audiobookshelf-sso-default-listener); the fleetstate 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
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.