chore(v2): 从兴邦预审v2白名单抽取独立可运行基座
按 docs/v2/05-implementation-plan.md §1 候选迁移表与 claude_use/T00 白名单, 从 55e2cdb 抽取 224 个文件(2.48 MB)到独立目录 F:/mini-OpenClaw-v2。 - 排除:凭据(.env/server-deploy.conf/旧DELIVERY.md)、运行数据(xingbang_data 等 4 个 SQLite)、依赖(venv/node_modules 符号链接)、归档(_backup*/test-evidence/ ori_file)、Office 临时文件 - 目标目录独立 git 仓库,初始恢复点;Gitea 远程与分支待 OQ-06 Q2 定案 - 未部署、未启动服务、未调用模型 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import {NextRequest, NextResponse} from "next/server";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const runtime = "nodejs";
|
||||
const allowed = new Set(["chat", "sessions", "files", "skills", "tokens", "config", "xingbang"]);
|
||||
|
||||
async function proxy(request:NextRequest, {params}:{params:{path:string[]}}) {
|
||||
if (!allowed.has(params.path[0]) || params.path.some(p=>!p || p==="." || p===".." || /[\\/]/.test(p)))
|
||||
return NextResponse.json({detail:"接口不存在"},{status:404});
|
||||
const base = process.env.CORE_BACKEND_URL || process.env.RISK_AGENT_BACKEND_URL || "http://127.0.0.1:8002";
|
||||
try {
|
||||
// Production nextUrl can contain the bind address (0.0.0.0), not the client Host.
|
||||
// Never use Host as authentication: all production core/tool access requires admin.
|
||||
const authorization = request.headers.get("authorization") || "";
|
||||
const host = request.headers.get("host") || "";
|
||||
const requiresAuth = process.env.NODE_ENV === "production" || process.env.XINGBANG_REQUIRE_AUTH === "1" || !!process.env.XINGBANG_AUTH_USERS || !!authorization;
|
||||
if (requiresAuth) {
|
||||
if (!authorization.startsWith("Bearer ") || !authorization.slice(7).trim())
|
||||
return NextResponse.json({detail:"请先在风险研判页面连接管理员访问身份(401)"},{status:401});
|
||||
const identity = await fetch(`${base}/api/xingbang/risk-agent/access/context`, {
|
||||
headers:{Authorization:authorization,"X-Xingbang-Client-Host":host},
|
||||
cache:"no-store",redirect:"error",signal:request.signal,
|
||||
});
|
||||
if (!identity.ok) return NextResponse.json({detail:"访问身份校验失败"},{status:identity.status === 401 || identity.status === 403 ? identity.status : 503});
|
||||
const user = await identity.json();
|
||||
if (user.role !== "admin" || user.preview || !user.id)
|
||||
return NextResponse.json({detail:"通用聊天与文件工具仅限已认证管理员"},{status:403});
|
||||
} else {
|
||||
let hostname = "";
|
||||
try { hostname = new URL(`http://${host}`).hostname; } catch { /* reject malformed Host */ }
|
||||
if (!["localhost","127.0.0.1","::1","[::1]"].includes(hostname))
|
||||
return NextResponse.json({detail:"未配置身份认证,仅允许本机开发访问"},{status:403});
|
||||
const origin = request.headers.get("origin");
|
||||
if ((origin && new URL(origin).host !== host) || request.headers.get("sec-fetch-site") === "cross-site")
|
||||
return NextResponse.json({detail:"不允许跨站访问开发工具"},{status:403});
|
||||
}
|
||||
const upstream = await fetch(`${base}/api/${params.path.map(encodeURIComponent).join("/")}${request.nextUrl.search}`, {
|
||||
method:request.method,headers:{"Content-Type":request.headers.get("content-type")||"application/json",Authorization:authorization},
|
||||
body:["GET","HEAD"].includes(request.method)?undefined:await request.arrayBuffer(),
|
||||
cache:"no-store",redirect:"error",signal:request.signal,
|
||||
});
|
||||
const headers = new Headers({"Content-Type":upstream.headers.get("content-type")||"application/json", "Cache-Control":"no-cache, no-transform", "X-Accel-Buffering":"no"});
|
||||
// Never arrayBuffer()/text() an SSE response: pass through chunks immediately.
|
||||
return new NextResponse(upstream.body,{status:upstream.status,headers});
|
||||
} catch {
|
||||
return NextResponse.json({detail:"当前聊天后端未连接,请检查本地服务"},{status:502});
|
||||
}
|
||||
}
|
||||
export {proxy as GET, proxy as POST, proxy as PUT, proxy as DELETE};
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
async function proxy(request: NextRequest, {params}: {params:{path:string[]}}) {
|
||||
const base=process.env.RISK_AGENT_BACKEND_URL || "http://127.0.0.1:8002";
|
||||
try {
|
||||
const response=await fetch(`${base}/api/xingbang/risk-agent/${params.path.map(encodeURIComponent).join("/")}${request.nextUrl.search}`,{
|
||||
method:request.method,headers:{"Content-Type":request.headers.get("content-type")||"application/json", "Authorization":request.headers.get("authorization")||"", "X-Xingbang-Demo-Role":request.headers.get("x-xingbang-demo-role")||"", "X-Xingbang-Client-Host":request.headers.get("host")||"", "Idempotency-Key":request.headers.get("idempotency-key")||""},
|
||||
body:["GET","HEAD"].includes(request.method)?undefined:await request.arrayBuffer(),cache:"no-store",
|
||||
});
|
||||
const headers=new Headers({"Content-Type":response.headers.get("content-type")||"application/json"});
|
||||
const disposition=response.headers.get("content-disposition");
|
||||
if(disposition)headers.set("Content-Disposition",disposition);
|
||||
return new NextResponse(await response.arrayBuffer(),{status:response.status,headers});
|
||||
} catch {
|
||||
return NextResponse.json({detail:"风险 Agent 服务未连接,请检查后端运行状态"},{status:502});
|
||||
}
|
||||
}
|
||||
export {proxy as GET,proxy as POST,proxy as PUT,proxy as DELETE};
|
||||
Reference in New Issue
Block a user