From 66766f23ef8032294efe7621d21d4a10f38ebbd3 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 3 Nov 2026 02:06:20 -0800 Subject: [PATCH] docs(audiobookshelf): the IdP is the library gate now 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. --- .../Dankserver/Audiobookshelf.md | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/serverAppConfigs-docs/Dankserver/Audiobookshelf.md b/serverAppConfigs-docs/Dankserver/Audiobookshelf.md index d276d6e..312a0c1 100644 --- a/serverAppConfigs-docs/Dankserver/Audiobookshelf.md +++ b/serverAppConfigs-docs/Dankserver/Audiobookshelf.md @@ -98,9 +98,10 @@ Two ways in, both per listener: - **Local account** — **Settings → Users → Add User**: type `user`, tick only the libraries they may hear, and leave `download` off unless they need offline listening. Hand them the server address above and their own password. -- **Household SSO** — create the authentik account, add it to `audiobookshelf_users`, and the - first **Sign in with Authentik** auto-registers the ABS account (`authOpenIDAutoRegister`); - a signer in neither group is denied on purpose, after a successful authentication. +- **Household SSO** — create the authentik account, and that is the whole of it: the first + **Sign in with Authentik** auto-registers the ABS account (`authOpenIDAutoRegister`) as a + listener, because the claim grants that role to every signer. To shut a listener off, + disable or delete their user in Audiobookshelf — no IdP group membership can withhold it. Nothing rate-limits `/audiobookshelf/api/login` — this Caddy build carries no `rate_limit` module and ABS 2.37.2 ships no lockout — so the form is a guessing target exactly like @@ -116,20 +117,24 @@ the local form, and the phone app offers SSO on its server-address screen. The l | Piece | Owner | |-------|-------| -| Provider `audiobookshelf`, the two groups, the scope mapping | `scripts/authentik-audiobookshelf-sso.sh` — re-run it after an authentik rebuild, because this state lives in the IdP's database and a blueprint re-import reverts it silently | +| Provider `audiobookshelf`, the admin group, the scope mapping | `scripts/authentik-audiobookshelf-sso.sh` — re-run it after an authentik rebuild, because this state lives in the IdP's database and a blueprint re-import reverts it silently | | Client secret | `ABS_OIDC_CLIENT_SECRET` in `~/.config/secrets/audiobookshelf` on Dankserver (mode 600; never in the repo, a URL or an argv) | | The ABS half: issuer, the four IdP URLs, claim, auto-register | `scripts/audiobookshelf-init.sh`, armed automatically when that secret exists in the credential file | -| Role at first SSO sign-in | the `abs_groups` claim: `audiobookshelf_admins` → `admin`, `audiobookshelf_users` → `user`, in neither → denied (fail closed) | +| Role at first SSO sign-in | the `abs_groups` claim: every signer gets `user`; `audiobookshelf_admins` adds `admin`. Nothing in the IdP can deny the listener role — the login itself is the gate (law since 2026-11-03; the `audiobookshelf_users` group was retired, it had zero members and every household account was being refused) | | Admin group members | `akadmin` (the operator's own authentik admin account — sign in as it and the claim grants site admin) and `qa-e2e-admin` (the identity the deploy gate signs in as, so the script re-adds it every run; drop it and the gate fails, not a phone) | | Guard state of the door | none since 2026-11-03: proxy provider `audiobooks-proxy` and application "Audiobookshelf Guard" were deleted and the embedded outpost un-claimed them, because that chain answers a native client with challenge HTML ([[Caddy]] *Unguard a vhost* for the order). The SSO relying party `audiobookshelf` above is a different object and stays | | Redirect URIs on the RP | `/auth/openid/callback` **and** `/auth/openid/mobile-redirect` for **both** origins (LAN address and public name): the app sends the mobile URI belonging to the door it was configured with, and STRICT matching answers an unregistered one with "Redirect URI Error" | -The IdP saying yes is not the same as being allowed in: with `authOpenIDAutoRegister` on, -a first sign-in creates the account, and the claim alone decides the type. A user in -neither group is refused after a successful authentication — that is the mapping working, -not a broken login. +With `authOpenIDAutoRegister` on, a first sign-in creates the account and the claim alone +decides its type — and the claim always carries `user`, so **the IdP is now the library gate**: +any authentik account that can reach this application becomes a listener. That is the intent +(the household signs in with the accounts it already has), which makes two things worth +checking when they change: whether authentik's own invitation/signup flow stays something only +Frank drives, and whether an access policy should be bound to the `audiobookshelf` application +to narrow who may even reach the login. Revoking one person is a disable/delete of their ABS +user; revoking everyone is that policy. -Both halves — authentik's provider row and ABS's settings — are asserted end to end by the Playwright gate named in *Verification*: a sign-in as `qa-e2e-admin` that must land inside the app with site-admin rights. +Both halves — authentik's provider row and ABS's settings — are asserted end to end by the Playwright gate named in *Verification*. `23-` signs in as `qa-e2e-admin` and must land inside the app with site-admin rights; `24-` signs in as the standard identity, which is in no Audiobookshelf group, and must land as a listener. Together they pin the escalation and the default: a mapping that stops granting silently locks the household out, and a mapping that over-grants turns the household into site admins. ## Laws @@ -315,13 +320,16 @@ bootstrapper against a stub of the API above: create-once, idempotent re-run, no URL, refusal by URL when the server does not answer, the deployer calling it only after the health gate, and the SSO arm sending the mounted base path as the redirect subfolder); `tests/media_overlay.bats` (only [[Plex]] and [[Jellyfin]] may reference the metadata view); -and the live Playwright gate `tests/e2e/specs/23-audiobookshelf-sso.spec.ts`, run by -`tests/e2e/run.sh` at the end of every `deploy.sh` — the `admin` project signs in as -`qa-e2e-admin` against `http://192.168.0.235:8000/audiobookshelf` and asserts the two things -no stub can see: every `.js` the app loads arrives as JavaScript (a bundle built for the wrong -base path arrives as `text/html`, which boots nothing), and `/api/me` answers 200 with -`type: "admin"` — true only while grant types, the OpenID scope mappings and the group claim -all hold together. +and the live Playwright gates `tests/e2e/specs/23-audiobookshelf-sso.spec.ts` and +`tests/e2e/specs/24-audiobookshelf-sso-default-listener.spec.ts`, run by `tests/e2e/run.sh` at +the end of every `deploy.sh`. `23-` (the `admin` project) signs in as `qa-e2e-admin` against +`http://192.168.0.235:8000/audiobookshelf` and asserts the two things no stub can see: every +`.js` the app loads arrives as JavaScript (a bundle built for the wrong base path arrives as +`text/html`, which boots nothing), and `/api/me` answers 200 with `type: "admin"` — true only +while grant types, the OpenID scope mappings and the group claim all hold together. `24-` (the +`standard` project) signs in as `qa-e2e`, an account in no Audiobookshelf group, and must come +back `type: "user"`: the default grant that lets the household in, so a scope-mapping +regression surfaces as a failed sign-in in the gate instead of as a friend's complaint. The public door is pinned by the guard audit `tests/e2e/specs/14-guard.spec.ts`, which now carries an **exemption** assertion where it used to carry a guarded one: an anonymous raw