mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
fix(authz): fall back to URL token when Authorization isn't a usable Bearer (#3504)
Integrated into release/v3.8.18
This commit is contained in:
@@ -9,12 +9,19 @@ function extractBearer(request: Request): string | null {
|
||||
const xApiKey = request.headers.get("x-api-key") ?? request.headers.get("X-Api-Key");
|
||||
if (raw) {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed.toLowerCase().startsWith("bearer ")) return null;
|
||||
return trimmed.slice(7).trim() || null;
|
||||
if (trimmed.toLowerCase().startsWith("bearer ")) {
|
||||
const token = trimmed.slice(7).trim();
|
||||
if (token) return token;
|
||||
}
|
||||
// A non-"Bearer <token>" Authorization header (an empty "Bearer ", or a
|
||||
// client's own non-OmniRoute token — VS Code Copilot sends one even when the
|
||||
// OmniRoute key lives in the URL path of a /vscode tokenized endpoint) must
|
||||
// NOT short-circuit auth. Fall through to x-api-key and the path-scoped URL
|
||||
// token below instead of rejecting the request with "Authentication required".
|
||||
}
|
||||
|
||||
if (xApiKey) {
|
||||
return xApiKey?.trim() || null;
|
||||
return xApiKey.trim() || null;
|
||||
}
|
||||
|
||||
return extractApiKey(request);
|
||||
|
||||
@@ -218,3 +218,53 @@ test("#2257 — no bearer + REQUIRE_API_KEY=false → anonymous (unchanged, no f
|
||||
console.warn = originalWarn;
|
||||
}
|
||||
});
|
||||
|
||||
// ─── #3504 — non-usable Authorization must NOT short-circuit the URL path token ─
|
||||
// VS Code Copilot sends its own (empty / non-OmniRoute) Authorization header even
|
||||
// when the OmniRoute key lives in the URL path of a /vscode tokenized endpoint.
|
||||
// A non-"Bearer <token>" Authorization must fall through to the URL token instead
|
||||
// of returning null and 401'ing under REQUIRE_API_KEY=true.
|
||||
|
||||
// validateApiKey is the real (no-DB → always-false) implementation here, so we
|
||||
// distinguish "URL token was extracted" from "no token found" by the rejection
|
||||
// MESSAGE: an extracted-but-unknown token → "Invalid API key"; nothing extracted
|
||||
// → "Authentication required". On the pre-fix code a non-Bearer Authorization
|
||||
// returned null, so these would all 401 with "Authentication required".
|
||||
|
||||
test("#3504 — empty 'Bearer ' Authorization falls through to the URL path token", async () => {
|
||||
process.env.REQUIRE_API_KEY = "true";
|
||||
const policy = await loadPolicy();
|
||||
const headers = new Headers({ authorization: "Bearer " });
|
||||
const out = await policy.evaluate(
|
||||
ctx(headers, "/api/v1/vscode/sk-url-token/chat/completions")
|
||||
);
|
||||
assert.equal(out.allow, false);
|
||||
if (!out.allow) {
|
||||
assert.equal(out.status, 401);
|
||||
assert.equal(
|
||||
out.message,
|
||||
"Invalid API key",
|
||||
"URL token must be extracted (→ 'Invalid API key'), not skipped (→ 'Authentication required')"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("#3504 — a non-Bearer scheme (Basic) also falls through to the URL token", async () => {
|
||||
process.env.REQUIRE_API_KEY = "true";
|
||||
const policy = await loadPolicy();
|
||||
const headers = new Headers({ authorization: "Basic Zm9vOmJhcg==" });
|
||||
const out = await policy.evaluate(
|
||||
ctx(headers, "/api/v1/vscode/sk-url-token/chat/completions")
|
||||
);
|
||||
assert.equal(out.allow, false);
|
||||
if (!out.allow) assert.equal(out.message, "Invalid API key");
|
||||
});
|
||||
|
||||
test("#3504 — non-Bearer Authorization with NO URL token still rejects as unauthenticated", async () => {
|
||||
process.env.REQUIRE_API_KEY = "true";
|
||||
const policy = await loadPolicy();
|
||||
const headers = new Headers({ authorization: "Bearer " });
|
||||
const out = await policy.evaluate(ctx(headers, "/api/v1/chat/completions"));
|
||||
assert.equal(out.allow, false);
|
||||
if (!out.allow) assert.equal(out.message, "Authentication required");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user