github vavallee/bindery v0.6.1
v0.6.1 — Authentication overhaul

latest releases: v1.15.3, v1.15.2, v1.15.1...
one month ago

Note: This release ships the full v0.6.0 authentication-overhaul feature set. The v0.6.0 tag itself failed its GoReleaser cross-compile step (Windows GOOS couldn't resolve syscall.Stat_t in the SQLite error-path helper) so no binaries or :0.6.0 Docker 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 /setup flow 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_session is HttpOnly + SameSite=Lax. 30-day "remember me" or 12-hour default. Secure is intentionally left unset because TLS is usually terminated upstream (Traefik); front with a proxy that adds Strict-Transport-Security if you need strict HTTPS-only cookies.
  • Three auth modesenabled (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-Key header 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 endpointsGET /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_KEY is 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, and auth.mode settings are filtered out of the generic GET /setting and GET /setting/{key} endpoints — they are readable only via /auth/config for the authenticated admin.
  • Frontend: added /login and /setup routes, an AuthProvider + AuthGuard that redirect unauthenticated users, a "Sign out" action in the header, and a Security section in Settings → General.

Fixed

  • Middleware was treating /auth/status as an unauth-allowed path before verifying the session cookie, so the endpoint always reported authenticated: 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 FormData on submit instead of relying on React-controlled state. Browser password-manager autofill populates input.value without firing onChange, 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 /author and PUT /author/{id} accept metadataProfileId. 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

  • FetchAuthorBooks now filters against the author's metadata profile's allowed_languages CSV instead of the global search.preferredLanguage setting. 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 describeDir into describe_unix.go (POSIX uid/gid) and describe_windows.go (path + mode only) via //go:build tags so GoReleaser can cross-compile Windows targets. No runtime change on Linux.

Don't miss a new bindery release

NewReleases is sending notifications on new releases.