
























@@ -48,15 +48,15 @@ describe("handleControlUiHttpRequest", () => {
4848expect(params.end).toHaveBeenCalledWith("Not Found");
4949}
505051-function runControlUiRequest(params: {
51+async function runControlUiRequest(params: {
5252url: string;
5353method: "GET" | "HEAD" | "POST";
5454rootPath: string;
5555basePath?: string;
5656rootKind?: "resolved" | "bundled";
5757}) {
5858const { res, end } = makeMockHttpResponse();
59-const handled = handleControlUiHttpRequest(
59+const handled = await handleControlUiHttpRequest(
6060{ url: params.url, method: params.method } as IncomingMessage,
6161res,
6262{
@@ -67,6 +67,33 @@ describe("handleControlUiHttpRequest", () => {
6767return { res, end, handled };
6868}
696970+async function runBootstrapConfigRequest(params: {
71+rootPath: string;
72+basePath?: string;
73+auth?: ResolvedGatewayAuth;
74+headers?: IncomingMessage["headers"];
75+}) {
76+const { res, end } = makeMockHttpResponse();
77+const url = params.basePath
78+ ? `${params.basePath}${CONTROL_UI_BOOTSTRAP_CONFIG_PATH}`
79+ : CONTROL_UI_BOOTSTRAP_CONFIG_PATH;
80+const handled = await handleControlUiHttpRequest(
81+{
82+ url,
83+method: "GET",
84+headers: params.headers ?? {},
85+socket: { remoteAddress: "127.0.0.1" },
86+} as IncomingMessage,
87+res,
88+{
89+ ...(params.basePath ? { basePath: params.basePath } : {}),
90+ ...(params.auth ? { auth: params.auth } : {}),
91+root: { kind: "resolved", path: params.rootPath },
92+},
93+);
94+return { res, end, handled };
95+}
96+7097async function runAvatarRequest(params: {
7198url: string;
7299method: "GET" | "HEAD";
@@ -241,7 +268,7 @@ describe("handleControlUiHttpRequest", () => {
241268await withControlUiRoot({
242269fn: async (tmp) => {
243270const { res, setHeader } = makeMockHttpResponse();
244-const handled = handleControlUiHttpRequest(
271+const handled = await handleControlUiHttpRequest(
245272{ url: "/", method: "GET" } as IncomingMessage,
246273res,
247274{
@@ -405,7 +432,7 @@ describe("handleControlUiHttpRequest", () => {
405432indexHtml: html,
406433fn: async (tmp) => {
407434const { res, setHeader } = makeMockHttpResponse();
408-handleControlUiHttpRequest({ url: "/", method: "GET" } as IncomingMessage, res, {
435+await handleControlUiHttpRequest({ url: "/", method: "GET" } as IncomingMessage, res, {
409436root: { kind: "resolved", path: tmp },
410437});
411438const cspCalls = setHeader.mock.calls.filter(
@@ -424,7 +451,7 @@ describe("handleControlUiHttpRequest", () => {
424451indexHtml: html,
425452fn: async (tmp) => {
426453const { res, end } = makeMockHttpResponse();
427-const handled = handleControlUiHttpRequest(
454+const handled = await handleControlUiHttpRequest(
428455{ url: "/", method: "GET" } as IncomingMessage,
429456res,
430457{
@@ -445,7 +472,7 @@ describe("handleControlUiHttpRequest", () => {
445472await withControlUiRoot({
446473fn: async (tmp) => {
447474const { res, end } = makeMockHttpResponse();
448-const handled = handleControlUiHttpRequest(
475+const handled = await handleControlUiHttpRequest(
449476{ url: CONTROL_UI_BOOTSTRAP_CONFIG_PATH, method: "GET" } as IncomingMessage,
450477res,
451478{
@@ -467,11 +494,43 @@ describe("handleControlUiHttpRequest", () => {
467494});
468495});
469496497+it("rejects bootstrap config requests without a valid auth token when auth is enabled", async () => {
498+await withControlUiRoot({
499+fn: async (tmp) => {
500+const { res, handled, end } = await runBootstrapConfigRequest({
501+rootPath: tmp,
502+auth: { mode: "token", token: "test-token", allowTailscale: false },
503+});
504+expect(handled).toBe(true);
505+expect(res.statusCode).toBe(401);
506+expect(String(end.mock.calls[0]?.[0] ?? "")).toContain("Unauthorized");
507+},
508+});
509+});
510+511+it("serves bootstrap config JSON when auth is enabled and the token is valid", async () => {
512+await withControlUiRoot({
513+fn: async (tmp) => {
514+const { res, handled, end } = await runBootstrapConfigRequest({
515+rootPath: tmp,
516+auth: { mode: "token", token: "test-token", allowTailscale: false },
517+headers: {
518+authorization: "Bearer test-token",
519+},
520+});
521+expect(handled).toBe(true);
522+expect(res.statusCode).toBe(200);
523+const parsed = parseBootstrapPayload(end);
524+expect(parsed.assistantAgentId).toBe("main");
525+},
526+});
527+});
528+470529it("serves bootstrap config JSON under basePath", async () => {
471530await withControlUiRoot({
472531fn: async (tmp) => {
473532const { res, end } = makeMockHttpResponse();
474-const handled = handleControlUiHttpRequest(
533+const handled = await handleControlUiHttpRequest(
475534{ url: `/openclaw${CONTROL_UI_BOOTSTRAP_CONFIG_PATH}`, method: "GET" } as IncomingMessage,
476535res,
477536{
@@ -613,7 +672,7 @@ describe("handleControlUiHttpRequest", () => {
613672await fs.symlink(outsideFile, path.join(assetsDir, "leak.txt"));
614673615674const { res, end } = makeMockHttpResponse();
616-const handled = handleControlUiHttpRequest(
675+const handled = await handleControlUiHttpRequest(
617676{ url: "/assets/leak.txt", method: "GET" } as IncomingMessage,
618677res,
619678{
@@ -634,7 +693,7 @@ describe("handleControlUiHttpRequest", () => {
634693const { assetsDir, filePath } = await writeAssetFile(tmp, "actual.txt", "inside-ok\n");
635694await fs.symlink(filePath, path.join(assetsDir, "linked.txt"));
636695637-const { res, end, handled } = runControlUiRequest({
696+const { res, end, handled } = await runControlUiRequest({
638697url: "/assets/linked.txt",
639698method: "GET",
640699rootPath: tmp,
@@ -652,7 +711,7 @@ describe("handleControlUiHttpRequest", () => {
652711fn: async (tmp) => {
653712await writeAssetFile(tmp, "actual.txt", "inside-ok\n");
654713655-const { res, end, handled } = runControlUiRequest({
714+const { res, end, handled } = await runControlUiRequest({
656715url: "/assets/actual.txt",
657716method: "HEAD",
658717rootPath: tmp,
@@ -675,7 +734,7 @@ describe("handleControlUiHttpRequest", () => {
675734await fs.rm(path.join(tmp, "index.html"));
676735await fs.symlink(outsideIndex, path.join(tmp, "index.html"));
677736678-const { res, end, handled } = runControlUiRequest({
737+const { res, end, handled } = await runControlUiRequest({
679738url: "/app/route",
680739method: "GET",
681740rootPath: tmp,
@@ -698,7 +757,7 @@ describe("handleControlUiHttpRequest", () => {
698757await fs.rm(path.join(tmp, "index.html"));
699758await fs.link(outsideIndex, path.join(tmp, "index.html"));
700759701-const { res, end, handled } = runControlUiRequest({
760+const { res, end, handled } = await runControlUiRequest({
702761url: "/",
703762method: "GET",
704763rootPath: tmp,
@@ -716,7 +775,7 @@ describe("handleControlUiHttpRequest", () => {
716775fn: async (tmp) => {
717776await createHardlinkedAssetFile(tmp);
718777719-const { res, end, handled } = runControlUiRequest({
778+const { res, end, handled } = await runControlUiRequest({
720779url: "/assets/app.hl.js",
721780method: "GET",
722781rootPath: tmp,
@@ -734,7 +793,7 @@ describe("handleControlUiHttpRequest", () => {
734793fn: async (tmp) => {
735794await createHardlinkedAssetFile(tmp);
736795737-const { res, end, handled } = runControlUiRequest({
796+const { res, end, handled } = await runControlUiRequest({
738797url: "/assets/app.hl.js",
739798method: "GET",
740799rootPath: tmp,
@@ -753,7 +812,7 @@ describe("handleControlUiHttpRequest", () => {
753812fn: async (tmp) => {
754813for (const webhookPath of ["/bluebubbles-webhook", "/custom-webhook", "/callback"]) {
755814const { res } = makeMockHttpResponse();
756-const handled = handleControlUiHttpRequest(
815+const handled = await handleControlUiHttpRequest(
757816{ url: webhookPath, method: "POST" } as IncomingMessage,
758817res,
759818{ root: { kind: "resolved", path: tmp } },
@@ -770,7 +829,7 @@ describe("handleControlUiHttpRequest", () => {
770829await withControlUiRoot({
771830fn: async (tmp) => {
772831const { res } = makeMockHttpResponse();
773-const handled = handleControlUiHttpRequest(
832+const handled = await handleControlUiHttpRequest(
774833{ url: "/bluebubbles-webhook", method: "POST" } as IncomingMessage,
775834res,
776835{ basePath: "/openclaw", root: { kind: "resolved", path: tmp } },
@@ -784,7 +843,7 @@ describe("handleControlUiHttpRequest", () => {
784843await withControlUiRoot({
785844fn: async (tmp) => {
786845for (const apiPath of ["/api", "/api/sessions", "/api/channels/nostr"]) {
787-const { handled } = runControlUiRequest({
846+const { handled } = await runControlUiRequest({
788847url: apiPath,
789848method: "GET",
790849rootPath: tmp,
@@ -799,7 +858,7 @@ describe("handleControlUiHttpRequest", () => {
799858await withControlUiRoot({
800859fn: async (tmp) => {
801860for (const pluginPath of ["/plugins", "/plugins/diffs/view/abc/def"]) {
802-const { handled } = runControlUiRequest({
861+const { handled } = await runControlUiRequest({
803862url: pluginPath,
804863method: "GET",
805864rootPath: tmp,
@@ -813,7 +872,7 @@ describe("handleControlUiHttpRequest", () => {
813872it("falls through POST requests when basePath is empty", async () => {
814873await withControlUiRoot({
815874fn: async (tmp) => {
816-const { handled, end } = runControlUiRequest({
875+const { handled, end } = await runControlUiRequest({
817876url: "/webhook/bluebubbles",
818877method: "POST",
819878rootPath: tmp,
@@ -828,7 +887,7 @@ describe("handleControlUiHttpRequest", () => {
828887await withControlUiRoot({
829888fn: async (tmp) => {
830889for (const route of ["/openclaw", "/openclaw/", "/openclaw/some-page"]) {
831-const { handled, end } = runControlUiRequest({
890+const { handled, end } = await runControlUiRequest({
832891url: route,
833892method: "POST",
834893rootPath: tmp,
@@ -850,7 +909,7 @@ describe("handleControlUiHttpRequest", () => {
850909851910const secretPathUrl = secretPath.split(path.sep).join("/");
852911const absolutePathUrl = secretPathUrl.startsWith("/") ? secretPathUrl : `/${secretPathUrl}`;
853-const { res, end, handled } = runControlUiRequest({
912+const { res, end, handled } = await runControlUiRequest({
854913url: `/openclaw/${absolutePathUrl}`,
855914method: "GET",
856915rootPath: root,
@@ -879,7 +938,7 @@ describe("handleControlUiHttpRequest", () => {
879938throw error;
880939}
881940882-const { res, end, handled } = runControlUiRequest({
941+const { res, end, handled } = await runControlUiRequest({
883942url: "/openclaw/assets/leak.txt",
884943method: "GET",
885944rootPath: root,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。