diff --git a/frontend/src/lib/admin/admin-error-response.ts b/frontend/src/lib/admin/admin-error-response.ts index d73a7d3c..234e3c5a 100644 --- a/frontend/src/lib/admin/admin-error-response.ts +++ b/frontend/src/lib/admin/admin-error-response.ts @@ -7,10 +7,30 @@ function isPostgresError(error: unknown): error is { code: string } { && typeof (error as { code?: unknown }).code === "string"; } +function errorMessage(error: unknown) { + return typeof error === "object" && error !== null && "message" in error + && typeof (error as { message?: unknown }).message === "string" + ? (error as { message: string }).message + : ""; +} + export function adminErrorResponse(error: unknown) { if (error instanceof AdminAuthorizationError) { return NextResponse.json({ error: error.message }, { status: error.status }); } + const message = errorMessage(error); + if (message.includes("模型供应商地址不在服务器允许列表中")) { + return NextResponse.json({ error: message }, { status: 400 }); + } + if (message.includes("网关地址不允许")) { + return NextResponse.json({ error: message }, { status: 400 }); + } + if (message.includes("model_provider_url_unsafe")) { + return NextResponse.json({ error: "模型供应商地址不安全或解析到内网" }, { status: 400 }); + } + if (message.includes("模型供应商密钥未配置") || message.includes("model_provider_key_required")) { + return NextResponse.json({ error: "模型供应商密钥未配置" }, { status: 409 }); + } if (isPostgresError(error)) { if (error.code === "42501") return NextResponse.json({ error: "无权执行此操作" }, { status: 403 }); if (error.code === "40001") return NextResponse.json({ error: "资源已被其他管理员修改,请刷新后重试" }, { status: 409 }); diff --git a/frontend/tests/admin-auth.test.ts b/frontend/tests/admin-auth.test.ts index 76ea6df6..6ce241d2 100644 --- a/frontend/tests/admin-auth.test.ts +++ b/frontend/tests/admin-auth.test.ts @@ -168,6 +168,25 @@ test("configuration, identity reader, and RBAC query failures become sanitized 5 } }); +test("adminErrorResponse maps model provider configuration failures without exposing details", async () => { + for (const [error, status, message] of [ + [new Error("模型供应商地址不在服务器允许列表中"), 400, "模型供应商地址不在服务器允许列表中"], + [new Error("网关地址不允许解析到本机、内网或保留地址"), 400, "网关地址不允许解析到本机、内网或保留地址"], + [new Error("model_provider_url_unsafe"), 400, "模型供应商地址不安全或解析到内网"], + [new Error("model_provider_key_required"), 409, "模型供应商密钥未配置"], + ] as const) { + const response = adminErrorResponse(error); + assert.equal(response.status, status); + assert.deepEqual(await response.json(), { error: message }); + } + + for (const error of [new Error("model_provider_runtime_immutable"), new Error("model_provider_code_immutable")]) { + const response = adminErrorResponse(error); + assert.equal(response.status, 500); + assert.deepEqual(await response.json(), { error: "后台服务暂时不可用" }); + } +}); + test("adminErrorResponse preserves the sanitized authorization 503", async () => { const response = adminErrorResponse( new AdminAuthorizationError("后台服务暂时不可用", 503),