Note: This release ships the full v0.6.0 authentication-overhaul feature set. The
v0.6.0tag itself failed its GoReleaser cross-compile step (WindowsGOOScouldn't resolvesyscall.Stat_tin the SQLite error-path helper) so no binaries or:0.6.0Docker tag were ever published under it. v0.6.1 contains the one-line build-tag fix on top of identical runtime behaviour.
Authentication overhaul
Replaces the single-env-var API key gate with a full Sonarr-parity auth model. Upgrading from v0.5.x: the first launch after upgrade detects no user, redirects to /setup, and you create an admin account. BINDERY_API_KEY is still honoured as a seed for the new DB-stored key so existing integrations keep working on restart; after that it is inert (the key can be regenerated in-app).
Added
- Password-based login — first-run
/setupflow creates a single administrator account. Passwords hashed with argon2id (OWASP 2024 parameters). Minimum 8 characters enforced client-side. - Signed session cookies — self-contained HMAC-SHA256 cookies (no server-side session table).
bindery_sessionisHttpOnly+SameSite=Lax. 30-day "remember me" or 12-hour default.Secureis intentionally left unset because TLS is usually terminated upstream (Traefik); front with a proxy that addsStrict-Transport-Securityif you need strict HTTPS-only cookies. - Three auth modes —
enabled(always require login),local-only(bypass auth for RFC1918 / loopback / link-local / IPv6 ULA),disabled(no auth — only for trusted reverse-proxy deployments). Toggle in Settings → General → Security. Sonarr v4 parity. - Per-account API key — auto-generated on first boot, visible/regenerable in the Settings Security panel. Accepts
X-Api-Keyheader or?apikey=query param. Independent of the session cookie so scripts,curl, Tautulli, etc. work without cookies. - Login rate limiting — per-IP sliding window, 5 failures / 15 minutes, returns
429. Blocks credential-stuffing on internet-exposed deployments. - New endpoints —
GET /auth/status,POST /auth/login,POST /auth/logout,POST /auth/setup,GET /auth/config,POST /auth/password,POST /auth/apikey/regenerate,PUT /auth/mode.
Changed
/api/v1/*is now authenticated by default (previously optional). Health, auth status/login/logout/setup, and the setup flow itself are exempt.BINDERY_API_KEYis now a seed-only bootstrap variable. If set on first boot, the generated key matches it; on subsequent boots the stored DB value wins. Setting the env var on an already-initialised instance has no effect.auth.api_key,auth.session_secret, andauth.modesettings are filtered out of the genericGET /settingandGET /setting/{key}endpoints — they are readable only via/auth/configfor the authenticated admin.- Frontend: added
/loginand/setuproutes, anAuthProvider+AuthGuardthat redirect unauthenticated users, a "Sign out" action in the header, and a Security section in Settings → General.
Fixed
- Middleware was treating
/auth/statusas an unauth-allowed path before verifying the session cookie, so the endpoint always reportedauthenticated: false. Valid logins still set the cookie correctly but the UI bounced right back to/login. Cookie verification now runs for every request; the unauth-allow list only controls the 401 rejection. - Login and setup forms now read values via
FormDataon submit instead of relying on React-controlled state. Browser password-manager autofill populatesinput.valuewithout firingonChange, which left React state empty and silently disabled the submit button.
PUID/PGID startup sanity check (#13)
Bindery ships on distroless/static-debian12:nonroot — no shell, no gosu, so the container cannot switch user at runtime the way LinuxServer.io images do. The common failure mode is: operator sets PUID=1000 / PGID=1000 in their .env expecting LSIO semantics, but forgets the matching --user / runAsUser; Bindery silently runs as UID 65532, and the first write to /config or the library mount fails with an opaque permission denied.
This release turns that into a loud, actionable startup error. When BINDERY_PUID or BINDERY_PGID is set but does not match os.Getuid() / os.Getgid(), Bindery logs the mismatch along with the exact docker run --user, docker-compose user:, and securityContext.runAsUser snippets that would fix it, then exits non-zero. Leaving both variables unset preserves the previous behaviour (no check, runs as the distroless default UID). Non-Linux builds skip the check entirely (Getuid / Getgid return -1 on Windows).
The README's Configuration → Running as a specific UID/GID section documents the Docker / compose / k8s patterns end-to-end.
A follow-up ticket (to be opened after v0.6.0) tracks the larger LSIO-style variant image with a gosu entrypoint that actually switches user at runtime — the Bindery team didn't want to ship a second image this cycle.
Author delete can sweep files (#15)
DELETE /api/v1/author/{id}?deleteFiles=true now walks every book's file_path and removes it from disk before the DB cascade takes the rows out. Paths are collected before the delete (the cascade wipes the book rows that hold them, so a post-delete walk would find nothing). Per-path errors are logged but don't abort the response — the author is already gone and a partial sweep is better than rolling the whole thing back.
The UI confirm dialogs on the Author list and detail pages peek at each author's books, name the file/folder count in the confirmation message, and pass deleteFiles=true when the user OKs. Authors with no files on disk get the old plain confirm.
Closes the orphan-files gap reported against the Jared Diamond delete after #9 landed.
Metadata language filter (#14)
Foreign-language works from OpenLibrary/Hardcover/Google Books were landing in the library regardless of the user's preferred language. The metadata_profiles table (seeded in migration 003) already carried allowed_languages='eng' by default, but nothing consulted it — author-book ingestion filtered against a separate global search.preferredLanguage setting, and authors were never linked to a profile.
Added
- Author record now carries
metadata_profile_id;POST /authorandPUT /author/{id}acceptmetadataProfileId. New authors default to the seeded "Standard" profile (id=1) so the language filter applies out of the box. - Metadata profile editor in Settings → Metadata Profiles — create/edit profiles with a language multi-select (English, French, German, Dutch, Spanish, Italian, Portuguese, Japanese, Chinese, Russian). Empty selection = accept any language.
- Metadata profile picker in the Add Author modal (shown when more than one profile exists).
Changed
FetchAuthorBooksnow filters against the author's metadata profile'sallowed_languagesCSV instead of the globalsearch.preferredLanguagesetting. Books with an unknown language are always kept (data-availability varies by provider).
Security notes
- Sessions use
SameSite=Lax, which mitigates cross-site form-submission CSRF. An explicit CSRF token pass is on the roadmap. - OIDC / SSO and reverse-proxy header trust are explicitly out of scope for this release; see the Roadmap in the README for the planned path.
v0.6.1 patch
- Split
describeDirintodescribe_unix.go(POSIX uid/gid) anddescribe_windows.go(path + mode only) via//go:buildtags so GoReleaser can cross-compile Windows targets. No runtime change on Linux.