

















@@ -6,7 +6,9 @@ import {
66connectReq,
77CONTROL_UI_CLIENT,
88ConnectErrorDetailCodes,
9+createSignedDevice,
910getFreePort,
11+readConnectChallengeNonce,
1012openWs,
1113originForPort,
1214rpcReq,
@@ -312,7 +314,7 @@ describe("gateway auth compatibility baseline", () => {
312314testState.gatewayAuth = { mode: "none" };
313315delete process.env.OPENCLAW_GATEWAY_TOKEN;
314316port = await getFreePort();
315-server = await startGatewayServer(port);
317+server = await startGatewayServer(port, { controlUiEnabled: true });
316318});
317319318320afterAll(async () => {
@@ -329,5 +331,89 @@ describe("gateway auth compatibility baseline", () => {
329331ws.close();
330332}
331333});
334+335+test("keeps auth-none control ui first-connect token absence unchanged", async () => {
336+const ws = await openWs(port, { origin: originForPort(port) });
337+try {
338+const deviceIdentityPath = path.join(
339+os.tmpdir(),
340+`openclaw-auth-none-control-ui-first-${process.pid}-${port}.json`,
341+);
342+const res = await connectReq(ws, {
343+skipDefaultAuth: true,
344+client: { ...CONTROL_UI_CLIENT },
345+scopes: ["operator.read"],
346+ deviceIdentityPath,
347+});
348+expect(res.ok).toBe(true);
349+const helloOk = res.payload as
350+| {
351+auth?: {
352+deviceToken?: unknown;
353+};
354+}
355+| undefined;
356+expect(helloOk?.auth?.deviceToken).toBeUndefined();
357+} finally {
358+ws.close();
359+}
360+});
361+362+test("keeps auth-none control ui stale-key token handoff unchanged", async () => {
363+const ws = await openWs(port, { origin: originForPort(port) });
364+try {
365+const { loadOrCreateDeviceIdentity, publicKeyRawBase64UrlFromPem } =
366+await import("../infra/device-identity.js");
367+const { approveDevicePairing, requestDevicePairing } =
368+await import("../infra/device-pairing.js");
369+const nonce = await readConnectChallengeNonce(ws);
370+const identityPath = path.join(
371+os.tmpdir(),
372+`openclaw-auth-none-control-ui-${process.pid}-${port}.json`,
373+);
374+const staleIdentityPath = path.join(
375+os.tmpdir(),
376+`openclaw-auth-none-control-ui-stale-${process.pid}-${port}.json`,
377+);
378+const { identity, device } = await createSignedDevice({
379+token: null,
380+scopes: ["operator.read"],
381+clientId: CONTROL_UI_CLIENT.id,
382+clientMode: CONTROL_UI_CLIENT.mode,
383+ identityPath,
384+ nonce,
385+});
386+const staleIdentity = loadOrCreateDeviceIdentity(staleIdentityPath);
387+const pending = await requestDevicePairing({
388+deviceId: identity.deviceId,
389+publicKey: publicKeyRawBase64UrlFromPem(staleIdentity.publicKeyPem),
390+clientId: CONTROL_UI_CLIENT.id,
391+clientMode: CONTROL_UI_CLIENT.mode,
392+role: "operator",
393+scopes: ["operator.read"],
394+});
395+await approveDevicePairing(pending.request.requestId, {
396+callerScopes: ["operator.admin"],
397+});
398+399+const res = await connectReq(ws, {
400+skipDefaultAuth: true,
401+client: { ...CONTROL_UI_CLIENT },
402+scopes: ["operator.read"],
403+ device,
404+});
405+expect(res.ok).toBe(true);
406+const helloOk = res.payload as
407+| {
408+auth?: {
409+deviceToken?: unknown;
410+};
411+}
412+| undefined;
413+expect(typeof helloOk?.auth?.deviceToken).toBe("string");
414+} finally {
415+ws.close();
416+}
417+});
332418});
333419});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。