docs(audiobookshelf): the two IdP laws the dead login taught, and the gate

Records that an ORM-created provider has no grant types and that the claim
mapping is not the whole property_mappings list, with the failure signatures
to recognise; names qa-e2e-admin as the admin-group fixture and the new
Playwright spec as the live verification.
This commit is contained in:
Frank committed 2026-11-02 20:21:21 -08:00
1 parent d82b8efdd7
commit 54ec72da17
1 file changed
+27 -2
@@ -97,12 +97,15 @@ the local form, and the phone app offers SSO on its server-address screen. The l
| 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) |
| Admin group members | `akadmin` (operator stand-in) and `qa-e2e-admin` — the second is the identity the deploy gate signs in as, so the script re-adds it on every run; drop it and the gate fails, not a phone |
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.
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.
## Laws
- **Speak the image's env names, not the README's.** The server reads `PORT`,
@@ -128,6 +131,21 @@ not a broken login.
provider's registered URI must equal `<origin><subfolder>/auth/openid/callback`, so each
script derives the value from one variable of its own (`ABS_URL`, `ABS_BASE`) and
`tests/audiobookshelf_init.bats` pins what reaches the wire.
- **An ORM-created provider has no grant types.** authentik 2026.8 defaults `grant_types` to
`[]`, and `/application/o/authorize/` then refuses the code flow with oauthlib's generic
"The request is otherwise malformed" — the wire names nothing, the server log line that names
it is `Invalid grant_type for provider`. Worse, the redirect-URI check runs *first*, so a
provider carrying both faults shows only "Redirect URI Error" until the URI is fixed, and the
second wall appears after the fix ships (measured 2026-11-02: the browser reached authentik,
got `code`+`state`, and ABS still bounced to `/login?error=undefined`). The script sets
`authorization_code` + `refresh_token`; a blueprint re-import reverts the row silently.
- **The claim mapping is not the whole `property_mappings` list.** A provider holding only the
custom mapping grants a token scoped shorter than the scopes ABS requests
(`openid profile email abs_groups`): the code is issued and exchanged normally, and ABS's
`/userinfo` call then dies `insufficient_scope (The request requires higher privileges than
provided by the access token)` — a fault that reads like an ABS bug and is an IdP row. Attach
the built-in OpenID `openid`/`profile`/`email` mappings beside the claim mapping, which is
what every working RP on this IdP carries (measured 2026-11-02).
- **The audio bind stays `:ro`.** ABS writes nothing that belongs beside the
audio: its library, covers, progress and sidecar metadata all land in
`/metadata` on the SSD. A writable bind would put stray writes on the spindle
@@ -248,8 +266,15 @@ not a broken login.
health check, the base path the shipped SPA needs), `tests/audiobookshelf_init.bats` (the
bootstrapper against a stub of the API above: create-once, idempotent re-run, no secret in a
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) and
`tests/media_overlay.bats` (only [[Plex]] and [[Jellyfin]] may reference the metadata view).
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.
Five book folders hold media no scanner can read, so they stay out of the library:
three 2024 Nebula nominees in the `_unsorted` box (two `.m4b` with no `moov` atom,