feat: gate admin ui with optional access key

This commit is contained in:
2026-06-30 19:46:03 +08:00
parent 41357f5e7a
commit d90a1cd94f
4 changed files with 300 additions and 22 deletions
+52 -19
View File
@@ -11,11 +11,15 @@ import path from "node:path";
const gatewayRoot = path.resolve(import.meta.dirname, "..");
const gatewayEntry = path.join(gatewayRoot, "gateway.mjs");
function assert(condition, message) {
if (!condition) {
throw new Error(message);
}
}
function assert(condition, message) {
if (!condition) {
throw new Error(message);
}
}
const adminHeaders = {
"x-codex-retry-gateway-key": "test-admin-key",
};
async function getFreePort() {
const server = net.createServer();
@@ -388,6 +392,7 @@ async function run() {
"Selected model is at capacity. Please try a different model.",
"stream disconnected before completion: Concurrency limit exceeded for account, please retry later",
],
management_access_key: "test-admin-key",
upstream_fetch_retry_attempts: 5,
upstream_fetch_retry_backoff_ms: 25,
non_stream_status_code: 502,
@@ -410,8 +415,35 @@ async function run() {
`${error?.message || error}\nstdout:\n${output.stdout || "(empty)"}\nstderr:\n${output.stderr || "(empty)"}`,
);
}
const modelsResponse = await fetch(`http://127.0.0.1:${gatewayPort}/v1/models`);
const lockedUiResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/ui`);
assert(lockedUiResponse.status === 401, `未带 key 的 UI 不应可访问: ${lockedUiResponse.status}`);
const lockedUiText = await lockedUiResponse.text();
assert(lockedUiText.includes("Access key"), "未带 key 的 UI 未返回 access key 页面");
const lockedStatusResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/status`);
assert(lockedStatusResponse.status === 401, `未带 key 的 status API 不应可访问: ${lockedStatusResponse.status}`);
const unlockedUiResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/ui?key=test-admin-key`, {
redirect: "manual",
});
assert(unlockedUiResponse.status === 200, `带 key 的 UI 访问失败: ${unlockedUiResponse.status}`);
assert(
(unlockedUiResponse.headers.get("set-cookie") || "").includes("codex_retry_gateway_access=test-admin-key"),
"带 key 的 UI 未设置 access cookie",
);
const unlockedStatusResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/status`, {
headers: { "x-codex-retry-gateway-key": "test-admin-key" },
});
assert(unlockedStatusResponse.status === 200, `带 key 的 status API 访问失败: ${unlockedStatusResponse.status}`);
const unlockedStatusPayload = await unlockedStatusResponse.json();
assert(
unlockedStatusPayload?.config?.management_access_key_configured === true,
"status API 未暴露 management_access_key_configured",
);
const modelsResponse = await fetch(`http://127.0.0.1:${gatewayPort}/v1/models`);
assert(modelsResponse.status === 200, `/v1/models 透传状态异常: ${modelsResponse.status}`);
assert(
modelsResponse.headers.get("x-upstream-test") === "models-ok",
@@ -455,7 +487,7 @@ async function run() {
assert(recoveredResponse.status === 200, `首次 fetch failed 后未自动恢复: ${recoveredResponse.status}`);
assert(recoveredBody?.retry_attempt === 2, "首次 fetch failed 后未命中第二次上游请求");
const requestsResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?limit=20`);
const requestsResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?limit=20`, { headers: adminHeaders });
const requestsPayload = await requestsResponse.json();
const recoveredEntry = requestsPayload?.entries?.find((entry) => entry.path === "/responses" && entry.status_code === 200);
assert(requestsResponse.status === 200, `请求历史 API 状态异常: ${requestsResponse.status}`);
@@ -475,7 +507,7 @@ async function run() {
assert(threadTrackedBody?.id === "resp_test", "thread non-stream 返回体缺少 response id");
assert(threadTrackedBody?.thread_id === "thread_nonstream", "thread non-stream 返回体缺少 thread_id");
const threadRequestsResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?query=${encodeURIComponent("thread_nonstream")}`);
const threadRequestsResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?query=${encodeURIComponent("thread_nonstream")}`, { headers: adminHeaders });
const threadRequestsPayload = await threadRequestsResponse.json();
const threadEntry = (threadRequestsPayload?.entries || []).find((entry) => entry.thread_id === "thread_nonstream");
assert(threadEntry?.response_id === "resp_test", "non-stream 请求记录未保留 response_id");
@@ -515,7 +547,7 @@ async function run() {
assert(differentPathResponse.status === 200, `不同路径发送失败: ${differentPathResponse.status}`);
await differentPathResponse.json();
const requestIdRequestsResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?limit=40`);
const requestIdRequestsResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?limit=40`, { headers: adminHeaders });
const requestIdRequestsPayload = await requestIdRequestsResponse.json();
const sameRequestEntries = (requestIdRequestsPayload?.entries || []).filter(
(entry) => entry.path === "/responses" && entry.request_body_bytes === Buffer.byteLength(sameRequestPayload),
@@ -543,6 +575,7 @@ async function run() {
const requestIdQueryResponse = await fetch(
`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?query=${encodeURIComponent(sameRequestEntryA.request_id)}`,
{ headers: adminHeaders },
);
const requestIdQueryPayload = await requestIdQueryResponse.json();
assert(requestIdQueryResponse.status === 200, `request_id 搜索失败: ${requestIdQueryResponse.status}`);
@@ -595,7 +628,7 @@ async function run() {
"capacity 抖动恢复后的返回体异常",
);
const requestsAfterCapacityRecoveryResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?limit=20`);
const requestsAfterCapacityRecoveryResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?limit=20`, { headers: adminHeaders });
const requestsAfterCapacityRecovery = await requestsAfterCapacityRecoveryResponse.json();
const capacityRecoveredEntry = requestsAfterCapacityRecovery?.entries?.find(
(entry) => entry.path === "/responses" && entry.status_code === 200 && entry.upstream_attempt_count >= 3,
@@ -615,7 +648,7 @@ async function run() {
"200+capacity 抖动恢复后的返回体异常",
);
const requestsAfterCapacityStatus200RecoveryResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?limit=30`);
const requestsAfterCapacityStatus200RecoveryResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?limit=30`, { headers: adminHeaders });
const requestsAfterCapacityStatus200Recovery = await requestsAfterCapacityStatus200RecoveryResponse.json();
const capacityStatus200RecoveredEntry = requestsAfterCapacityStatus200Recovery?.entries?.find(
(entry) => entry.path === "/responses" && entry.status_code === 200 && entry.upstream_attempt_count >= 3,
@@ -660,7 +693,7 @@ async function run() {
assert(streamCapacityRecoveredResponse.status === 200, `stream capacity 抖动后未自动恢复: ${streamCapacityRecoveredResponse.status}`);
assert(streamCapacityRecoveredText.includes("hello"), "stream capacity 恢复后未拿到正常 SSE 内容");
const requestsAfterStreamCapacityRecoveryResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?limit=20`);
const requestsAfterStreamCapacityRecoveryResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?limit=20`, { headers: adminHeaders });
const requestsAfterStreamCapacityRecovery = await requestsAfterStreamCapacityRecoveryResponse.json();
const streamCapacityRecoveredEntry = requestsAfterStreamCapacityRecovery?.entries?.find(
(entry) => entry.path === "/responses" && entry.status_code === 200 && entry.response_stream && entry.upstream_attempt_count >= 3,
@@ -682,7 +715,7 @@ async function run() {
assert(streamCapacityResponseFailedRecoveredResponse.status === 200, `stream response.failed capacity 抖动后未自动恢复: ${streamCapacityResponseFailedRecoveredResponse.status}`);
assert(streamCapacityResponseFailedRecoveredText.includes("hello"), "stream response.failed capacity 恢复后未拿到正常 SSE 内容");
const requestsAfterStreamCapacityResponseFailedRecoveryResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?limit=30`);
const requestsAfterStreamCapacityResponseFailedRecoveryResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?limit=30`, { headers: adminHeaders });
const requestsAfterStreamCapacityResponseFailedRecovery = await requestsAfterStreamCapacityResponseFailedRecoveryResponse.json();
const streamCapacityResponseFailedRecoveredEntry = requestsAfterStreamCapacityResponseFailedRecovery?.entries?.find(
(entry) => entry.path === "/responses" && entry.status_code === 200 && entry.response_stream && entry.upstream_attempt_count >= 3,
@@ -694,7 +727,7 @@ async function run() {
{ stream: true, test_reasoning_tokens: 128, thread_id: "thread_stream_ok" },
);
assert(streamThreadResponse.status === 200, `stream thread 请求失败: ${streamThreadResponse.status}`);
const streamThreadRequestsResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?query=${encodeURIComponent("thread_stream_ok")}`);
const streamThreadRequestsResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?query=${encodeURIComponent("thread_stream_ok")}`, { headers: adminHeaders });
const streamThreadRequestsPayload = await streamThreadRequestsResponse.json();
const streamThreadEntry = (streamThreadRequestsPayload?.entries || []).find((entry) => entry.thread_id === "thread_stream_ok");
assert(streamThreadEntry?.response_id === "resp_stream", "stream 请求记录未保留 response_id");
@@ -717,7 +750,7 @@ async function run() {
});
assert(metadataThreadResponse.status === 200, `metadata thread 请求失败: ${metadataThreadResponse.status}`);
await metadataThreadResponse.json();
const metadataThreadRequestsResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?query=${encodeURIComponent("thread_client_metadata")}`);
const metadataThreadRequestsResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?query=${encodeURIComponent("thread_client_metadata")}`, { headers: adminHeaders });
const metadataThreadRequestsPayload = await metadataThreadRequestsResponse.json();
const metadataThreadEntry = (metadataThreadRequestsPayload?.entries || []).find((entry) => entry.thread_id === "thread_client_metadata");
assert(metadataThreadEntry?.thread_id === "thread_client_metadata", "client_metadata.thread_id 未写入请求记录");
@@ -770,7 +803,7 @@ async function run() {
body: JSON.stringify({ stream: true, test_reasoning_tokens: 128, test_stream_chunk_delay_ms: 180 }),
});
await new Promise((resolve) => setTimeout(resolve, 260));
const midRequestsResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?limit=20`);
const midRequestsResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/requests?limit=20`, { headers: adminHeaders });
const midRequestsPayload = await midRequestsResponse.json();
const inFlightStreamEntry = midRequestsPayload?.entries?.find(
(entry) =>
@@ -800,7 +833,7 @@ async function run() {
);
assert(terminatedStream.status === 502, `/responses 上游半路断流未返回 502: ${terminatedStream.status}`);
const metricsBeforeRestartResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/status`);
const metricsBeforeRestartResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/status`, { headers: adminHeaders });
const metricsBeforeRestart = await metricsBeforeRestartResponse.json();
assert(metricsBeforeRestartResponse.status === 200, `status API 状态异常: ${metricsBeforeRestartResponse.status}`);
assert(metricsBeforeRestart?.metrics?.reasoning_516_count >= 1, "重启前 reasoning_516_count 未累计");
@@ -812,7 +845,7 @@ async function run() {
gateway = startGateway(configPath, logPath);
await waitForHealth(`http://127.0.0.1:${gatewayPort}${config.health_path}`);
const metricsAfterRestartResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/status`);
const metricsAfterRestartResponse = await fetch(`http://127.0.0.1:${gatewayPort}/__codex_retry_gateway/api/status`, { headers: adminHeaders });
const metricsAfterRestart = await metricsAfterRestartResponse.json();
assert(metricsAfterRestartResponse.status === 200, `重启后 status API 状态异常: ${metricsAfterRestartResponse.status}`);
assert(metricsAfterRestart?.metrics?.reasoning_516_count >= metricsBeforeRestart?.metrics?.reasoning_516_count, "重启后 reasoning_516_count 未保留");