From 23c525287e3f87f254e81548eb92c5ee54d9e6db Mon Sep 17 00:00:00 2001 From: KactusTzru Date: Tue, 16 Jun 2026 10:45:05 +0700 Subject: [PATCH 1/2] 1 --- src/lib/providers/validation.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/lib/providers/validation.ts b/src/lib/providers/validation.ts index a250836c28c..849401c4d20 100644 --- a/src/lib/providers/validation.ts +++ b/src/lib/providers/validation.ts @@ -2899,6 +2899,27 @@ async function validateQwenWebProvider({ apiKey }: any) { if (!resp.ok) { return { valid: false, error: `Qwen returned HTTP ${resp.status}` }; } + + // Parse JSON response and verify we have a real user object + // Qwen returns HTTP 200 even for invalid tokens, so we must check the body + try { + const data = await resp.json(); + const user = data?.user || data?.data?.user; + + if (!user) { + return { + valid: false, + error: + "Qwen session token is invalid or expired — re-login at https://chat.qwen.ai and paste a fresh full Cookie header", + }; + } + } catch (parseError) { + return { + valid: false, + error: "Qwen returned invalid JSON response", + }; + } + return { valid: true, error: null }; } catch (error) { return toValidationErrorResult(error); From e5531368a3569fdf1a9514413c3526b3f9cff2f0 Mon Sep 17 00:00:00 2001 From: KactusTzru Date: Tue, 16 Jun 2026 09:47:38 -0300 Subject: [PATCH 2/2] test(qwen-web): cover cookie validation false-positive on HTTP 200 (#3958, #3931) Co-authored-by: diegosouzapw --- .../qwen-web-cookie-validation-3958.test.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/unit/qwen-web-cookie-validation-3958.test.ts diff --git a/tests/unit/qwen-web-cookie-validation-3958.test.ts b/tests/unit/qwen-web-cookie-validation-3958.test.ts new file mode 100644 index 00000000000..12bb3a468e9 --- /dev/null +++ b/tests/unit/qwen-web-cookie-validation-3958.test.ts @@ -0,0 +1,53 @@ +// Regression for #3931 / #3958: Qwen's `GET /api/v2/user` returns HTTP 200 even +// for invalid tokens, so the validator must inspect the response body for a real +// `user` object — checking `resp.ok` alone produced a false-positive "Valid". + +import test from "node:test"; +import assert from "node:assert/strict"; + +const { validateProviderApiKey } = await import("../../src/lib/providers/validation.ts"); + +const originalFetch = globalThis.fetch; + +test.afterEach(() => { + globalThis.fetch = originalFetch; +}); + +function jsonResponse(body: string) { + return new Response(body, { + status: 200, + headers: { "content-type": "application/json" }, + }); +} + +test("qwen-web validation is VALID when the 200 body carries a real user object", async () => { + globalThis.fetch = (async () => + jsonResponse(JSON.stringify({ user: { id: "u-1", name: "tester" } }))) as typeof fetch; + + const result = await validateProviderApiKey({ provider: "qwen-web", apiKey: "qwen-token-abc123" }); + assert.strictEqual(result.valid, true); +}); + +test("qwen-web validation rejects a 200 response with no user object (was false-positive)", async () => { + globalThis.fetch = (async () => jsonResponse(JSON.stringify({}))) as typeof fetch; + + const result = await validateProviderApiKey({ provider: "qwen-web", apiKey: "qwen-token-abc123" }); + assert.strictEqual(result.valid, false); + assert.match(result.error, /invalid or expired/i); +}); + +test("qwen-web validation accepts a nested data.user object", async () => { + globalThis.fetch = (async () => + jsonResponse(JSON.stringify({ data: { user: { id: "u-2" } } }))) as typeof fetch; + + const result = await validateProviderApiKey({ provider: "qwen-web", apiKey: "qwen-token-abc123" }); + assert.strictEqual(result.valid, true); +}); + +test("qwen-web validation rejects a 200 body that is not valid JSON", async () => { + globalThis.fetch = (async () => jsonResponse("<>")) as typeof fetch; + + const result = await validateProviderApiKey({ provider: "qwen-web", apiKey: "qwen-token-abc123" }); + assert.strictEqual(result.valid, false); + assert.match(result.error, /invalid JSON/i); +});