mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 16:48:26 +00:00
fix(gateway): require auth for canvas host and a2ui assets (#9518) (thanks @coygeek)
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
import { createServer as createHttpsServer } from "node:https";
|
||||
import type { CanvasHostHandler } from "../canvas-host/server.js";
|
||||
import type { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import type { GatewayWsClient } from "./server/ws-types.js";
|
||||
import { resolveAgentAvatar } from "../agents/identity-avatar.js";
|
||||
import {
|
||||
A2UI_PATH,
|
||||
@@ -18,7 +19,7 @@ import {
|
||||
} from "../canvas-host/a2ui.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { handleSlackHttpRequest } from "../slack/http/index.js";
|
||||
import { authorizeGatewayConnect } from "./auth.js";
|
||||
import { authorizeGatewayConnect, isLocalDirectRequest, type ResolvedGatewayAuth } from "./auth.js";
|
||||
import {
|
||||
handleControlUiAvatarRequest,
|
||||
handleControlUiHttpRequest,
|
||||
@@ -38,7 +39,8 @@ import {
|
||||
resolveHookDeliver,
|
||||
} from "./hooks.js";
|
||||
import { sendUnauthorized } from "./http-common.js";
|
||||
import { getBearerToken } from "./http-utils.js";
|
||||
import { getBearerToken, getHeader } from "./http-utils.js";
|
||||
import { resolveGatewayClientIp } from "./net.js";
|
||||
import { handleOpenAiHttpRequest } from "./openai-http.js";
|
||||
import { handleOpenResponsesHttpRequest } from "./openresponses-http.js";
|
||||
import { handleToolsInvokeHttpRequest } from "./tools-invoke-http.js";
|
||||
@@ -78,6 +80,51 @@ function isCanvasPath(pathname: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function hasAuthorizedWsClientForIp(clients: Set<GatewayWsClient>, clientIp: string): boolean {
|
||||
for (const client of clients) {
|
||||
if (client.clientIp && client.clientIp === clientIp) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function authorizeCanvasRequest(params: {
|
||||
req: IncomingMessage;
|
||||
auth: ResolvedGatewayAuth;
|
||||
trustedProxies: string[];
|
||||
clients: Set<GatewayWsClient>;
|
||||
}): Promise<boolean> {
|
||||
const { req, auth, trustedProxies, clients } = params;
|
||||
if (isLocalDirectRequest(req, trustedProxies)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const token = getBearerToken(req);
|
||||
if (token) {
|
||||
const authResult = await authorizeGatewayConnect({
|
||||
auth: { ...auth, allowTailscale: false },
|
||||
connectAuth: { token, password: token },
|
||||
req,
|
||||
trustedProxies,
|
||||
});
|
||||
if (authResult.ok) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const clientIp = resolveGatewayClientIp({
|
||||
remoteAddr: req.socket?.remoteAddress ?? "",
|
||||
forwardedFor: getHeader(req, "x-forwarded-for"),
|
||||
realIp: getHeader(req, "x-real-ip"),
|
||||
trustedProxies,
|
||||
});
|
||||
if (!clientIp) {
|
||||
return false;
|
||||
}
|
||||
return hasAuthorizedWsClientForIp(clients, clientIp);
|
||||
}
|
||||
|
||||
export type HooksRequestHandler = (req: IncomingMessage, res: ServerResponse) => Promise<boolean>;
|
||||
|
||||
export function createHooksRequestHandler(
|
||||
@@ -226,6 +273,7 @@ export function createHooksRequestHandler(
|
||||
|
||||
export function createGatewayHttpServer(opts: {
|
||||
canvasHost: CanvasHostHandler | null;
|
||||
clients: Set<GatewayWsClient>;
|
||||
controlUiEnabled: boolean;
|
||||
controlUiBasePath: string;
|
||||
controlUiRoot?: ControlUiRootState;
|
||||
@@ -234,11 +282,12 @@ export function createGatewayHttpServer(opts: {
|
||||
openResponsesConfig?: import("../config/types.gateway.js").GatewayHttpResponsesConfig;
|
||||
handleHooksRequest: HooksRequestHandler;
|
||||
handlePluginRequest?: HooksRequestHandler;
|
||||
resolvedAuth: import("./auth.js").ResolvedGatewayAuth;
|
||||
resolvedAuth: ResolvedGatewayAuth;
|
||||
tlsOptions?: TlsOptions;
|
||||
}): HttpServer {
|
||||
const {
|
||||
canvasHost,
|
||||
clients,
|
||||
controlUiEnabled,
|
||||
controlUiBasePath,
|
||||
controlUiRoot,
|
||||
@@ -305,16 +354,15 @@ export function createGatewayHttpServer(opts: {
|
||||
}
|
||||
}
|
||||
if (canvasHost) {
|
||||
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
||||
const url = new URL(req.url ?? "/", "http://localhost");
|
||||
if (isCanvasPath(url.pathname)) {
|
||||
const token = getBearerToken(req);
|
||||
const authResult = await authorizeGatewayConnect({
|
||||
auth: resolvedAuth,
|
||||
connectAuth: token ? { token, password: token } : null,
|
||||
const ok = await authorizeCanvasRequest({
|
||||
req,
|
||||
auth: resolvedAuth,
|
||||
trustedProxies,
|
||||
clients,
|
||||
});
|
||||
if (!authResult.ok) {
|
||||
if (!ok) {
|
||||
sendUnauthorized(res);
|
||||
return;
|
||||
}
|
||||
@@ -363,41 +411,38 @@ export function attachGatewayUpgradeHandler(opts: {
|
||||
httpServer: HttpServer;
|
||||
wss: WebSocketServer;
|
||||
canvasHost: CanvasHostHandler | null;
|
||||
resolvedAuth: import("./auth.js").ResolvedGatewayAuth;
|
||||
clients: Set<GatewayWsClient>;
|
||||
resolvedAuth: ResolvedGatewayAuth;
|
||||
}) {
|
||||
const { httpServer, wss, canvasHost, resolvedAuth } = opts;
|
||||
const { httpServer, wss, canvasHost, clients, resolvedAuth } = opts;
|
||||
httpServer.on("upgrade", (req, socket, head) => {
|
||||
void (async () => {
|
||||
if (canvasHost) {
|
||||
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
||||
const url = new URL(req.url ?? "/", "http://localhost");
|
||||
if (url.pathname === CANVAS_WS_PATH) {
|
||||
const configSnapshot = loadConfig();
|
||||
const token = getBearerToken(req);
|
||||
const authResult = await authorizeGatewayConnect({
|
||||
auth: resolvedAuth,
|
||||
connectAuth: token ? { token, password: token } : null,
|
||||
const trustedProxies = configSnapshot.gateway?.trustedProxies ?? [];
|
||||
const ok = await authorizeCanvasRequest({
|
||||
req,
|
||||
trustedProxies: configSnapshot.gateway?.trustedProxies ?? [],
|
||||
auth: resolvedAuth,
|
||||
trustedProxies,
|
||||
clients,
|
||||
});
|
||||
if (!authResult.ok) {
|
||||
if (!ok) {
|
||||
socket.write("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (canvasHost?.handleUpgrade(req, socket, head)) {
|
||||
return;
|
||||
if (canvasHost.handleUpgrade(req, socket, head)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
wss.handleUpgrade(req, socket, head, (ws) => {
|
||||
wss.emit("connection", ws, req);
|
||||
});
|
||||
})().catch(() => {
|
||||
try {
|
||||
socket.destroy();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
socket.destroy();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user