Gateway: preserve operator scopes without device identity

This commit is contained in:
Tyler Yust 2026-03-12 18:42:11 -07:00
parent 996de610e8
commit 896f111a95
3 changed files with 10 additions and 10 deletions

View File

@ -63,7 +63,7 @@ describe("gateway auth compatibility baseline", () => {
}
});
test("clears client-declared scopes for shared-token operator connects", async () => {
test("keeps requested scopes for shared-token operator connects without device identity", async () => {
const ws = await openWs(port);
try {
const res = await connectReq(ws, {
@ -74,8 +74,8 @@ describe("gateway auth compatibility baseline", () => {
expect(res.ok).toBe(true);
const adminRes = await rpcReq(ws, "set-heartbeats", { enabled: false });
expect(adminRes.ok).toBe(false);
expect(adminRes.error?.message).toBe("missing scope: operator.admin");
expect(adminRes.ok).toBe(true);
expect((adminRes.payload as { enabled?: boolean } | undefined)?.enabled).toBe(false);
} finally {
ws.close();
}
@ -183,7 +183,7 @@ describe("gateway auth compatibility baseline", () => {
}
});
test("clears client-declared scopes for shared-password operator connects", async () => {
test("keeps requested scopes for shared-password operator connects without device identity", async () => {
const ws = await openWs(port);
try {
const res = await connectReq(ws, {
@ -194,8 +194,8 @@ describe("gateway auth compatibility baseline", () => {
expect(res.ok).toBe(true);
const adminRes = await rpcReq(ws, "set-heartbeats", { enabled: false });
expect(adminRes.ok).toBe(false);
expect(adminRes.error?.message).toBe("missing scope: operator.admin");
expect(adminRes.ok).toBe(true);
expect((adminRes.payload as { enabled?: boolean } | undefined)?.enabled).toBe(false);
} finally {
ws.close();
}

View File

@ -526,7 +526,7 @@ export function attachGatewayWsMessageHandler(params: {
hasSharedAuth,
isLocalClient,
});
if (!device && (!isControlUi || decision.kind !== "allow")) {
if (!device && decision.kind !== "allow" && !isControlUi) {
clearUnboundScopes();
}
if (decision.kind === "allow") {

View File

@ -580,11 +580,11 @@ vi.mock("../channels/web/index.js", async () => {
};
});
vi.mock("../commands/agent.js", () => ({
agentCommand,
agentCommandFromIngress: agentCommand,
agentCommand: hoisted.agentCommand,
agentCommandFromIngress: hoisted.agentCommand,
}));
vi.mock("../auto-reply/reply.js", () => ({
getReplyFromConfig,
getReplyFromConfig: hoisted.getReplyFromConfig,
}));
vi.mock("../cli/deps.js", async () => {
const actual = await vi.importActual<typeof import("../cli/deps.js")>("../cli/deps.js");