mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
gemini-cli (and any @google/genai-based client) sends its credential exclusively via x-goog-api-key and it is not client-configurable to use Authorization/x-api-key instead. Add it as an unconditional fallback, after Authorization: Bearer and x-api-key, before the path-scoped URL token, in both the real enforcement gate (src/server/authz/policies/clientApi.ts::extractBearer()) and the general extractor (src/sse/services/auth.ts::extractApiKey()). The header-read/trim logic is extracted into a new leaf module (src/sse/services/googApiKeyAuth.ts) shared by both call sites, so the frozen auth.ts file only takes the minimal chokepoint wiring (config/quality/file-size-baseline.json rebaselined 2458->2461 with justification, matching this repo's established extraction pattern). Closes #7034
172 lines
7.0 KiB
TypeScript
172 lines
7.0 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const { extractApiKey } = await import("../../src/sse/services/auth.ts");
|
|
|
|
function makeRequest(headers: Record<string, string>): Request {
|
|
return new Request("https://omniroute.test/v1/messages", { headers });
|
|
}
|
|
|
|
const ANTHROPIC = { "anthropic-version": "2023-06-01" } as const;
|
|
|
|
test("extractApiKey returns Bearer key when Authorization header is set", () => {
|
|
const req = makeRequest({ Authorization: "Bearer sk-test-bearer" });
|
|
assert.equal(extractApiKey(req), "sk-test-bearer");
|
|
});
|
|
|
|
test("extractApiKey trims surrounding whitespace from Bearer token", () => {
|
|
const req = makeRequest({ Authorization: "Bearer sk-padded-token " });
|
|
assert.equal(extractApiKey(req), "sk-padded-token");
|
|
});
|
|
|
|
test("extractApiKey is case-insensitive on the Authorization header name", () => {
|
|
const req = makeRequest({ authorization: "Bearer sk-lowercase-header" });
|
|
assert.equal(extractApiKey(req), "sk-lowercase-header");
|
|
});
|
|
|
|
test("extractApiKey is case-insensitive on the 'bearer' prefix", () => {
|
|
const req = makeRequest({ Authorization: "bearer sk-lowercase-prefix" });
|
|
assert.equal(extractApiKey(req), "sk-lowercase-prefix");
|
|
});
|
|
|
|
test("extractApiKey falls back to x-api-key when Authorization is absent and anthropic-version is set (#2225)", () => {
|
|
const req = makeRequest({ "x-api-key": "sk-anthropic-native", ...ANTHROPIC });
|
|
assert.equal(extractApiKey(req), "sk-anthropic-native");
|
|
});
|
|
|
|
test("extractApiKey accepts uppercase X-Api-Key header alongside anthropic-version (#2225)", () => {
|
|
const req = makeRequest({ "X-Api-Key": "sk-uppercase-xapikey", ...ANTHROPIC });
|
|
assert.equal(extractApiKey(req), "sk-uppercase-xapikey");
|
|
});
|
|
|
|
test("extractApiKey trims surrounding whitespace from x-api-key value", () => {
|
|
const req = makeRequest({ "x-api-key": " sk-padded-xapikey ", ...ANTHROPIC });
|
|
assert.equal(extractApiKey(req), "sk-padded-xapikey");
|
|
});
|
|
|
|
test("extractApiKey prefers Bearer over x-api-key when both are present (back-compat)", () => {
|
|
const req = makeRequest({
|
|
Authorization: "Bearer sk-bearer-wins",
|
|
"x-api-key": "sk-loser",
|
|
...ANTHROPIC,
|
|
});
|
|
assert.equal(extractApiKey(req), "sk-bearer-wins");
|
|
});
|
|
|
|
test("extractApiKey returns null when neither header is present", () => {
|
|
const req = makeRequest({});
|
|
assert.equal(extractApiKey(req), null);
|
|
});
|
|
|
|
test("extractApiKey returns null when x-api-key contains only whitespace", () => {
|
|
const req = makeRequest({ "x-api-key": " ", ...ANTHROPIC });
|
|
assert.equal(extractApiKey(req), null);
|
|
});
|
|
|
|
test("extractApiKey returns null when Authorization is not a Bearer scheme and x-api-key is absent", () => {
|
|
const req = makeRequest({ Authorization: "Basic <stub-base64>" });
|
|
assert.equal(extractApiKey(req), null);
|
|
});
|
|
|
|
test("extractApiKey falls back to x-api-key when Authorization is a non-Bearer scheme (anthropic-version present)", () => {
|
|
const req = makeRequest({
|
|
Authorization: "Basic <stub-base64>",
|
|
"x-api-key": "stub-fallback-after-basic",
|
|
...ANTHROPIC,
|
|
});
|
|
assert.equal(extractApiKey(req), "stub-fallback-after-basic");
|
|
});
|
|
|
|
test("extractApiKey ignores x-api-key when anthropic-version is missing — protects local-mode non-Anthropic clients", () => {
|
|
const req = makeRequest({ "x-api-key": "placeholder-key" });
|
|
assert.equal(extractApiKey(req), null);
|
|
});
|
|
|
|
test("extractApiKey accepts Anthropic-Version (TitleCase) header", () => {
|
|
const req = makeRequest({
|
|
"x-api-key": "sk-titlecase-version",
|
|
"Anthropic-Version": "2024-10-22",
|
|
});
|
|
assert.equal(extractApiKey(req), "sk-titlecase-version");
|
|
});
|
|
|
|
test("extractApiKey returns the key from x-goog-api-key when Authorization and x-api-key are absent (#7034)", () => {
|
|
const req = makeRequest({ "x-goog-api-key": "sk-goog-native" });
|
|
assert.equal(extractApiKey(req), "sk-goog-native");
|
|
});
|
|
|
|
test("extractApiKey accepts uppercase X-Goog-Api-Key header casing (#7034)", () => {
|
|
const req = makeRequest({ "X-Goog-Api-Key": "sk-goog-uppercase" });
|
|
assert.equal(extractApiKey(req), "sk-goog-uppercase");
|
|
});
|
|
|
|
test("extractApiKey trims surrounding whitespace from x-goog-api-key value (#7034)", () => {
|
|
const req = makeRequest({ "x-goog-api-key": " sk-goog-padded " });
|
|
assert.equal(extractApiKey(req), "sk-goog-padded");
|
|
});
|
|
|
|
test("extractApiKey returns null when x-goog-api-key contains only whitespace (#7034)", () => {
|
|
const req = makeRequest({ "x-goog-api-key": " " });
|
|
assert.equal(extractApiKey(req), null);
|
|
});
|
|
|
|
test("extractApiKey prefers Bearer over x-goog-api-key when both are present (#7034)", () => {
|
|
const req = makeRequest({
|
|
Authorization: "Bearer sk-bearer-wins",
|
|
"x-goog-api-key": "sk-goog-loser",
|
|
});
|
|
assert.equal(extractApiKey(req), "sk-bearer-wins");
|
|
});
|
|
|
|
test("extractApiKey prefers x-api-key (with anthropic-version) over x-goog-api-key when both are present (#7034)", () => {
|
|
const req = makeRequest({
|
|
"x-api-key": "sk-anthropic-wins",
|
|
"x-goog-api-key": "sk-goog-loser",
|
|
...ANTHROPIC,
|
|
});
|
|
assert.equal(extractApiKey(req), "sk-anthropic-wins");
|
|
});
|
|
|
|
test("extractApiKey does not require anthropic-version for the x-goog-api-key fallback (#7034)", () => {
|
|
const req = makeRequest({ "x-goog-api-key": "sk-goog-no-version-needed" });
|
|
assert.equal(extractApiKey(req), "sk-goog-no-version-needed");
|
|
});
|
|
|
|
test("extractApiKey extracts a path-scoped token from /api/v1/vscode/<token>/...", () => {
|
|
const req = new Request("https://omniroute.test/api/v1/vscode/sk-test-path-token/models");
|
|
assert.equal(extractApiKey(req), "sk-test-path-token");
|
|
});
|
|
|
|
test("extractApiKey extracts a path-scoped token from /api/v1/vscode/raw/<token>/...", () => {
|
|
const req = new Request(
|
|
"https://omniroute.test/api/v1/vscode/raw/sk-test-path-token/api/version"
|
|
);
|
|
assert.equal(extractApiKey(req), "sk-test-path-token");
|
|
});
|
|
|
|
test("extractApiKey extracts a path-scoped token from /api/v1/vscode/combos/<token>/...", () => {
|
|
const req = new Request(
|
|
"https://omniroute.test/api/v1/vscode/combos/sk-test-path-token/api/version"
|
|
);
|
|
assert.equal(extractApiKey(req), "sk-test-path-token");
|
|
});
|
|
|
|
test("extractApiKey does NOT extract a query-string token (#3300 security follow-up)", () => {
|
|
// Query-string fallbacks (?token / ?key / ?apiKey / ?api_key) were removed —
|
|
// a credential in the query string leaks into access logs / Referer headers.
|
|
for (const q of ["token", "key", "apiKey", "api_key"]) {
|
|
const req = new Request(`https://omniroute.test/api/v1/models?${q}=sk-test-query-token`);
|
|
assert.equal(extractApiKey(req), null, `?${q}= must not be extracted`);
|
|
}
|
|
});
|
|
|
|
test("extractApiKey skips the path-scoped token when allowUrl is false (management auth)", () => {
|
|
const req = new Request("https://omniroute.test/api/v1/vscode/sk-test-path-token/models");
|
|
assert.equal(extractApiKey(req, { allowUrl: false }), null);
|
|
// Headers still work regardless of allowUrl.
|
|
const withHeader = new Request("https://omniroute.test/api/v1/vscode/sk-test-path-token/models", {
|
|
headers: { Authorization: "Bearer sk-header-wins" },
|
|
});
|
|
assert.equal(extractApiKey(withHeader, { allowUrl: false }), "sk-header-wins");
|
|
});
|