feat(qbittorrent): login compat for v4/v5.0-5.2 and three auth modes
- Login success = HTTP 200 "Ok." or 204 (5.2+); session cookie taken from
Set-Cookie by name (SID / QBT_SID_<port> / custom) and sent back as-is.
- Failure classes: "Fails." / 401 -> auth; 403 IP ban / 429 -> rate;
wrong API Key (403) -> auth with hint; 5xx -> protocol.
- Downloader instance auth kind: password / api_key (qB 5.2+ Bearer, no
/auth/login) / none (whitelisted subnet); stored in settings.authKind,
credential write-only in secret_ref, cleared when switching to none.
One qbAuthConfig() shared by sync, agent capability and connection test.
- Settings form: auth kind select, password / API Key fields (no echo).
- testkit: mock qB 5.2 login behaviour, IP ban, bypassAuth;
makeTorrent({ realPieces }) builds real piece hashes for real qB checks.
- Real-container matrix (QB_REAL, skipped by default): 5.1.2 and 5.2.4,
login / add / query / tags / pause-resume / delete keeping data.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
c846e91cc4
commit
74f1566985
14 files changed
+756
-59
No files matched your search
@@ -67,6 +67,19 @@ export function TestButton({
|
||||
|
||||
const credentialHint = { editHint: "留空不修改" };
|
||||
|
||||
const AUTH_KINDS = [
|
||||
{ value: "password", label: "用户名和密码" },
|
||||
{ value: "api_key", label: "API Key" },
|
||||
{ value: "none", label: "无认证(内网白名单)" },
|
||||
];
|
||||
|
||||
/** 已存认证方式;旧数据没有 settings.authKind 时按用户名 / 凭证推断(与服务端一致) */
|
||||
function authKindOf(r: DownloaderInstance): string {
|
||||
const k = (r.settings as { authKind?: unknown }).authKind;
|
||||
if (k === "password" || k === "api_key" || k === "none") return k;
|
||||
return r.username ? "password" : r.secretRef ? "api_key" : "none";
|
||||
}
|
||||
|
||||
export function DownloadersSection() {
|
||||
return (
|
||||
<SettingsBlock section="downloaders" anchor="instances" title="qBittorrent 实例" plain>
|
||||
@@ -121,8 +134,29 @@ function DownloadersTable() {
|
||||
required: true,
|
||||
placeholder: "http://192.168.1.10:8080",
|
||||
},
|
||||
{ name: "username", label: "用户名", kind: "text", hint: "留空时凭证按 WebUI API Key 使用" },
|
||||
{ name: "credential", label: "密码 / API Key", kind: "password", ...credentialHint },
|
||||
{
|
||||
name: "authKind",
|
||||
label: "认证方式",
|
||||
kind: "select",
|
||||
options: AUTH_KINDS,
|
||||
hint: "API Key 需要 qBittorrent 5.2 及以上,在 WebUI 设置里生成",
|
||||
},
|
||||
{ name: "username", label: "用户名", kind: "text", visible: (v) => v.authKind === "password" },
|
||||
{
|
||||
name: "password",
|
||||
label: "密码",
|
||||
kind: "password",
|
||||
visible: (v) => v.authKind === "password",
|
||||
...credentialHint,
|
||||
},
|
||||
{
|
||||
name: "apiKey",
|
||||
label: "API Key",
|
||||
kind: "password",
|
||||
placeholder: "qbt_…",
|
||||
visible: (v) => v.authKind === "api_key",
|
||||
...credentialHint,
|
||||
},
|
||||
{ name: "managedTag", label: "管理标签", kind: "text", placeholder: "umt", hint: "只接管带此标签的任务" },
|
||||
{ name: "cacheVolumeKey", label: "缓存卷标识", kind: "text", placeholder: "cache-1" },
|
||||
{ name: "enabled", label: "启用", kind: "switch" },
|
||||
@@ -130,8 +164,10 @@ function DownloadersTable() {
|
||||
initial: {
|
||||
name: "",
|
||||
baseUrl: "",
|
||||
authKind: "password",
|
||||
username: "",
|
||||
credential: "",
|
||||
password: "",
|
||||
apiKey: "",
|
||||
managedTag: "umt",
|
||||
cacheVolumeKey: "",
|
||||
enabled: true,
|
||||
@@ -139,8 +175,10 @@ function DownloadersTable() {
|
||||
fromRow: (r) => ({
|
||||
name: r.name,
|
||||
baseUrl: r.baseUrl,
|
||||
authKind: authKindOf(r),
|
||||
username: r.username ?? "",
|
||||
credential: "",
|
||||
password: "",
|
||||
apiKey: "",
|
||||
managedTag: r.managedTag,
|
||||
cacheVolumeKey: r.cacheVolumeKey ?? "",
|
||||
enabled: r.enabled,
|
||||
@@ -149,8 +187,10 @@ function DownloadersTable() {
|
||||
name: str(v.name),
|
||||
kind: mode === "create" ? "qbittorrent" : undefined,
|
||||
baseUrl: str(v.baseUrl),
|
||||
username: nullStr(v.username),
|
||||
credential: optStr(v.credential),
|
||||
authKind: str(v.authKind),
|
||||
username: v.authKind === "password" ? nullStr(v.username) : null,
|
||||
credential:
|
||||
v.authKind === "password" ? optStr(v.password) : v.authKind === "api_key" ? optStr(v.apiKey) : undefined,
|
||||
managedTag: optStr(v.managedTag),
|
||||
cacheVolumeKey: nullStr(v.cacheVolumeKey),
|
||||
enabled: v.enabled === true,
|
||||
|
||||
Reference in new issue
Block a user