fix(gateway): allow health method for all authenticated roles (#19699)
Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: b9764432672d15d63061df2d2e58542e5c777479 Co-authored-by: Nachx639 <71144023+Nachx639@users.noreply.github.com> Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com> Reviewed-by: @mbelinky
This commit is contained in:
@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Gateway/Auth: allow authenticated clients across roles/scopes to call `health` while preserving role and scope enforcement for non-health methods. (#19699) thanks @Nachx639.
|
||||
- Gateway/Hooks: include transform export name in hook-transform cache keys so distinct exports from the same module do not reuse the wrong cached transform function. (#13855) thanks @mcaxtr.
|
||||
- Gateway/Control UI: return 404 for missing static-asset paths instead of serving SPA fallback HTML, while preserving client-route fallback behavior for extensionless and non-asset dotted paths. (#12060) thanks @mcaxtr.
|
||||
- Gateway/Pairing: prevent device-token rotate scope escalation by enforcing an approved-scope baseline, preserving approved scopes across metadata updates, and rejecting rotate requests that exceed approved role scope implications. (#20703) thanks @coygeek.
|
||||
|
||||
@@ -39,6 +39,9 @@ function authorizeGatewayMethod(method: string, client: GatewayRequestOptions["c
|
||||
if (!client?.connect) {
|
||||
return null;
|
||||
}
|
||||
if (method === "health") {
|
||||
return null;
|
||||
}
|
||||
const role = client.connect.role ?? "operator";
|
||||
const scopes = client.connect.scopes ?? [];
|
||||
if (isNodeRoleMethod(method)) {
|
||||
|
||||
@@ -111,9 +111,9 @@ async function expectMissingScopeAfterConnect(
|
||||
try {
|
||||
const res = await connectReq(ws, opts);
|
||||
expect(res.ok).toBe(true);
|
||||
const health = await rpcReq(ws, "health");
|
||||
expect(health.ok).toBe(false);
|
||||
expect(health.error?.message).toContain("missing scope");
|
||||
const status = await rpcReq(ws, "status");
|
||||
expect(status.ok).toBe(false);
|
||||
expect(status.error?.message).toContain("missing scope");
|
||||
} finally {
|
||||
ws.close();
|
||||
}
|
||||
@@ -363,6 +363,18 @@ describe("gateway server auth/connect", () => {
|
||||
await expectMissingScopeAfterConnect(port, { device: null });
|
||||
});
|
||||
|
||||
test("allows health when scopes are empty", async () => {
|
||||
const ws = await openWs(port);
|
||||
try {
|
||||
const res = await connectReq(ws, { scopes: [] });
|
||||
expect(res.ok).toBe(true);
|
||||
const health = await rpcReq(ws, "health");
|
||||
expect(health.ok).toBe(true);
|
||||
} finally {
|
||||
ws.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("does not grant admin when scopes are omitted", async () => {
|
||||
const ws = await openWs(port);
|
||||
const token = resolveGatewayTokenOrEnv();
|
||||
@@ -400,9 +412,11 @@ describe("gateway server auth/connect", () => {
|
||||
expect(presenceScopes).toEqual([]);
|
||||
expect(presenceScopes).not.toContain("operator.admin");
|
||||
|
||||
const status = await rpcReq(ws, "status");
|
||||
expect(status.ok).toBe(false);
|
||||
expect(status.error?.message).toContain("missing scope");
|
||||
const health = await rpcReq(ws, "health");
|
||||
expect(health.ok).toBe(false);
|
||||
expect(health.error?.message).toContain("missing scope");
|
||||
expect(health.ok).toBe(true);
|
||||
|
||||
ws.close();
|
||||
});
|
||||
@@ -680,9 +694,11 @@ describe("gateway server auth/connect", () => {
|
||||
const ws = await openTailscaleWs(port);
|
||||
const res = await connectReq(ws, { token: "secret", device: null });
|
||||
expect(res.ok).toBe(true);
|
||||
const status = await rpcReq(ws, "status");
|
||||
expect(status.ok).toBe(false);
|
||||
expect(status.error?.message).toContain("missing scope");
|
||||
const health = await rpcReq(ws, "health");
|
||||
expect(health.ok).toBe(false);
|
||||
expect(health.error?.message).toContain("missing scope");
|
||||
expect(health.ok).toBe(true);
|
||||
ws.close();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -96,6 +96,9 @@ describe("gateway role enforcement", () => {
|
||||
const statusRes = await rpcReq(nodeWs, "status", {});
|
||||
expect(statusRes.ok).toBe(false);
|
||||
expect(statusRes.error?.message ?? "").toContain("unauthorized role");
|
||||
|
||||
const healthRes = await rpcReq(nodeWs, "health", {});
|
||||
expect(healthRes.ok).toBe(true);
|
||||
} finally {
|
||||
nodeWs.close();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user