fix(auth): support qBittorrent 5.2+ login and session cookie - #180
Merged
Conversation
qBittorrent 5.2.3RC returns 204 with an empty body on successful /api/v2/auth/login instead of the pre-5.2 200 "Ok." body, which was incorrectly treated as a failed login and surfaced as a generic "Connection failed" error. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
qBittorrent 5.2+ names its session cookie QBT_SID_<port> (its own
WebUI listen port baked into the name) instead of the legacy plain
SID, so the /SID=([^;]+)/ regex in login() never matched and auth
kept failing even after the 204-status fix. Widen it to match any
cookie whose name contains SID as a token.
Verified live against a 5.2+ instance that the server accepts the
session value back under any cookie name (only the value is
validated, not the name), so qbtFetch's Cookie: SID=${sid} header
needs no change.
… SID
qBittorrent 5.2+ rejects the session value when sent back under the wrong
cookie name (verified live: same SID value, QBT_SID_8080=<value> -> 200,
SID=<value> -> 403). login() now returns {name, value} captured from the
Set-Cookie regex match instead of just the value, and that pair is plumbed
through sidCache/getSession/withSessionRetry/qbtFetch so the Cookie header
uses the actual assigned name.
The regex-over-comma-joined-header approach could pick the wrong cookie (any name merely containing "SID", e.g. "SIDCC") and could bleed a cookie's value across a comma boundary into the next Set-Cookie entry when the SID cookie had no trailing attributes. getSetCookie() returns each Set-Cookie header as its own array element, so each is parsed as a single cookie and matched against an exact SID/QBT_SID_<port> name pattern.
Knip Code AnalysisFound 8 total issues
View details
Use |
|
✅ Security audit passed Passed (38/38)
Summary: 38/38 checks passed See |
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.
Fixes qBittorrent client connections against qBittorrent 5.2.0+ (released 2026-05-03), which are currently broken for every user on that version or newer.
What broke
qBittorrent 5.2.0 changed two things about
/api/v2/auth/loginat once, and we depend on both:204 No Contentwith an empty body.loginAction()no longer callssetResult(), andwebapplication.cppmaps a null result to 204. We required the body to equal"Ok.", so login threw "Authentication failed — check username and password" on correct credentials.SIDtoQBT_SID_<port>:/SID=([^;]+)/, which doesn't matchQBT_SID_8080=..., and we sent the value back asCookie: SID=..., a name the server's exact-name lookup ignores. Either break alone is fatal.Bad credentials also now return
401instead of200with body"Fails.".What this changes
204as login success alongside the legacy200+"Ok.".SidCookie { name, value }and thread it throughgetSession,withSessionRetry,qbtFetch,getTorrents,getTransferInfo, andsyncMaindata. The cookie name is server-determined, so it can't stay a hardcoded constant.Set-Cookierather than deriving it. The port in the name is qBittorrent's own configured WebUI port, not the port we connected on — behind a reverse proxy on :443 it's still the internal port, so deriving it from our config would be wrong.^QBT_SID_\d+$or the legacy exactSID, keeping ≤5.1 servers working.Set-Cookieheader individually viagetSetCookie()..get("set-cookie")comma-joins multiple headers, and cookie values andExpiresdates legally contain commas, so regexing across the joined string was unsafe. This fixes a latent bug that predates 5.2.Net production change: 38 insertions, 15 deletions in
src/lib/download-clients/qbt/transport.ts.Verification
Behavior confirmed against qBittorrent source at the
release-5.2.0tag (src/webui/webapplication.cpp,src/webui/api/authcontroller.cpp). Note the GitHub wiki and most third-party docs still describe the 5.0 API and show plainSID=— they are stale on this point.Local run against this branch:
New tests cover 204 login, the
QBT_SID_<port>name, a decoy cookie whose name merely containsSIDas a substring, and the comma-joined-header bleed case.Provenance
These four commits are cherry-picked from #175, authored by @clawdbrunner, and are unrelated to that PR's tracker-adapter work — they touch only
src/lib/download-clients/. Split out so the client fix can ship without waiting on that review. When #175 rebases, git will recognize these as already applied and drop them automatically.Not included
qBittorrent API error: 401 Unauthorizedrather than the friendlier credentials message, because the generic!response.okcheck runs before the body check. Worth a follow-up.Authorization: Bearer, qBittorrent 5.2.0+ / WebAPI 2.15.1) would remove cookie handling entirely, butauth/*is forbidden under a key sotestConnection()would need to probetransfer/infoinstead. Separate change.