fix(providers): validate Z.ai web auth semantics (#10329)

This commit is contained in:
Aman
2026-08-15 21:14:55 -06:00
committed by GitHub
parent 47f53f37ea
commit 0b347eaea1
4 changed files with 164 additions and 1 deletions

View File

@@ -0,0 +1 @@
- **fix(providers):** validate Z.ai web Local Storage sessions against the authenticated user-settings endpoint and preserve exact upstream status codes ([#10329](https://github.com/diegosouzapw/OmniRoute/pull/10329)) — thanks @Zartharas

View File

@@ -81,6 +81,7 @@ import {
validatePoeProvider,
} from "./validation/audioMiscProviders";
import { validateChatGptWebCodexProvider } from "./validation/chatgptWebCodex";
import { validateZaiWebProvider } from "./validation/zaiWeb";
import { validateSearchProvider, SEARCH_VALIDATOR_CONFIGS } from "./validation/searchProviders";
import {
validateClarifaiProvider,
@@ -227,7 +228,7 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
error:
"Modal requires a Base URL pointing to your OpenAI-compatible Modal app " +
"(e.g. https://<workspace>--<app>.modal.run/v1). " +
"Fill in the \"Base URL override\" field.",
'Fill in the "Base URL override" field.',
};
}
return validateOpenAILikeProvider({
@@ -249,6 +250,7 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
snowflake: validateSnowflakeProvider,
gigachat: validateGigachatProvider,
"deepseek-web": validateDeepSeekWebProvider,
"zai-web": validateZaiWebProvider,
"grok-web": validateGrokWebProvider,
"qwen-web": validateQwenWebProvider,
"kimi-web": validateKimiWebProvider,

View File

@@ -0,0 +1,52 @@
import { extractZaiToken } from "@omniroute/open-sse/services/zaiWebCredentials.ts";
import { toValidationErrorResult, validationRead } from "./transport";
const ZAI_SESSION_PROBE_URL = "https://chat.z.ai/api/v1/users/user/settings";
export async function validateZaiWebProvider({ apiKey }: { apiKey?: string }) {
const token = extractZaiToken(String(apiKey || ""));
if (!token) {
return {
valid: false,
error:
'Invalid Z.ai web-session credential — copy the "token" value from chat.z.ai Local Storage.',
};
}
try {
const response = await validationRead(ZAI_SESSION_PROBE_URL, {
method: "GET",
headers: {
Accept: "application/json, text/plain, */*",
Authorization: `Bearer ${token}`,
Origin: "https://chat.z.ai",
Referer: "https://chat.z.ai/",
},
});
if (response.status >= 200 && response.status < 300) {
return {
valid: true,
error: null,
};
}
if (response.status === 401) {
return {
valid: false,
error:
'Invalid or expired Z.ai web-session credential — copy a fresh "token" value from chat.z.ai Local Storage.',
statusCode: 401,
};
}
return {
valid: false,
error: `Z.ai session validation returned HTTP ${response.status}`,
statusCode: response.status,
};
} catch (error: unknown) {
return toValidationErrorResult(error);
}
}

View File

@@ -0,0 +1,108 @@
import test from "node:test";
import assert from "node:assert/strict";
const originalFetch = globalThis.fetch;
const { validateProviderApiKey } = await import("../../src/lib/providers/validation.ts");
let nextStatus = 200;
let lastRequest: {
url: string;
method: string;
authorization: string;
cookie: string;
} | null = null;
let lastResponse: Response | null = null;
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
const headers = new Headers(init?.headers);
lastRequest = {
url: String(input),
method: String(init?.method || "GET"),
authorization: headers.get("authorization") || "",
cookie: headers.get("cookie") || "",
};
lastResponse = new Response(
JSON.stringify({
secret: "response-body-must-not-be-consumed",
}),
{
status: nextStatus,
headers: {
"content-type": "application/json",
},
}
);
return lastResponse;
}) as typeof fetch;
test.after(() => {
globalThis.fetch = originalFetch;
});
async function validate(status: number) {
nextStatus = status;
lastRequest = null;
lastResponse = null;
return validateProviderApiKey({
provider: "zai-web",
apiKey: "synthetic-zai-token",
providerSpecificData: {},
});
}
test("zai-web uses the token-only authenticated user-settings GET", async () => {
const result = await validate(200);
assert.equal(result.valid, true);
assert.equal(lastRequest?.url, "https://chat.z.ai/api/v1/users/user/settings");
assert.equal(lastRequest?.method, "GET");
assert.equal(lastRequest?.authorization, "Bearer synthetic-zai-token");
assert.equal(lastRequest?.cookie, "");
assert.equal(lastResponse?.bodyUsed, false);
});
test("zai-web preserves exact 401 as credential rejection", async () => {
const result = await validate(401);
assert.equal(result.valid, false);
assert.equal(result.statusCode, 401);
assert.match(result.error, /invalid or expired/i);
assert.equal(lastResponse?.bodyUsed, false);
});
test("zai-web preserves 403 without calling it expired", async () => {
const result = await validate(403);
assert.equal(result.valid, false);
assert.equal(result.statusCode, 403);
assert.doesNotMatch(result.error, /invalid or expired/i);
assert.equal(lastResponse?.bodyUsed, false);
});
test("zai-web preserves rate-limit and server statuses", async () => {
for (const status of [429, 503]) {
const result = await validate(status);
assert.equal(result.valid, false);
assert.equal(result.statusCode, status);
assert.doesNotMatch(result.error, /invalid or expired/i);
assert.equal(lastResponse?.bodyUsed, false);
}
});