Every authentik account that signs in to Audiobookshelf becomes a listener #134
@@ -98,9 +98,10 @@ Two ways in, both per listener:
|
|||||||
- **Local account** — **Settings → Users → Add User**: type `user`, tick only the libraries
|
- **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
|
they may hear, and leave `download` off unless they need offline listening. Hand them the
|
||||||
server address above and their own password.
|
server address above and their own password.
|
||||||
- **Household SSO** — create the authentik account, add it to `audiobookshelf_users`, and the
|
- **Household SSO** — create the authentik account, and that is the whole of it: the first
|
||||||
first **Sign in with Authentik** auto-registers the ABS account (`authOpenIDAutoRegister`);
|
**Sign in with Authentik** auto-registers the ABS account (`authOpenIDAutoRegister`) as a
|
||||||
a signer in neither group is denied on purpose, after a successful authentication.
|
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`
|
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
|
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 |
|
| 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) |
|
| 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 |
|
| 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) |
|
| 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 |
|
| 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 | `<base>/auth/openid/callback` **and** `<base>/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" |
|
| Redirect URIs on the RP | `<base>/auth/openid/callback` **and** `<base>/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,
|
With `authOpenIDAutoRegister` on, a first sign-in creates the account and the claim alone
|
||||||
a first sign-in creates the account, and the claim alone decides the type. A user in
|
decides its type — and the claim always carries `user`, so **the IdP is now the library gate**:
|
||||||
neither group is refused after a successful authentication — that is the mapping working,
|
any authentik account that can reach this application becomes a listener. That is the intent
|
||||||
not a broken login.
|
(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
|
## 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
|
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);
|
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);
|
`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
|
and the live Playwright gates `tests/e2e/specs/23-audiobookshelf-sso.spec.ts` and
|
||||||
`tests/e2e/run.sh` at the end of every `deploy.sh` — the `admin` project signs in as
|
`tests/e2e/specs/24-audiobookshelf-sso-default-listener.spec.ts`, run by `tests/e2e/run.sh` at
|
||||||
`qa-e2e-admin` against `http://192.168.0.235:8000/audiobookshelf` and asserts the two things
|
the end of every `deploy.sh`. `23-` (the `admin` project) signs in as `qa-e2e-admin` against
|
||||||
no stub can see: every `.js` the app loads arrives as JavaScript (a bundle built for the wrong
|
`http://192.168.0.235:8000/audiobookshelf` and asserts the two things no stub can see: every
|
||||||
base path arrives as `text/html`, which boots nothing), and `/api/me` answers 200 with
|
`.js` the app loads arrives as JavaScript (a bundle built for the wrong base path arrives as
|
||||||
`type: "admin"` — true only while grant types, the OpenID scope mappings and the group claim
|
`text/html`, which boots nothing), and `/api/me` answers 200 with `type: "admin"` — true only
|
||||||
all hold together.
|
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
|
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
|
carries an **exemption** assertion where it used to carry a guarded one: an anonymous raw
|
||||||
|
|||||||
Reference in new issue
Block a user