mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-16 15:43:57 -06:00
83900e4683
* feat(browser): add relay authentication v2 * fix(browser): cap relay test WebSocket payload * fix(browser): keep relay E2E inside extension boundary * fix(browser): isolate relay admission and cleanup auth * fix(browser): finish relay auth migration hardening * fix(browser): keep preauth transport bounded through teardown
92 lines
2.7 KiB
JavaScript
92 lines
2.7 KiB
JavaScript
import {
|
|
createExtensionRelayAuthClient,
|
|
EXTENSION_RELAY_V2_PROTOCOL,
|
|
parseRelayAuthJson,
|
|
} from "./relay-auth-v2.js";
|
|
import { buildRelayWsProtocols } from "./relay-core.js";
|
|
|
|
/** Open one v2-only relay socket and expose application frames only after auth.ok. */
|
|
export function openAuthenticatedRelaySocket({
|
|
relayUrl,
|
|
token,
|
|
isCurrent,
|
|
onAuthenticated,
|
|
onApplicationMessage,
|
|
onAuthenticationFailure,
|
|
onClose,
|
|
}) {
|
|
const authClientPromise = createExtensionRelayAuthClient({ token, relayUrl });
|
|
const ws = new WebSocket(relayUrl, buildRelayWsProtocols());
|
|
let authenticated = false;
|
|
|
|
ws.addEventListener("open", () => {
|
|
void (async () => {
|
|
try {
|
|
if (!isCurrent(ws)) {
|
|
ws.close();
|
|
return;
|
|
}
|
|
if (ws.protocol !== EXTENSION_RELAY_V2_PROTOCOL) {
|
|
throw new Error("relay did not negotiate Browser Relay Authentication v2");
|
|
}
|
|
const authClient = await authClientPromise;
|
|
ws.send(JSON.stringify(authClient.start()));
|
|
} catch (error) {
|
|
onAuthenticationFailure(ws, error);
|
|
}
|
|
})();
|
|
});
|
|
|
|
let messageChain = Promise.resolve();
|
|
ws.addEventListener("message", (event) => {
|
|
messageChain = messageChain.then(async () => {
|
|
try {
|
|
if (!isCurrent(ws)) {
|
|
return;
|
|
}
|
|
const raw = String(event.data);
|
|
if (authenticated) {
|
|
let message;
|
|
try {
|
|
message = JSON.parse(raw);
|
|
} catch {
|
|
return;
|
|
}
|
|
if (typeof message?.type === "string" && message.type.startsWith("auth.")) {
|
|
throw new Error("relay sent an authentication frame after completion");
|
|
}
|
|
onApplicationMessage(ws, message);
|
|
return;
|
|
}
|
|
|
|
const message = parseRelayAuthJson(raw);
|
|
if (!message) {
|
|
throw new Error("relay sent malformed authentication JSON");
|
|
}
|
|
const authClient = await authClientPromise;
|
|
if (message.type === "auth.challenge") {
|
|
const response = await authClient.acceptChallenge(message);
|
|
if (isCurrent(ws) && ws.readyState === WebSocket.OPEN) {
|
|
ws.send(JSON.stringify(response));
|
|
}
|
|
return;
|
|
}
|
|
if (message.type !== "auth.ok") {
|
|
throw new Error("relay authentication frame is out of sequence");
|
|
}
|
|
await authClient.acceptOk(message);
|
|
if (!isCurrent(ws) || ws.readyState !== WebSocket.OPEN) {
|
|
return;
|
|
}
|
|
authenticated = true;
|
|
await onAuthenticated(ws);
|
|
} catch (error) {
|
|
onAuthenticationFailure(ws, error);
|
|
}
|
|
});
|
|
});
|
|
|
|
ws.addEventListener("close", () => onClose(ws, authenticated));
|
|
return ws;
|
|
}
|