Bindery v1.0 introduces per-user library scoping: authors, books, downloads, quality profiles, and metadata profiles can be owned by a specific user, and users log in with their own credentials.
Root folders are not per-user. They are a single shared, admin-managed pool (Settings → Root Folders), and import destinations resolve per-author plus a global default — there is no per-user library directory today. Regular users don't get their own root folders. Per-user root folders are a tracked future enhancement, not current behaviour.
Important — data isolation is opt-in. Per-user data isolation is gated behind the
BINDERY_ENFORCE_TENANCYenvironment variable, which defaults OFF. With it unset (the default), any authenticated user can see and manage all data — Bindery behaves like a single-user instance regardless of how many accounts exist. To make users see only their own library, setBINDERY_ENFORCE_TENANCY=true.When enforcement is on, Bindery scopes:
- Tier-2 join-scoped resources — download queue, history, pending grabs, and the OPDS catalogue — to the requesting user.
- Per-user resources — each user's own authors, books, profiles, API key, password, and notification preferences. (Root folders are not per-user; see the note above.)
Admins see everything in list views. With enforcement on, an
adminis never filtered by ownership: the authors and books list endpoints (and the OPDS feed) return all users' libraries plus unowned/global rows, the same way an admin can already open any single item by ID. This is a shared library across admins, by design — it does not widen access, it makes lists consistent with per-item access. Non-admin (user) accounts stay isolated to their own rows plus unowned/global rows. Requests authenticated by API key (or indisabled/local-onlyauth modes) carry no user identity and are likewise unscoped.Role-based gating of admin-only configuration (indexers, download clients, user management, system settings) applies in both modes — that does not depend on
BINDERY_ENFORCE_TENANCY. The flag only controls whether library data is partitioned per user.
For upgrade instructions and migration steps, see docs/upgrade-v1.md.
Two roles exist: admin and user.
- The first account created through the
/setupwizard is alwaysadmin. - Users created by an admin via the Users page (the people icon in the header) default to the
userrole. - OIDC auto-provisioned users get the
userrole by default. To make the IdP authoritative for the admin role, setBINDERY_OIDC_ADMIN_GROUPso users in that IdP group are promoted automatically; see OIDC role mapping in docs/auth-oidc.md. - An admin can change any user's role at any time: Make admin / Make user on the Users page, or
PUT /api/v1/auth/users/{id}/rolewith{"role": "admin"}.
| Action | admin |
user |
|---|---|---|
| View and manage own authors/books/downloads | Yes | Yes |
| View and manage own quality/metadata profiles | Yes | Yes |
| Manage root folders (single shared/global pool) | Yes | No |
| Change own password and API key | Yes | Yes |
| Configure own notification preferences | Yes | Yes |
| View other users' library data | Yes | No |
| Manage other users' library data | Yes | No |
| Create, edit, delete users | Yes | No |
| Change user roles | Yes | No |
| Configure indexers | Yes | No |
| Configure download clients | Yes | No |
| Configure system-wide settings | Yes | No |
| View admin settings tabs in UI | Yes | No |
| Trigger system-level operations (backup, scan, migrate) | Yes | No |
Users → Add User (admin only, via the people icon in the header), or via API:
curl -X POST http://bindery:8787/api/v1/auth/users \
-H "X-Api-Key: <admin-api-key>" \
-H "Content-Type: application/json" \
-d '{"username": "alice", "password": "correct-horse-battery", "role": "user"}'OIDC users are created automatically on first login — no pre-creation needed unless BINDERY_OIDC_AUTO_PROVISION=false.
curl http://bindery:8787/api/v1/auth/users \
-H "X-Api-Key: <admin-api-key>"Returns: [{"id": 1, "username": "admin", "role": "admin", "last_seen": "..."}]. Passwords and OIDC credentials are never returned.
# Promote to admin
curl -X PUT http://bindery:8787/api/v1/auth/users/2/role \
-H "X-Api-Key: <admin-api-key>" \
-H "Content-Type: application/json" \
-d '{"role": "admin"}'curl -X DELETE http://bindery:8787/api/v1/auth/users/2 \
-H "X-Api-Key: <admin-api-key>"Deleting a user does not delete their library data. Authors, books, and downloads owned by that user remain in the database but become inaccessible through the normal UI. Reassign or remove the user's data before deleting:
- While signed in as an admin, review the account's content from the regular Authors and Books pages — admin list views are never ownership-filtered, so every user's rows are visible there.
- Delete anything that should not be kept.
- Then delete the user.
Everyone sees the General tab (appearance, downloads, file naming, storage, backup, security — including their own API key and password change) and About. The remaining tabs are admin-only, in four groups:
- Sources — Indexers, Download Clients, Notifications
- Library — Quality Profiles, Metadata Profiles, Root Folders
- Integrations — Calibre, Audiobookshelf, Grimmory, API Keys
- System — Import, Blocklist, Logs
Non-admins who open an admin tab are redirected back to General; admin API routes return 403. Users are managed on the dedicated Users page (the people icon in the header), not inside Settings.
v1.0 replaces the X-Requested-With header check with a proper double-submit CSRF token on all session-cookie-authenticated mutations.
Browser users: the UI handles this transparently.
API scripts using session cookies: fetch a token first:
TOKEN=$(curl -s -b "bindery_session=<value>" \
http://bindery:8787/api/v1/auth/csrf | jq -r .token)
curl -X POST http://bindery:8787/api/v1/author \
-b "bindery_session=<value>" \
-H "X-CSRF-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Ursula K. Le Guin"}'API scripts using X-Api-Key: CSRF is not required — API-key requests bypass the check entirely. Existing automation continues to work without changes.
- docs/troubleshooting-auth.md — consolidated symptom→cause→fix table for all auth phases
- docs/DEPLOYMENT.md#environment-variables —
BINDERY_ENFORCE_TENANCYand other runtime knobs
| Symptom | Cause | Fix |
|---|---|---|
| User B can see User A's authors or books via the API | BINDERY_ENFORCE_TENANCY is unset — this is the documented default behaviour, not a bug |
This is expected when tenancy enforcement is off: all authenticated users share one library view. To partition library data per user, set BINDERY_ENFORCE_TENANCY=true and restart. (Admins can access all users' data regardless of this flag, by design.) |
| Data remains after a user is deleted | DELETE /auth/users/{id} does not cascade to library data |
Reassign or delete the user's authors and books before deleting the user account (see "Deleting a user" above). |
403 Forbidden on an API call that worked before v1.0 |
Session-cookie mutations now require X-CSRF-Token |
Switch callers to X-Api-Key auth (CSRF-exempt), or add a GET /auth/csrf preflight to your script (see "CSRF tokens" above). |
| Admin locked out — no admin account exists or all admins deleted | User row has role='user' or all admin rows were removed |
Recover via direct DB update (no Bindery restart needed if you can write to the DB file): sqlite3 /config/bindery.db "UPDATE users SET role='admin' WHERE username='<your-username>';" — or in Kubernetes: kubectl exec deploy/bindery -- sqlite3 /config/bindery.db "UPDATE users SET role='admin' WHERE id=1;" |
OIDC user auto-provisioned as user but should be admin |
BINDERY_OIDC_ADMIN_GROUP not set, so the IdP group is never consulted |
Set BINDERY_OIDC_ADMIN_GROUP to the IdP group name (and BINDERY_OIDC_GROUP_CLAIM if your IdP emits groups under a non-default claim). To promote a single existing account without group mapping: PUT /api/v1/auth/users/{id}/role with {"role": "admin"}. |
| Non-admin user can reach admin settings page in UI | Browser cached a pre-v1.0 session or route bundle | Hard-refresh the page (Ctrl+Shift+R). If it persists, log out and back in. The backend enforces role checks regardless of what the UI renders. |