Repository navigation
fix(QBittorrentProxyV2): accept empty body as auth success (qBit >= 4.5) - #162
Closed
csmarshall wants to merge 1 commit into
Closed
csmarshall wants to merge 1 commit into
csmarshall wants to merge 1 commit into
Conversation
qBittorrent 4.5+ changed the WebUI auth response: a successful POST to
/api/v2/auth/login now returns HTTP 204 No Content with an empty body
instead of HTTP 200 with body "Ok.". A bad password from outside the
trusted-subnet whitelist returns HTTP 401 Unauthorized.
The current implementation has two related bugs against modern
qBittorrent:
1. `if (response.Content != "Ok.")` treats the new empty-body success
response as a failure, so every successful login is mis-reported
as DownloadClientAuthenticationException.
2. The HttpException catch only routes HttpStatusCode.Forbidden (403)
to DownloadClientAuthenticationException. A 401 falls through to
a generic DownloadClientException ("Failed to connect"), giving
the user the wrong error category for bad credentials.
Radarr and Sonarr already converged on the fix in their identical
copies of QBittorrentProxyV2.cs; this PR ports the same two changes
to bookshelf so the three forks stay aligned:
* Treat both Unauthorized (401) and Forbidden (403) as auth
failures.
* Only throw on a non-empty / non-"Ok." response body (use
IsNotNullOrWhiteSpace so whitespace-only bodies also count as
success). Empty body = success in qBit 4.5+.
References (current develop on both, identical methods):
https://github.com/Radarr/Radarr/blob/develop/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV2.cs
https://github.com/Sonarr/Sonarr/blob/develop/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV2.cs
csmarshall
force-pushed
the
fix/qbittorrent-auth-empty-response
branch
from
May 14, 2026 01:37
66df69c to
0604a7b
Compare
3 of 4 tasks
|
Possible duplicate of #158 |
Jaffa00
pushed a commit
to Jaffa00/bookshelf
that referenced
this pull request
Jun 9, 2026
qBittorrent 5.x added API-key authentication as an alternative to the
classic username/password cookie flow. Radarr and Sonarr support it;
bookshelf doesn't yet. Port the additions verbatim (adapted to
bookshelf's existing settings layout — no other refactors).
Changes:
src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs
* New `ApiKey` field at FieldDefinition(4), with PrivacyLevel.ApiKey
so it's masked in the UI and logs.
* Validator rejects configurations where ApiKey is set AND
Username/Password are also set, mirroring Radarr's rule.
* Renumber existing fields (5..14) to make room for the ApiKey
field where users naturally look for it (right above Username).
Property names are unchanged so existing stored configs are
preserved.
src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV2.cs
* BuildRequest now emits `Authorization: Bearer <key>` when ApiKey
is set, and only attaches BasicNetworkCredential when falling back
to username/password. Mixing the two would be redundant and could
confuse some qBit middlewares.
* ProcessRequest gains a fast path for the ApiKey case that skips
AuthenticateClient (no cookie session needed) and routes 401/403
to DownloadClientAuthenticationException directly, matching the
Radarr/Sonarr pattern.
Independent of pennydreadful#162: this change builds and works whether or not the
auth-response-handling fix has landed yet. Users on classic
username/password auth see no behavior change.
References:
https://github.com/Radarr/Radarr/blob/develop/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs
https://github.com/Radarr/Radarr/blob/develop/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV2.cs
ethanbergstrom
pushed a commit
to ethanbergstrom/bookshelf
that referenced
this pull request
Jul 4, 2026
qBittorrent 5.x added API-key authentication as an alternative to the
classic username/password cookie flow. Radarr and Sonarr support it;
bookshelf doesn't yet. Port the additions verbatim (adapted to
bookshelf's existing settings layout — no other refactors).
Changes:
src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs
* New `ApiKey` field at FieldDefinition(4), with PrivacyLevel.ApiKey
so it's masked in the UI and logs.
* Validator rejects configurations where ApiKey is set AND
Username/Password are also set, mirroring Radarr's rule.
* Renumber existing fields (5..14) to make room for the ApiKey
field where users naturally look for it (right above Username).
Property names are unchanged so existing stored configs are
preserved.
src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV2.cs
* BuildRequest now emits `Authorization: Bearer <key>` when ApiKey
is set, and only attaches BasicNetworkCredential when falling back
to username/password. Mixing the two would be redundant and could
confuse some qBit middlewares.
* ProcessRequest gains a fast path for the ApiKey case that skips
AuthenticateClient (no cookie session needed) and routes 401/403
to DownloadClientAuthenticationException directly, matching the
Radarr/Sonarr pattern.
Independent of pennydreadful#162: this change builds and works whether or not the
auth-response-handling fix has landed yet. Users on classic
username/password auth see no behavior change.
References:
https://github.com/Radarr/Radarr/blob/develop/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs
https://github.com/Radarr/Radarr/blob/develop/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV2.cs
3n8
pushed a commit
to 3n8/bookshelf
that referenced
this pull request
Aug 27, 2026
qBittorrent 5.2.0+ returns 204 No Content on successful /api/v2/auth/login instead of 200 with body "Ok.". Treat empty bodies as success and map HTTP 401 to an authentication error, matching Sonarr/Radarr. Also publish images as ghcr.io/3n8/bookshelf and grant the CI token the permissions a fork needs to push packages. Fixes pennydreadful#160, pennydreadful#165, pennydreadful#178 (unmerged upstream PRs pennydreadful#158, pennydreadful#162, pennydreadful#170).
Owner
pennydreadful
added a commit
that referenced
this pull request
Sep 10, 2026
## Summary Port qBittorrent's API-key authentication support (introduced in qBittorrent 5.x) from Radarr and Sonarr to bookshelf. Adds an `ApiKey` field to the qBit download-client settings; when populated, requests carry an `Authorization: Bearer <key>` header and the cookie-auth flow is bypassed entirely. Existing username/password configurations see no behavior change. ## Why qBittorrent 5.x added API tokens as a more secure / scriptable alternative to user passwords. Radarr ([source](https://github.com/Radarr/Radarr/blob/develop/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs)) and Sonarr both support it; bookshelf is the odd one out among the *arrs. Users running modern qBit who'd prefer API-key auth currently can't. ## Changes **`QBittorrentSettings.cs`** - New `ApiKey` field at `FieldDefinition(4)`, with `PrivacyLevel.ApiKey` so it's masked in the UI and exported configs. - Validator rejects configurations where `ApiKey` is set alongside `Username` or `Password`, mirroring Radarr's rule. - Renumber existing `FieldDefinition` order numbers (5..14) to slot the API Key field right above Username/Password where users naturally look for it. Property names are unchanged — stored configs are unaffected. **`QBittorrentProxyV2.cs`** - `BuildRequest` emits `Authorization: Bearer <key>` when `ApiKey` is set and only attaches `BasicNetworkCredential` when falling back to username/password. - `ProcessRequest` gains a fast path for the `ApiKey` case that skips `AuthenticateClient` (no cookie session needed) and routes 401/403 directly to `DownloadClientAuthenticationException`, matching the Radarr/Sonarr pattern. ## Independent of #162 This PR is intentionally orthogonal to #162 (the auth-response-handling fix). It builds and works whether or not #162 has landed: - `ApiKey` users follow a fast path that doesn't touch the `Content != "Ok."` check at all. - Username/password users on this branch alone hit the unchanged legacy code path, which still has the empty-body bug #162 fixes. Once both PRs land they're additive. ## Test plan - [x] Cross-checked the patch against Radarr's and Sonarr's `QBittorrentSettings.cs` + `QBittorrentProxyV2.cs` for behavioral parity. - [x] Stored configs preserved (`FieldDefinition` numbers are display-order only; property names are unchanged). - [x] Built locally: `dotnet build NzbDrone.Core/Readarr.Core.csproj -c Release` clean — 0 warnings, 0 errors. - [ ] CI to verify build + existing tests. ## Out of scope Other divergences between bookshelf's and Radarr/Sonarr's qBit clients (the `AddTags()` method, base-class refactor to `DownloadClientSettingsBase`, translation-key labels, etc.) are deliberately not part of this PR — kept tight to the API-key feature so it's easy to review and accept. Co-authored-by: pennydreadful <222923085+pennydreadful@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Port the qBit-auth-response handling from Radarr/Sonarr to bookshelf so all three forks treat modern qBittorrent the same way.
Bugs fixed
QBittorrentProxyV2.AuthenticateClienthas two related bugs against qBittorrent 4.5+ that radarr and sonarr both already fixed but bookshelf hasn't pulled in:Empty-body success is mis-reported as auth failure. qBit 4.5+ returns
HTTP 204 No Content(empty body) on a successful/api/v2/auth/logininstead ofHTTP 200with body"Ok.". The existingif (response.Content != "Ok.")treats""as failure, so every successful login becomes aDownloadClientAuthenticationException.HTTP 401 routed to the wrong exception type. A bad password from outside qBit's trusted-subnet whitelist returns
401 Unauthorized. The existingHttpExceptioncatch only converts403 ForbiddentoDownloadClientAuthenticationException;401falls through to a genericDownloadClientException("Failed to connect"), giving users the wrong error category.Symptom
Persistent
Failed to authenticate with qBittorrentlog spam in current bookshelf releases against a known-good username/password pair — including configurations where Radarr and Sonarr on the same machine, with the same credentials, work fine.Reproduces deterministically against
linuxserver/qbittorrent:5.2.0(qBittorrent 4.5+). Hand-tested withwgetagainst/api/v2/auth/loginfrom inside the bookshelf container:Fix
Mirror Radarr's
QBittorrentProxyV2.cs(Sonarr's copy is byte-identical in the relevant block):catch (HttpException ex) { - _logger.Debug("qbitTorrent authentication failed."); - if (ex.Response.StatusCode == HttpStatusCode.Forbidden) + _logger.Debug(ex, "qbitTorrent authentication failed."); + + if (ex.Response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) { throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.", ex); } ... } ... - // returns "Fails." on bad login - if (response.Content != "Ok.") + if (response.Content.IsNotNullOrWhiteSpace() && response.Content != "Ok.") { ... }The legacy
"Fails."body still throwsDownloadClientAuthenticationExceptionvia the non-empty/non-"Ok."path, so older qBit versions are not regressed.Scope choice
The rest of
QBittorrentProxyV2.cshas diverged ~90 lines from Radarr/Sonarr but most of that divergence is either correct-for-bookshelf (MusicCategoryvsMovieCategory) or new features (ApiKey-based auth,AddTags()) that are orthogonal to this bug. Kept this PR tight to the auth-response handling so it's an obviously-safe, easy-to-review backport. Happy to do follow-up PRs for the new-feature bits if they're wanted.Test plan
linuxserver/qbittorrent:5.2.0(qBittorrent 4.5+).QBittorrentProxyV2.AuthenticateClient— byte-for-byte equivalent in the changed region.dotnet build NzbDrone.Core/Readarr.Core.csproj -c Releaseclean — 0 warnings, 0 errors.