mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
fix(providers): web-cookie fallback validation reports unsupported instead of a false valid (#6309)
validateWebCookieProvider() previously required a providerRegistry.ts entry and
returned "Provider not found in registry" for web-cookie-only providers like
lmarena, gemini-business, poe-web, venice-web and v0-vercel-web. A fallback to
WEB_COOKIE_PROVIDERS[provider].website was proposed, but live verification showed
probing `${website}/models` does not reliably signal session validity for these
(redirects/SPA 200s regardless of cookie validity) — it would report an expired
or garbage cookie as valid, which is worse than an honest "not supported". Until
each provider has a verified, side-effect-free auth probe against its real API
host, the fallback now returns `unsupported: true` with no network call. Also
reverts the probe transport from validationRead back to directHttpsRequest,
which fixes a globalThis.fetch mock/patch-timing mismatch that made the
pre-existing tests/unit/provider-validation-web-cookie-auth007.test.ts hit the
live network in CI, and adds the missing Cookie header to the probe request.
Regression guard: tests/unit/web-cookie-validation-fallback.test.ts.
Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
@@ -50,6 +50,7 @@ _Living section — bullets land here as PRs merge into `release/v3.8.47` (paral
|
||||
- **fix(api):** editing any existing OpenAI Codex provider connection in the dashboard returned "Invalid request" and the edit could never be saved ([#6562](https://github.com/diegosouzapw/OmniRoute/issues/6562)) — `createProviderConnection()` (`src/lib/db/providers.ts`) auto-increments a new connection's `priority` to `MAX(priority)+1` per provider with no upper bound, and OAuth-imported connections (Codex `codex-auth/import` / `import-bulk`, up to 50 accounts per call, callable repeatedly — the standard Codex bulk-account-rotation workflow) never pass through `createProviderSchema`'s Zod validation at all, so nothing ever capped that value; `EditConnectionModal`'s `handleSubmit` always resends the connection's current `priority` unchanged on every save, and `updateProviderConnectionSchema` capped `priority`/`globalPriority` at `max(100)` — a UI-only ceiling the create path never enforced — so the first edit of any connection whose priority had already grown past 100 (routine once a Codex account count exceeds 100) failed validation regardless of which field the user actually changed. Raised the ceiling to `max(100_000)` on both fields — still bounded (a genuinely out-of-range value is still rejected), just wide enough to accept priorities the app itself already produces. Regression guard: `tests/unit/codex-connection-edit-6562.test.ts` (a Codex OAuth connection whose priority already exceeds the old 100 cap now validates + persists on edit; a control payload with a still-genuinely-invalid priority is still rejected with "Invalid request").
|
||||
- **mimocode**: rotate accounts on MiMoCode's rate-limit-style 400s (body-classified) instead of failing on the first account; malformed 400s still fail fast with the real upstream error (#6648 — thanks @pizzav-xyz)
|
||||
- **fix(cli):** compression CLI REST fallback now reads/writes the canonical `defaultMode` field (surfaced as `strategy`) instead of a nonexistent `engine` key, and table output renders nested objects as JSON instead of `[object Object]` (#6571 — thanks @charleszolot)
|
||||
- **fix(providers):** web-cookie providers without a `providerRegistry.ts` entry (`lmarena`, `gemini-business`, `poe-web`, `venice-web`, `v0-vercel-web`) now report `unsupported: true` instead of silently "OK" ([#6309](https://github.com/diegosouzapw/OmniRoute/pull/6309)) — `validateWebCookieProvider()` (`src/lib/providers/validation.ts`) previously required a registry entry and returned "Provider not found in registry" for these; a fallback to `WEB_COOKIE_PROVIDERS[provider].website` was proposed, but live verification showed the `${website}/models` probe does not reliably signal session validity for these providers (redirects/SPA 200s regardless of cookie validity — e.g. lmarena's real API is `arena.ai`, not `lmarena.ai`; Poe's real endpoint is a GraphQL POST, not a REST `/models`), so it would report an expired or garbage cookie as valid. Until each provider has a verified, side-effect-free auth probe against its real API host, the fallback now returns `unsupported` (no network call) instead of a false positive. Regression guard: `tests/unit/web-cookie-validation-fallback.test.ts`. (thanks @oyi77)
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
|
||||
@@ -107,11 +107,16 @@ export { isRetryableProxyTarget, isSecurityBlockError } from "./validation/trans
|
||||
export async function validateWebCookieProvider({
|
||||
provider,
|
||||
apiKey,
|
||||
providerSpecificData = {},
|
||||
}: any) {
|
||||
providerSpecificData: _providerSpecificData = {},
|
||||
}: {
|
||||
provider: string;
|
||||
apiKey?: string;
|
||||
providerSpecificData?: Record<string, unknown>;
|
||||
}) {
|
||||
try {
|
||||
const entry = getRegistryEntry(provider);
|
||||
if (!entry) {
|
||||
const cookieProvider = WEB_COOKIE_PROVIDERS[provider as keyof typeof WEB_COOKIE_PROVIDERS];
|
||||
if (!entry && !cookieProvider) {
|
||||
return { valid: false, error: "Provider not found in registry", unsupported: true };
|
||||
}
|
||||
|
||||
@@ -121,9 +126,26 @@ export async function validateWebCookieProvider({
|
||||
return { valid: false, error: "Cookie required for web-cookie provider", unsupported: false };
|
||||
}
|
||||
|
||||
if (!entry) {
|
||||
// Providers listed in WEB_COOKIE_PROVIDERS without a providerRegistry entry (e.g.
|
||||
// lmarena, gemini-business, poe-web, venice-web, v0-vercel-web) only expose a
|
||||
// marketing website URL, not a real API host. Probing `${website}/models`
|
||||
// does not reliably signal session validity for these —
|
||||
// live verification showed most return redirects or SPA 200s regardless of
|
||||
// cookie validity, which would silently report an expired/garbage cookie as
|
||||
// "OK" (worse than an honest "not supported"). Until each of these providers
|
||||
// has a verified, side-effect-free auth probe against its real API host, report
|
||||
// unsupported instead of a false positive.
|
||||
return {
|
||||
valid: false,
|
||||
error: "Provider validation not supported",
|
||||
unsupported: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Attempt a minimal request to check if the session is valid
|
||||
// Use /models endpoint or a minimal completion request depending on the provider
|
||||
const baseUrl = entry.baseUrl || "";
|
||||
const baseUrl = normalizeBaseUrl(entry.baseUrl || "");
|
||||
const testUrl = `${baseUrl}/models`;
|
||||
|
||||
const res = await directHttpsRequest(
|
||||
@@ -132,6 +154,7 @@ export async function validateWebCookieProvider({
|
||||
method: "GET",
|
||||
headers: {
|
||||
"User-Agent": STANDARD_USER_AGENT,
|
||||
Cookie: cookie,
|
||||
},
|
||||
},
|
||||
10_000
|
||||
@@ -150,7 +173,7 @@ export async function validateWebCookieProvider({
|
||||
// a 401/403 from the /models probe is the only definitive "session expired" signal
|
||||
// for web-cookie auth, so a non-auth status is treated as a valid session.
|
||||
return { valid: true, error: null, unsupported: false };
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
return toValidationErrorResult(error);
|
||||
}
|
||||
}
|
||||
|
||||
110
tests/unit/web-cookie-validation-fallback.test.ts
Normal file
110
tests/unit/web-cookie-validation-fallback.test.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
// Tests for validateWebCookieProvider fallback when no registry entry exists.
|
||||
// Covers providers like lmarena, gemini-business, poe-web, venice-web and v0-vercel-web
|
||||
// that are listed in WEB_COOKIE_PROVIDERS but have no entry in providerRegistry.ts.
|
||||
//
|
||||
// These providers only expose a marketing website URL (WEB_COOKIE_PROVIDERS[id].website),
|
||||
// not a real API host. Probing `${website}/models` does not reliably signal session
|
||||
// validity — live verification showed most of these hosts return redirects or SPA 200s
|
||||
// regardless of cookie validity, which would silently report an expired/garbage cookie as
|
||||
// "OK". Until each provider has a verified, side-effect-free auth probe against its real
|
||||
// API host, validateWebCookieProvider reports `unsupported: true` for this fallback case
|
||||
// instead of a false "valid" — and does so WITHOUT making any network probe.
|
||||
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { validateProviderApiKey } = await import("../../src/lib/providers/validation.ts");
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
const fetchCalls: Array<{ url: string; headers: Record<string, string> }> = [];
|
||||
|
||||
test.beforeEach(() => {
|
||||
fetchCalls.length = 0;
|
||||
globalThis.fetch = (async (url: string | URL, init?: RequestInit) => {
|
||||
const headers: Record<string, string> = {};
|
||||
if (init?.headers) {
|
||||
if (init.headers instanceof Headers) {
|
||||
init.headers.forEach((v, k) => {
|
||||
headers[k] = v;
|
||||
});
|
||||
} else if (Array.isArray(init.headers)) {
|
||||
for (const [k, v] of init.headers) headers[k] = v;
|
||||
} else {
|
||||
Object.assign(headers, init.headers);
|
||||
}
|
||||
}
|
||||
fetchCalls.push({ url: String(url), headers });
|
||||
// If this mock is ever hit for a no-registry-entry provider, the test will fail on
|
||||
// the `fetchCalls.length` assertion below — this response is never meant to be read.
|
||||
return new Response("", { status: 404 });
|
||||
}) as typeof fetch;
|
||||
});
|
||||
|
||||
test.afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
// ── lmarena (no registry entry, falls back to WEB_COOKIE_PROVIDERS) ──
|
||||
|
||||
test("lmarena validation is unsupported (no verified auth probe) and makes no network call", async () => {
|
||||
const result = await validateProviderApiKey({
|
||||
provider: "lmarena",
|
||||
apiKey: "test-lmarena-cookie",
|
||||
});
|
||||
assert.strictEqual(result.valid, false);
|
||||
assert.equal(result.unsupported, true);
|
||||
assert.equal(fetchCalls.length, 0, "must not probe the marketing website");
|
||||
});
|
||||
|
||||
test("lmarena validation rejects empty cookie before checking support", async () => {
|
||||
const result = await validateProviderApiKey({
|
||||
provider: "lmarena",
|
||||
apiKey: "",
|
||||
});
|
||||
assert.strictEqual(result.valid, false);
|
||||
assert.match(result.error, /api key required|cookie/i);
|
||||
assert.equal(fetchCalls.length, 0);
|
||||
});
|
||||
|
||||
// ── gemini-business (no registry entry, falls back to WEB_COOKIE_PROVIDERS) ──
|
||||
|
||||
test("gemini-business validation is unsupported and makes no network call", async () => {
|
||||
const result = await validateProviderApiKey({
|
||||
provider: "gemini-business",
|
||||
apiKey: "test-gemini-cookie",
|
||||
});
|
||||
assert.strictEqual(result.valid, false);
|
||||
assert.equal(result.unsupported, true);
|
||||
assert.equal(fetchCalls.length, 0);
|
||||
});
|
||||
|
||||
// ── remaining WEB_COOKIE_PROVIDERS-only providers (no registry entry) ──
|
||||
// NOTE: doubao-web and zenmux-free are intentionally NOT covered here — unlike when this
|
||||
// fix was proposed, both now carry a providerRegistry.ts entry (added independently of
|
||||
// this PR), so they no longer exercise the no-registry-entry fallback branch this test
|
||||
// file targets; they go through the pre-existing entry-based probe instead, which is out
|
||||
// of scope for this fix.
|
||||
|
||||
for (const provider of ["poe-web", "venice-web", "v0-vercel-web"]) {
|
||||
test(`${provider} validation is unsupported and makes no network call`, async () => {
|
||||
const result = await validateProviderApiKey({
|
||||
provider,
|
||||
apiKey: "some-cookie-value",
|
||||
});
|
||||
assert.strictEqual(result.valid, false);
|
||||
assert.equal(result.unsupported, true);
|
||||
assert.equal(fetchCalls.length, 0);
|
||||
});
|
||||
}
|
||||
|
||||
// ── generic fallback guard ──
|
||||
|
||||
test("unknown web-cookie provider without registry returns unsupported", async () => {
|
||||
const result = await validateProviderApiKey({
|
||||
provider: "fake-web",
|
||||
apiKey: "some-key",
|
||||
});
|
||||
assert.strictEqual(result.valid, false);
|
||||
assert.equal(result.unsupported, true);
|
||||
});
|
||||
Reference in New Issue
Block a user