mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 13:52:09 +03:00
fix(cli): allow nullable/optional apiKey in cliMitmStartSchema (#2857)
Integrated into release/v3.8.6.
This commit is contained in:
committed by
GitHub
parent
c9251f9326
commit
1442e086e4
@@ -56,21 +56,23 @@ export async function POST(request) {
|
||||
if (isValidationFailure(validation)) {
|
||||
return NextResponse.json({ error: validation.error }, { status: 400 });
|
||||
}
|
||||
const { apiKey: rawApiKey, sudoPassword } = validation.data;
|
||||
// (#523) Extract keyId BEFORE validation — Zod strips unknown fields!
|
||||
const apiKeyId = typeof rawBody?.keyId === "string" ? rawBody.keyId.trim() : null;
|
||||
const { apiKey: rawApiKey, keyId: rawKeyId, sudoPassword } = validation.data;
|
||||
const apiKeyId = rawKeyId ?? null;
|
||||
const apiKey = await resolveApiKey(apiKeyId, rawApiKey);
|
||||
if (!apiKey || apiKey === "sk_omniroute") {
|
||||
return NextResponse.json(
|
||||
{ error: "Missing apiKey: provide a valid apiKey or a resolvable keyId" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
const { startMitm, getCachedPassword, setCachedPassword } =
|
||||
await import("@/mitm/manager.runtime");
|
||||
const isWin = process.platform === "win32";
|
||||
const isRootUser = !isWin && isRoot();
|
||||
const pwd = sudoPassword || getCachedPassword() || "";
|
||||
|
||||
if (!apiKey || (!isWin && !pwd && !isRootUser)) {
|
||||
return NextResponse.json(
|
||||
{ error: isWin ? "Missing apiKey" : "Missing apiKey or sudoPassword" },
|
||||
{ status: 400 }
|
||||
);
|
||||
if (!isWin && !pwd && !isRootUser) {
|
||||
return NextResponse.json({ error: "Missing sudoPassword" }, { status: 400 });
|
||||
}
|
||||
|
||||
const result = await startMitm(apiKey, pwd);
|
||||
|
||||
@@ -1996,7 +1996,8 @@ export const v1betaGeminiGenerateSchema = z
|
||||
});
|
||||
|
||||
export const cliMitmStartSchema = z.object({
|
||||
apiKey: z.string().trim().min(1, "Missing apiKey"),
|
||||
apiKey: z.string().trim().min(1).nullable().optional(),
|
||||
keyId: z.string().trim().min(1).nullable().optional(),
|
||||
sudoPassword: z.string().optional(),
|
||||
});
|
||||
|
||||
|
||||
85
tests/unit/cli-mitm-schema.test.ts
Normal file
85
tests/unit/cli-mitm-schema.test.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { cliMitmStartSchema } from "../../src/shared/validation/schemas.ts";
|
||||
import { validateBody } from "../../src/shared/validation/helpers.ts";
|
||||
import { resolveApiKey } from "../../src/shared/services/apiKeyResolver.ts";
|
||||
|
||||
test("cliMitmStartSchema accepts a non-empty string apiKey", () => {
|
||||
const result = validateBody(cliMitmStartSchema, {
|
||||
apiKey: "sk-test-key-value",
|
||||
sudoPassword: "password123",
|
||||
});
|
||||
assert.equal(result.success, true);
|
||||
if (result.success) {
|
||||
assert.equal(result.data.apiKey, "sk-test-key-value");
|
||||
assert.equal(result.data.sudoPassword, "password123");
|
||||
}
|
||||
});
|
||||
|
||||
test("cliMitmStartSchema accepts a null apiKey", () => {
|
||||
const result = validateBody(cliMitmStartSchema, {
|
||||
apiKey: null,
|
||||
sudoPassword: "",
|
||||
});
|
||||
assert.equal(result.success, true);
|
||||
if (result.success) {
|
||||
assert.equal(result.data.apiKey, null);
|
||||
assert.equal(result.data.sudoPassword, "");
|
||||
}
|
||||
});
|
||||
|
||||
test("cliMitmStartSchema accepts an omitted apiKey", () => {
|
||||
const result = validateBody(cliMitmStartSchema, {
|
||||
sudoPassword: "",
|
||||
});
|
||||
assert.equal(result.success, true);
|
||||
if (result.success) {
|
||||
assert.equal(result.data.apiKey, undefined);
|
||||
}
|
||||
});
|
||||
|
||||
test("cliMitmStartSchema accepts and parses keyId correctly", () => {
|
||||
const result = validateBody(cliMitmStartSchema, {
|
||||
keyId: "api-key-id-123",
|
||||
sudoPassword: "password",
|
||||
});
|
||||
assert.equal(result.success, true);
|
||||
if (result.success) {
|
||||
assert.equal(result.data.keyId, "api-key-id-123");
|
||||
assert.equal(result.data.apiKey, undefined);
|
||||
}
|
||||
});
|
||||
|
||||
test("cliMitmStartSchema accepts null keyId", () => {
|
||||
const result = validateBody(cliMitmStartSchema, {
|
||||
keyId: null,
|
||||
sudoPassword: "",
|
||||
});
|
||||
assert.equal(result.success, true);
|
||||
if (result.success) {
|
||||
assert.equal(result.data.keyId, null);
|
||||
}
|
||||
});
|
||||
|
||||
// Regression test: null apiKey + unresolvable keyId must yield the sentinel 'sk_omniroute',
|
||||
// which the route guard must reject with a 400 rather than letting it pass to startMitm.
|
||||
test("resolveApiKey returns sentinel when apiKey is null and keyId is null", async () => {
|
||||
const result = await resolveApiKey(null, null);
|
||||
assert.equal(
|
||||
result,
|
||||
"sk_omniroute",
|
||||
"resolveApiKey should return the sentinel when no real key is available"
|
||||
);
|
||||
});
|
||||
|
||||
test("sentinel guard condition catches sk_omniroute and null", () => {
|
||||
const SENTINEL = "sk_omniroute";
|
||||
// Simulate what the route guard checks: (!apiKey || apiKey === 'sk_omniroute')
|
||||
const shouldReject = (apiKey: string | null | undefined): boolean =>
|
||||
!apiKey || apiKey === SENTINEL;
|
||||
|
||||
assert.equal(shouldReject(null), true, "null apiKey must be rejected");
|
||||
assert.equal(shouldReject(undefined), true, "undefined apiKey must be rejected");
|
||||
assert.equal(shouldReject("sk_omniroute"), true, "sentinel must be rejected");
|
||||
assert.equal(shouldReject("sk-real-key-abc"), false, "real key must be allowed");
|
||||
});
|
||||
Reference in New Issue
Block a user