mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 15:47:28 +00:00
test: sign gateway auth payloads
This commit is contained in:
@@ -146,6 +146,23 @@ function requireFirstSignCall(): [privateKey: string, payload: string] {
|
||||
return [privateKey, payload];
|
||||
}
|
||||
|
||||
function expectSignedPayloadFields(
|
||||
payload: string | undefined,
|
||||
params: { scopes: string[]; token: string; nonce: string },
|
||||
) {
|
||||
expect(payload?.split("|")).toEqual([
|
||||
"v2",
|
||||
"device-1",
|
||||
"openclaw-control-ui",
|
||||
"webchat",
|
||||
"operator",
|
||||
params.scopes.join(","),
|
||||
expect.stringMatching(/^\d+$/),
|
||||
params.token,
|
||||
params.nonce,
|
||||
]);
|
||||
}
|
||||
|
||||
function expectLatestRequestTiming(
|
||||
onRequestTiming: ReturnType<typeof vi.fn>,
|
||||
expected: Partial<RequestTimingPayload>,
|
||||
@@ -314,8 +331,6 @@ describe("GatewayBrowserClient", () => {
|
||||
|
||||
expect(connectFrame.method).toBe("connect");
|
||||
expect(connectFrame.params?.scopes).toEqual([...CONTROL_UI_OPERATOR_SCOPES]);
|
||||
expect(connectFrame.params?.scopes).toContain("operator.admin");
|
||||
expect(connectFrame.params?.scopes).toContain("operator.pairing");
|
||||
});
|
||||
|
||||
it("reuses cached device token scopes when connecting from bootstrap handoff", async () => {
|
||||
@@ -334,9 +349,12 @@ describe("GatewayBrowserClient", () => {
|
||||
|
||||
expect(connectFrame.method).toBe("connect");
|
||||
expect(connectFrame.params?.auth?.token).toBe("bootstrap-device-token");
|
||||
expect(connectFrame.params?.scopes).toEqual([
|
||||
"operator.approvals",
|
||||
"operator.read",
|
||||
"operator.write",
|
||||
]);
|
||||
expect(connectFrame.params?.scopes).toEqual(storedEntry.scopes);
|
||||
expect(connectFrame.params?.scopes).not.toContain("operator.admin");
|
||||
expect(connectFrame.params?.scopes).not.toContain("operator.pairing");
|
||||
});
|
||||
|
||||
it("reports browser security errors from WebSocket construction without retrying", async () => {
|
||||
@@ -515,8 +533,11 @@ describe("GatewayBrowserClient", () => {
|
||||
expect(connectFrame.params?.auth?.token).toBe("shared-auth-token");
|
||||
const [privateKey, signedPayload] = requireFirstSignCall();
|
||||
expect(privateKey).toBe("private-key");
|
||||
expect(signedPayload).toContain("|shared-auth-token|nonce-1");
|
||||
expect(signedPayload).not.toContain("stored-device-token");
|
||||
expectSignedPayloadFields(signedPayload, {
|
||||
scopes: [...CONTROL_UI_OPERATOR_SCOPES],
|
||||
token: "shared-auth-token",
|
||||
nonce: "nonce-1",
|
||||
});
|
||||
});
|
||||
|
||||
it("sends explicit shared token on insecure first connect without cached device fallback", async () => {
|
||||
@@ -571,7 +592,17 @@ describe("GatewayBrowserClient", () => {
|
||||
expect(connectFrame.params?.auth?.token).toBe("stored-device-token");
|
||||
const [privateKey, signedPayload] = requireFirstSignCall();
|
||||
expect(privateKey).toBe("private-key");
|
||||
expect(signedPayload).toContain("|stored-device-token|nonce-1");
|
||||
expectSignedPayloadFields(signedPayload, {
|
||||
scopes: [
|
||||
"operator.admin",
|
||||
"operator.approvals",
|
||||
"operator.pairing",
|
||||
"operator.read",
|
||||
"operator.write",
|
||||
],
|
||||
token: "stored-device-token",
|
||||
nonce: "nonce-1",
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores cached operator device tokens that do not include read access", async () => {
|
||||
@@ -592,7 +623,11 @@ describe("GatewayBrowserClient", () => {
|
||||
expect(connectFrame.method).toBe("connect");
|
||||
expect(connectFrame.params?.auth?.token).toBeUndefined();
|
||||
const [, signedPayload] = requireFirstSignCall();
|
||||
expect(signedPayload).not.toContain("under-scoped-device-token");
|
||||
expectSignedPayloadFields(signedPayload, {
|
||||
scopes: [...CONTROL_UI_OPERATOR_SCOPES],
|
||||
token: "",
|
||||
nonce: "nonce-1",
|
||||
});
|
||||
});
|
||||
|
||||
it("retries once with device token after token mismatch when shared token is explicit", async () => {
|
||||
@@ -793,7 +828,11 @@ describe("GatewayBrowserClient", () => {
|
||||
const { connectFrame } = await continueConnect(secondWs, "nonce-current");
|
||||
expect(connectFrame.method).toBe("connect");
|
||||
const signedPayload = signDevicePayloadMock.mock.calls.at(-1)?.[1];
|
||||
expect(signedPayload).toContain("|shared-auth-token|nonce-current");
|
||||
expectSignedPayloadFields(signedPayload, {
|
||||
scopes: [...CONTROL_UI_OPERATOR_SCOPES],
|
||||
token: "shared-auth-token",
|
||||
nonce: "nonce-current",
|
||||
});
|
||||
|
||||
client.stop();
|
||||
vi.useRealTimers();
|
||||
|
||||
Reference in New Issue
Block a user