Skip to content

Commit 893abf6

Browse files
author
chenyangjun-xy
committed
fix(gateway): separate auth check from device identity omission for auth-none
Separate two concerns that were conflated in callGatewayWithScopes: 1. **Shared-auth check** (hasLocalBackendSharedAuth): auth mode none satisfies the local-backend shared-auth requirement → no error thrown 2. **Device identity omission** (omitDeviceIdentity): keep allowAuthNone in shouldOmitDeviceIdentityForGatewayCall so auth-none triggers the existing device-less self-pairing bypass (shouldSkipLocalBackendSelfPairing) on the server side → device identity intentionally omitted, scopes preserved The server-side shouldSkipLocalBackendSelfPairing returns true for auth method none, which skips clearUnboundScopes and allows requested operator scopes (including operator.write) to survive the handshake without requiring paired-device checks. Keeping auth-none device-less preserves this contract.
1 parent f876955 commit 893abf6

2 files changed

Lines changed: 11 additions & 2 deletions

File tree

src/gateway/call.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,7 @@ describe("callGateway url resolution", () => {
923923
expect(lastClientOptions?.deviceIdentity).toBeNull();
924924
});
925925

926-
it("uses local backend auth-none without a device identity when required", async () => {
926+
it("satisfies local backend shared auth with auth-none without a device identity", async () => {
927927
getRuntimeConfig.mockReturnValue({
928928
gateway: { mode: "local", bind: "loopback", auth: { mode: "none" } },
929929
});
@@ -940,6 +940,9 @@ describe("callGateway url resolution", () => {
940940
expect(lastClientOptions?.scopes).toEqual(["operator.read", "operator.pairing"]);
941941
expect(lastClientOptions?.token).toBeUndefined();
942942
expect(lastClientOptions?.password).toBeUndefined();
943+
// auth mode "none" keeps the device-less self-pairing bypass; device identity
944+
// is intentionally omitted so the server-side shouldSkipLocalBackendSelfPairing
945+
// can preserve requested scopes without requiring paired-device checks.
943946
expect(lastClientOptions?.deviceIdentity).toBeNull();
944947
});
945948

src/gateway/call.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1153,17 +1153,23 @@ async function callGatewayWithScopes<T = Record<string, unknown>>(
11531153
const tlsFingerprint = await resolveGatewayTlsFingerprint({ opts, context, url });
11541154
const token = useStoredDeviceAuth ? undefined : resolvedCredentials.token;
11551155
const password = useStoredDeviceAuth ? undefined : resolvedCredentials.password;
1156+
// auth mode "none" satisfies the local-backend shared-auth requirement without
1157+
// token / password credentials. It also triggers device-identity omission so
1158+
// the server-side device-less self-pairing bypass (shouldSkipLocalBackendSelfPairing)
1159+
// preserves requested scopes — pairing the two concerns keeps the auth-none
1160+
// path safe.
11561161
const allowAuthNone =
11571162
opts.requireLocalBackendSharedAuth === true &&
11581163
resolveGatewayCallAuth(context.config).mode === "none";
1164+
const hasLocalBackendSharedAuth = Boolean(token || password) || allowAuthNone;
11591165
const omitDeviceIdentity = shouldOmitDeviceIdentityForGatewayCall({
11601166
opts,
11611167
url,
11621168
token,
11631169
password,
11641170
allowAuthNone,
11651171
});
1166-
if (opts.requireLocalBackendSharedAuth && !omitDeviceIdentity) {
1172+
if (opts.requireLocalBackendSharedAuth && !hasLocalBackendSharedAuth) {
11671173
throw new GatewayLocalBackendSharedAuthUnavailableError(
11681174
"local backend shared auth requires a loopback gateway with token/password credentials or auth mode none",
11691175
);

0 commit comments

Comments
 (0)