mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 15:30:39 +00:00
fix(gateway): let non-GET requests fall through controlUi routing when basePath is set
When controlUiBasePath is set, classifyControlUiRequest returned method-not-allowed (405) for all non-GET/HEAD requests under basePath, blocking plugin webhook handlers (BlueBubbles, Mattermost, etc.) from receiving POST requests. This is a 2026.3.1 regression. Return not-control-ui instead, matching the empty-basePath behavior, so requests fall through to plugin HTTP handlers. Remove the now-dead method-not-allowed type variant, handler branch, and utility function. Closes #31983 Closes #32275 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
Peter Steinberger
parent
11c397ef46
commit
3e9c8721fb
@@ -13,7 +13,3 @@ export function respondPlainText(res: ServerResponse, statusCode: number, body:
|
||||
export function respondNotFound(res: ServerResponse): void {
|
||||
respondPlainText(res, 404, "Not Found");
|
||||
}
|
||||
|
||||
export function respondMethodNotAllowed(res: ServerResponse): void {
|
||||
respondPlainText(res, 405, "Method Not Allowed");
|
||||
}
|
||||
|
||||
@@ -22,14 +22,26 @@ describe("classifyControlUiRequest", () => {
|
||||
expect(classified).toEqual({ kind: "not-found" });
|
||||
});
|
||||
|
||||
it("returns method-not-allowed for basePath non-read methods", () => {
|
||||
it("falls through basePath non-read methods for plugin webhooks", () => {
|
||||
const classified = classifyControlUiRequest({
|
||||
basePath: "/openclaw",
|
||||
pathname: "/openclaw",
|
||||
search: "",
|
||||
method: "POST",
|
||||
});
|
||||
expect(classified).toEqual({ kind: "method-not-allowed" });
|
||||
expect(classified).toEqual({ kind: "not-control-ui" });
|
||||
});
|
||||
|
||||
it("falls through PUT/DELETE/PATCH/OPTIONS under basePath for plugin handlers", () => {
|
||||
for (const method of ["PUT", "DELETE", "PATCH", "OPTIONS"]) {
|
||||
const classified = classifyControlUiRequest({
|
||||
basePath: "/openclaw",
|
||||
pathname: "/openclaw/webhook",
|
||||
search: "",
|
||||
method,
|
||||
});
|
||||
expect(classified, `${method} should fall through`).toEqual({ kind: "not-control-ui" });
|
||||
}
|
||||
});
|
||||
|
||||
it("returns redirect for basePath entrypoint GET", () => {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { isReadHttpMethod } from "./control-ui-http-utils.js";
|
||||
export type ControlUiRequestClassification =
|
||||
| { kind: "not-control-ui" }
|
||||
| { kind: "not-found" }
|
||||
| { kind: "method-not-allowed" }
|
||||
| { kind: "redirect"; location: string }
|
||||
| { kind: "serve" };
|
||||
|
||||
@@ -36,7 +35,7 @@ export function classifyControlUiRequest(params: {
|
||||
return { kind: "not-control-ui" };
|
||||
}
|
||||
if (!isReadHttpMethod(method)) {
|
||||
return { kind: "method-not-allowed" };
|
||||
return { kind: "not-control-ui" };
|
||||
}
|
||||
if (pathname === basePath) {
|
||||
return { kind: "redirect", location: `${basePath}/${search}` };
|
||||
|
||||
@@ -402,19 +402,18 @@ describe("handleControlUiHttpRequest", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 405 for POST requests under configured basePath", async () => {
|
||||
it("falls through POST requests under configured basePath (plugin webhook passthrough)", async () => {
|
||||
await withControlUiRoot({
|
||||
fn: async (tmp) => {
|
||||
for (const route of ["/openclaw", "/openclaw/", "/openclaw/some-page"]) {
|
||||
const { handled, res, end } = runControlUiRequest({
|
||||
const { handled, end } = runControlUiRequest({
|
||||
url: route,
|
||||
method: "POST",
|
||||
rootPath: tmp,
|
||||
basePath: "/openclaw",
|
||||
});
|
||||
expect(handled, `expected ${route} to be handled`).toBe(true);
|
||||
expect(res.statusCode, `expected ${route} status`).toBe(405);
|
||||
expect(end, `expected ${route} body`).toHaveBeenCalledWith("Method Not Allowed");
|
||||
expect(handled, `POST to ${route} should pass through to plugin handlers`).toBe(false);
|
||||
expect(end, `POST to ${route} should not write a response`).not.toHaveBeenCalled();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
import { buildControlUiCspHeader } from "./control-ui-csp.js";
|
||||
import {
|
||||
isReadHttpMethod,
|
||||
respondMethodNotAllowed,
|
||||
respondNotFound as respondControlUiNotFound,
|
||||
respondPlainText,
|
||||
} from "./control-ui-http-utils.js";
|
||||
@@ -293,10 +292,6 @@ export function handleControlUiHttpRequest(
|
||||
respondControlUiNotFound(res);
|
||||
return true;
|
||||
}
|
||||
if (route.kind === "method-not-allowed") {
|
||||
respondMethodNotAllowed(res);
|
||||
return true;
|
||||
}
|
||||
if (route.kind === "redirect") {
|
||||
applyControlUiSecurityHeaders(res);
|
||||
res.statusCode = 302;
|
||||
|
||||
Reference in New Issue
Block a user