# Access model: Wizarr provisions the ungated readers, one allow-list gates the rest

Users reach this stack over the `public` gateway and are not on the tailnet, so an Envoy extAuth gate is only usable where **no native client exists** — a native client presents an API key or device token, never a tinyauth cookie, and would be denied at the edge. Komga and Audiobookshelf therefore carry **no SecurityPolicy**; Shelfmark, Omnibus and BookOrbit's browser route share a **single allow-list** and self-register behind it. BookOrbit additionally splits its device endpoints (`/api/v1/{kobo,koreader,opds}/**`) onto a second, ungated HTTPRoute, because a Kobo can carry neither a tailnet membership nor a cookie.

**Komga is provisioned by Wizarr; Audiobookshelf is not.** The two ungated readers differ in what their native clients can do, so they are provisioned differently:

- **Komga** — its apps accept only username/password (not API keys), so users need a local credential. Wizarr multi-server invites create it, exactly as for Plex and Jellyfin. Wizarr is however a convenience, **not a hard requirement**: an OIDC-created user gets a random unseen password (`KomgaOAuth2UserServiceConfiguration.kt:76`) but can set their own via the non-admin Account page — `PATCH api/v2/users/me/password` carries no admin check and no old-password requirement (`UserController.kt:69`). Verified end to end: a non-admin set its own password (`204`) and `/opds/v1.2/catalog` accepted it immediately (`200`). That self-set password is what KOReader, Moon+ Reader and Panels need, since Komga's OPDS is `.httpBasic` against the account (`SecurityConfiguration.kt:106`) and an API key works **only** as an `X-API-Key` header — verified `401` when passed as the Basic password, and KOReader's OPDS plugin can send nothing but name/URL/username/password (`opdsbrowser.lua:411-425`).
  ⚠️ Give KOReader the **`/opds/v1.2/catalog`** URL. Per Komga's tested-client list, KOReader is supported on OPDS **v1** (with OpenSearch and PSE page streaming) and explicitly **not supported** on OPDS v2.
- **Audiobookshelf** — the ABS app performs a real OIDC PKCE login, so users self-serve with Google and need no local account. The app posts `redirect_uri=audiobookshelf://oauth`; ABS validates that scheme against its own `authOpenIDMobileRedirectURIs`, then asks the IdP to call back to `/auth/openid/mobile-redirect` and bounces to the app itself, so **both** `/auth/openid/callback` and `/auth/openid/mobile-redirect` must be trusted redirect URIs on the tinyauth client. With `authOpenIDAutoRegister`, a whitelisted user is created as type `user` with `accessAllLibraries` and `download` — no admin action at all.

## Consequences

- **Do not add a SecurityPolicy to Komga or Audiobookshelf.** It reads like a missing gate; adding one denies Mihon, Kobo Sync, KOReader and the Audiobookshelf app for every user.
- **Do not merge BookOrbit's two HTTPRoutes**, and do not gate `bookorbit-devices`. Those endpoints authenticate themselves — Kobo via a device token in the path, OPDS via per-user credentials. Verified from the public edge: `/api/v1/opds` answers `401` with `WWW-Authenticate: Basic realm="bookorbit OPDS"`, and a bad Kobo device token gets `401`.
- **OPDS credentials are self-serve; an admin never creates them.** `opds-user.controller.ts` is gated only on `Permission.OpdsAccess` with every method scoped to `@CurrentUser()`, and `opds_access` is already in the tinyauth provider's `defaultPermissionNames` — so an auto-provisioned user creates their own credential at `settings/opds`. Both live OIDC users hold `opds_access`, `library_download`, `kobo_sync`, `koreader_sync` and `book_dock_access`.
- **Self-registration is deliberately asymmetric**: OFF on Komga because it is ungated and password-based, ON for the apps behind the allow-list. Audiobookshelf is the exception — it is ungated *and* auto-registers, which is safe only because `TINYAUTH_OAUTH_WHITELIST` gates who can complete an OIDC login at the IdP. That whitelist is the sole authN gate for ABS; widening it grants audiobook access.
- **Do not enable Wizarr for Audiobookshelf**, and do not create local ABS accounts alongside OIDC. `authOpenIDMatchExistingBy` is unset, so an OIDC login never links to an existing local user — mixing the two produces duplicate ABS accounts for one person.
- **Revocation is credential-based, not allow-list-based.** Removing an address blocks new browser sign-ins but does not stop an already-issued Komga API key or a registered Kobo. Offboarding must revoke credentials in the app. ABS is the exception in the other direction: because its only credential is the OIDC login, removing the address from the whitelist does block new logins — but an existing ABS refresh token stays valid (ABS authenticates refreshes itself, not against the IdP), so deactivate the ABS user to cut access immediately.
- Onboarding costs one allow-list line per person, plus a Wizarr invite for Komga only, plus toggling `canRequest` in Omnibus, which hardcodes it to admins only at account creation.
