Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions open-sse/services/qoderCli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,22 @@ export async function validateQoderCliPat({

// Treat 5xx as valid bypass to prevent false negatives from legacy Qoder APIs (issue #1391)
if (res.status >= 500) {
const isCosyAppError =
/"success"\s*:\s*false/.test(errorDetail) &&
(/"msgCode"\s*:\s*500/.test(errorDetail) || /internal\s*server\s*error/i.test(errorDetail));

if (isCosyAppError) {
return {
valid: false,
error:
`Authentication failed (HTTP ${res.status}). The Qoder Cosy server returned an Internal Server Error. ` +
"This typically indicates that your Personal Access Token is invalid, expired, or not authorized. " +
"Please check your token at https://qoder.com/account/integrations." +
(errorDetail ? ` Server response: ${errorDetail}` : ""),
unsupported: false,
};
}

return {
valid: true,
error: `Validation endpoint returned HTTP ${res.status}${errorDetail ? `: ${errorDetail}` : ""}, treating PAT as valid`,
Expand Down
38 changes: 38 additions & 0 deletions tests/unit/qoder-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,41 @@ test("validateQoderCliPat treats 5xx HTTP failures as valid bypass", async () =>
globalThis.fetch = originalFetch;
}
});

test("validateQoderCliPat rejects 500 HTTP failures if response is Cosy app-level auth error", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = async (url) => {
if (String(url).includes("/ping")) return new Response("pong", { status: 200 });
return new Response(
' { "success" : false, "traceId": "a4e5de61929400b9243b4f6e49756906", "msgCode" : 500 } ',
{ status: 500 }
);
};

try {
const result = await qoderCli.validateQoderCliPat({ apiKey: "invalid-pat" });
assert.equal(result.valid, false);
assert.match(result.error!, /Authentication failed \(HTTP 500\)/);
} finally {
globalThis.fetch = originalFetch;
}
});

test("validateQoderCliPat rejects 500 HTTP failures using regex for Internal Server Error with whitespace", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = async (url) => {
if (String(url).includes("/ping")) return new Response("pong", { status: 200 });
return new Response(
' { "success" : false, "error": "Internal Server Error" } ',
{ status: 500 }
);
};

try {
const result = await qoderCli.validateQoderCliPat({ apiKey: "invalid-pat" });
assert.equal(result.valid, false);
assert.match(result.error!, /Authentication failed \(HTTP 500\)/);
} finally {
globalThis.fetch = originalFetch;
}
});