diff --git a/changelog.d/fixes/10519-token-backed-web-session-test-dispatch.md b/changelog.d/fixes/10519-token-backed-web-session-test-dispatch.md new file mode 100644 index 0000000000..009f0bd2e5 --- /dev/null +++ b/changelog.d/fixes/10519-token-backed-web-session-test-dispatch.md @@ -0,0 +1 @@ +- **fix(providers):** test token-backed web sessions through their provider validator instead of the OAuth path ([#10519](https://github.com/diegosouzapw/OmniRoute/pull/10519)) — thanks @Zartharas diff --git a/src/app/api/providers/[id]/test/route.ts b/src/app/api/providers/[id]/test/route.ts index 23c79ab5ab..447a7edfe7 100644 --- a/src/app/api/providers/[id]/test/route.ts +++ b/src/app/api/providers/[id]/test/route.ts @@ -27,6 +27,7 @@ import { shouldFallbackToPublicCodeSuggestions, } from "@/lib/oauth/gitlab"; import { providerAllowsOptionalApiKey } from "@/shared/constants/providers"; +import { shouldUseApiKeyConnectionTest } from "./webSessionTestDispatch"; import { removeConnectionHealth } from "@omniroute/open-sse/services/apiKeyRotator.ts"; import { classifyAmbiguousOrAuthError, type ClassifyFailureArgs } from "./mistralAmbiguousAuth"; import { buildApiKeyConnectionTestResult } from "./apiKeyTestResult"; @@ -761,7 +762,7 @@ export async function testSingleConnection(connectionId: string, validationModel refreshed: false, diagnosis: (runtime as any).diagnosis, }; - } else if (connection.authType === "apikey") { + } else if (shouldUseApiKeyConnectionTest(connection.authType, provider)) { const enrichedConnection = validationModelId ? { ...connection, diff --git a/src/app/api/providers/[id]/test/webSessionTestDispatch.ts b/src/app/api/providers/[id]/test/webSessionTestDispatch.ts new file mode 100644 index 0000000000..dc2ba36a5a --- /dev/null +++ b/src/app/api/providers/[id]/test/webSessionTestDispatch.ts @@ -0,0 +1,36 @@ +import { getWebSessionCredentialRequirement } from "@/shared/providers/webSessionCredentials"; + +/** + * Token-kind web-session providers (`getWebSessionCredentialRequirement(...).kind === + * "token"`) that actually have a token-aware connection validator wired up in + * `src/lib/providers/validation.ts`'s `SPECIALTY_VALIDATORS` map — either a dedicated + * `validate*WebProvider` entry, or (zai-web) a token-aware branch inside the generic + * `validateWebCookieProvider` probe (`src/lib/providers/validation/webCookie.ts`). + * + * `WEB_SESSION_CREDENTIAL_REQUIREMENTS` currently marks more providers as `kind: "token"` + * than have a matching validator (e.g. hailuo-web, microsoft-designer-web, t3-chat-web, + * promptql). Those fall through to `validateWebCookieProvider`'s generic probe, which + * sends the stored credential as a `Cookie` header and treats most non-401/403 responses + * as valid — the wrong wire format for a token-authenticated provider, so an invalid + * token can be reported as a healthy connection. Keep this set in sync with + * `SPECIALTY_VALIDATORS` (and the zai-web branch in `webCookie.ts`): add a provider here + * only after it has a real token-aware validator. + */ +const TOKEN_AWARE_VALIDATED_WEB_SESSION_PROVIDERS = new Set([ + "deepseek-web", + "kimi-web", + "tinycms-web", + "copilot-m365-web", + "copilot-web", + "zai-web", +]); + +export function shouldUseApiKeyConnectionTest(authType: unknown, providerId: unknown): boolean { + if (authType === "apikey") return true; + if (authType !== "cookie") return false; + if (getWebSessionCredentialRequirement(providerId)?.kind !== "token") return false; + return ( + typeof providerId === "string" && + TOKEN_AWARE_VALIDATED_WEB_SESSION_PROVIDERS.has(providerId) + ); +} diff --git a/tests/unit/provider-test-token-web-session-dispatch.test.ts b/tests/unit/provider-test-token-web-session-dispatch.test.ts new file mode 100644 index 0000000000..f24cb24948 --- /dev/null +++ b/tests/unit/provider-test-token-web-session-dispatch.test.ts @@ -0,0 +1,48 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { shouldUseApiKeyConnectionTest } = + await import("../../src/app/api/providers/[id]/test/webSessionTestDispatch.ts"); + +test("normal API-key connections keep the API-key test path", () => { + assert.equal(shouldUseApiKeyConnectionTest("apikey", "openai"), true); +}); + +test("token-kind cookie-auth web sessions use the API-key test path", () => { + assert.equal(shouldUseApiKeyConnectionTest("cookie", "deepseek-web"), true); + + assert.equal(shouldUseApiKeyConnectionTest("cookie", "zai-web"), true); +}); + +test("cookie-kind web sessions do not use the API-key test path", () => { + assert.equal(shouldUseApiKeyConnectionTest("cookie", "chatgpt-web"), false); + + assert.equal(shouldUseApiKeyConnectionTest("cookie", "claude-web"), false); +}); + +test("token-kind web sessions WITHOUT a token-aware validator stay off the API-key test path", () => { + // hailuo-web and promptql are `kind: "token"` in WEB_SESSION_CREDENTIAL_REQUIREMENTS, + // but neither has an entry in validation.ts's SPECIALTY_VALIDATORS map (nor a + // token-aware branch like zai-web's in validateWebCookieProvider). Routing them through + // the API-key test path would dispatch to the generic cookie probe, which sends the + // stored token as a `Cookie` header and treats most non-401/403 responses as valid — + // an invalid hailuo-web/promptql token could be reported as a healthy connection. + assert.equal(shouldUseApiKeyConnectionTest("cookie", "hailuo-web"), false); + assert.equal(shouldUseApiKeyConnectionTest("cookie", "promptql"), false); + + // Same reasoning applies to the other two token-kind providers with no validator. + assert.equal(shouldUseApiKeyConnectionTest("cookie", "microsoft-designer-web"), false); + assert.equal(shouldUseApiKeyConnectionTest("cookie", "t3-chat-web"), false); +}); + +test("every token-kind web session with a real token-aware validator uses the API-key test path", () => { + for (const providerId of ["deepseek-web", "kimi-web", "tinycms-web", "copilot-m365-web", "copilot-web", "zai-web"]) { + assert.equal(shouldUseApiKeyConnectionTest("cookie", providerId), true, providerId); + } +}); + +test("other auth types are not broadened", () => { + assert.equal(shouldUseApiKeyConnectionTest("oauth", "deepseek-web"), false); + + assert.equal(shouldUseApiKeyConnectionTest(null, "deepseek-web"), false); +});