Browser relay: accept raw gateway token in extension auth

(cherry picked from commit e682a768d0)
This commit is contained in:
Mustafa Kemal
2026-02-23 20:43:39 +08:00
committed by Peter Steinberger
parent d00d814ad1
commit bb8f538cd4
4 changed files with 45 additions and 10 deletions

View File

@@ -3,6 +3,7 @@ import type { AddressInfo } from "node:net";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
probeAuthenticatedOpenClawRelay,
resolveRelayAcceptedTokensForPort,
resolveRelayAuthTokenForPort,
} from "./extension-relay-auth.js";
import { getFreePort } from "./test-port.js";
@@ -51,6 +52,13 @@ describe("extension-relay-auth", () => {
expect(tokenA1).not.toBe(TEST_GATEWAY_TOKEN);
});
it("accepts both relay-scoped and raw gateway tokens for compatibility", () => {
const tokens = resolveRelayAcceptedTokensForPort(18790);
expect(tokens).toContain(TEST_GATEWAY_TOKEN);
expect(tokens[0]).not.toBe(TEST_GATEWAY_TOKEN);
expect(tokens[0]).toBe(resolveRelayAuthTokenForPort(18790));
});
it("accepts authenticated openclaw relay probe responses", async () => {
let seenToken: string | undefined;
await withRelayServer(

View File

@@ -27,14 +27,22 @@ function deriveRelayAuthToken(gatewayToken: string, port: number): string {
return createHmac("sha256", gatewayToken).update(`${RELAY_TOKEN_CONTEXT}:${port}`).digest("hex");
}
export function resolveRelayAuthTokenForPort(port: number): string {
export function resolveRelayAcceptedTokensForPort(port: number): string[] {
const gatewayToken = resolveGatewayAuthToken();
if (gatewayToken) {
return deriveRelayAuthToken(gatewayToken, port);
if (!gatewayToken) {
throw new Error(
"extension relay requires gateway auth token (set gateway.auth.token or OPENCLAW_GATEWAY_TOKEN)",
);
}
throw new Error(
"extension relay requires gateway auth token (set gateway.auth.token or OPENCLAW_GATEWAY_TOKEN)",
);
const relayToken = deriveRelayAuthToken(gatewayToken, port);
if (relayToken === gatewayToken) {
return [relayToken];
}
return [relayToken, gatewayToken];
}
export function resolveRelayAuthTokenForPort(port: number): string {
return resolveRelayAcceptedTokensForPort(port)[0];
}
export async function probeAuthenticatedOpenClawRelay(params: {

View File

@@ -277,6 +277,23 @@ describe("chrome extension relay server", () => {
ext.close();
});
it("accepts raw gateway token for relay auth compatibility", async () => {
const port = await getFreePort();
cdpUrl = `http://127.0.0.1:${port}`;
await ensureChromeExtensionRelayServer({ cdpUrl });
const versionRes = await fetch(`${cdpUrl}/json/version`, {
headers: { "x-openclaw-relay-token": TEST_GATEWAY_TOKEN },
});
expect(versionRes.status).toBe(200);
const ext = new WebSocket(
`ws://127.0.0.1:${port}/extension?token=${encodeURIComponent(TEST_GATEWAY_TOKEN)}`,
);
await waitForOpen(ext);
ext.close();
});
it(
"tracks attached page targets and exposes them via CDP + /json/list",
async () => {

View File

@@ -7,6 +7,7 @@ import { isLoopbackAddress, isLoopbackHost } from "../gateway/net.js";
import { rawDataToString } from "../infra/ws.js";
import {
probeAuthenticatedOpenClawRelay,
resolveRelayAcceptedTokensForPort,
resolveRelayAuthTokenForPort,
} from "./extension-relay-auth.js";
@@ -219,6 +220,7 @@ export async function ensureChromeExtensionRelayServer(opts: {
}
const relayAuthToken = resolveRelayAuthTokenForPort(info.port);
const relayAuthTokens = new Set(resolveRelayAcceptedTokensForPort(info.port));
let extensionWs: WebSocket | null = null;
const cdpClients = new Set<WebSocket>();
@@ -365,8 +367,8 @@ export async function ensureChromeExtensionRelayServer(opts: {
const path = url.pathname;
if (path.startsWith("/json")) {
const token = getHeader(req, RELAY_AUTH_HEADER);
if (!token || token !== relayAuthToken) {
const token = getHeader(req, RELAY_AUTH_HEADER)?.trim();
if (!token || !relayAuthTokens.has(token)) {
res.writeHead(401);
res.end("Unauthorized");
return;
@@ -489,7 +491,7 @@ export async function ensureChromeExtensionRelayServer(opts: {
if (pathname === "/extension") {
const token = getRelayAuthTokenFromRequest(req, url);
if (!token || token !== relayAuthToken) {
if (!token || !relayAuthTokens.has(token)) {
rejectUpgrade(socket, 401, "Unauthorized");
return;
}
@@ -514,7 +516,7 @@ export async function ensureChromeExtensionRelayServer(opts: {
if (pathname === "/cdp") {
const token = getRelayAuthTokenFromRequest(req, url);
if (!token || token !== relayAuthToken) {
if (!token || !relayAuthTokens.has(token)) {
rejectUpgrade(socket, 401, "Unauthorized");
return;
}