fix(gateway): retain operator scopes for non-local token-auth clients

This commit is contained in:
xydt-610 2026-03-21 11:56:31 +08:00
parent 598f1826d8
commit 565ab68951
3 changed files with 29 additions and 2 deletions

View File

@ -141,6 +141,23 @@ describe("ws connect policy", () => {
}).kind,
).toBe("reject-unauthorized");
// #51396: non-local token-auth backend clients retain scopes when authOk
// (primary auth) succeeded, even if sharedAuthOk (probe) disagrees.
expect(
evaluateMissingDeviceIdentity({
hasDeviceIdentity: false,
role: "operator",
isControlUi: false,
controlUiAuthPolicy: policy,
trustedProxyAuthOk: false,
sharedAuthOk: false,
authOk: true,
authMethod: "token",
hasSharedAuth: true,
isLocalClient: false,
}).kind,
).toBe("allow");
expect(
evaluateMissingDeviceIdentity({
hasDeviceIdentity: false,

View File

@ -89,6 +89,7 @@ export function evaluateMissingDeviceIdentity(params: {
trustedProxyAuthOk?: boolean;
sharedAuthOk: boolean;
authOk: boolean;
authMethod?: string;
hasSharedAuth: boolean;
isLocalClient: boolean;
}): MissingDeviceIdentityDecision {
@ -116,7 +117,15 @@ export function evaluateMissingDeviceIdentity(params: {
return { kind: "reject-control-ui-insecure-auth" };
}
}
if (roleCanSkipDeviceIdentity(params.role, params.sharedAuthOk)) {
// Operator with shared auth (token/password) skips device identity.
// Also allow when token/password auth succeeded directly (#51396: non-local
// token-auth clients were incorrectly getting scopes stripped).
const sharedAuthSufficient =
params.sharedAuthOk ||
(params.role === "operator" &&
params.authOk &&
(params.authMethod === "token" || params.authMethod === "password"));
if (roleCanSkipDeviceIdentity(params.role, sharedAuthSufficient)) {
return { kind: "allow" };
}
if (!params.authOk && params.hasSharedAuth) {

View File

@ -528,12 +528,13 @@ export function attachGatewayWsMessageHandler(params: {
trustedProxyAuthOk,
sharedAuthOk,
authOk,
authMethod,
hasSharedAuth,
isLocalClient,
});
// Shared token/password auth can bypass pairing for trusted operators.
// Device-less clients only keep self-declared scopes on the explicit
// allow path, including trusted token-authenticated backend operators.
// allow path, including trusted token-authenticated backend operators (#51396).
if (!device && decision.kind !== "allow") {
clearUnboundScopes();
}