From 54ec72da171a80f1e4910324c953ad049609ae54 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 2 Nov 2026 20:21:21 -0800 Subject: [PATCH] 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. --- .../Dankserver/Audiobookshelf.md | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/serverAppConfigs-docs/Dankserver/Audiobookshelf.md b/serverAppConfigs-docs/Dankserver/Audiobookshelf.md index 4a43943..bb53df6 100644 --- a/serverAppConfigs-docs/Dankserver/Audiobookshelf.md +++ b/serverAppConfigs-docs/Dankserver/Audiobookshelf.md @@ -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 `/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,