fix(gateway): restore localhost Control UI pairing when allowInsecureAuth is set (#22996)

* fix(gateway): allow localhost Control UI without device identity when allowInsecureAuth is set

* fix(gateway): pass isLocalClient to evaluateMissingDeviceIdentity

* test: add regression tests for localhost Control UI pairing

* fix(gateway): require pairing for legacy metadata upgrades

* test(gateway): fix legacy metadata e2e ws typing

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Marcus Widing
2026-02-22 00:04:52 +01:00
committed by GitHub
parent bfe016fa29
commit fa4e4efd92
4 changed files with 197 additions and 39 deletions

View File

@@ -469,6 +469,7 @@ export function attachGatewayWsMessageHandler(params: {
sharedAuthOk,
authOk,
hasSharedAuth,
isLocalClient,
});
if (decision.kind === "allow") {
return true;
@@ -706,50 +707,50 @@ export function attachGatewayWsMessageHandler(params: {
return;
}
} else {
const hasLegacyPairedMetadata =
paired.roles === undefined && paired.scopes === undefined;
const pairedRoles = Array.isArray(paired.roles)
? paired.roles
: paired.role
? [paired.role]
: [];
if (!hasLegacyPairedMetadata) {
const allowedRoles = new Set(pairedRoles);
if (allowedRoles.size === 0) {
logUpgradeAudit("role-upgrade", pairedRoles, paired.scopes);
const ok = await requirePairing("role-upgrade");
if (!ok) {
return;
}
} else if (!allowedRoles.has(role)) {
logUpgradeAudit("role-upgrade", pairedRoles, paired.scopes);
const ok = await requirePairing("role-upgrade");
if (!ok) {
return;
}
const pairedScopes = Array.isArray(paired.scopes)
? paired.scopes
: Array.isArray(paired.approvedScopes)
? paired.approvedScopes
: [];
const allowedRoles = new Set(pairedRoles);
if (allowedRoles.size === 0) {
logUpgradeAudit("role-upgrade", pairedRoles, pairedScopes);
const ok = await requirePairing("role-upgrade");
if (!ok) {
return;
}
} else if (!allowedRoles.has(role)) {
logUpgradeAudit("role-upgrade", pairedRoles, pairedScopes);
const ok = await requirePairing("role-upgrade");
if (!ok) {
return;
}
}
const pairedScopes = Array.isArray(paired.scopes) ? paired.scopes : [];
if (scopes.length > 0) {
if (pairedScopes.length === 0) {
if (scopes.length > 0) {
if (pairedScopes.length === 0) {
logUpgradeAudit("scope-upgrade", pairedRoles, pairedScopes);
const ok = await requirePairing("scope-upgrade");
if (!ok) {
return;
}
} else {
const scopesAllowed = roleScopesAllow({
role,
requestedScopes: scopes,
allowedScopes: pairedScopes,
});
if (!scopesAllowed) {
logUpgradeAudit("scope-upgrade", pairedRoles, pairedScopes);
const ok = await requirePairing("scope-upgrade");
if (!ok) {
return;
}
} else {
const scopesAllowed = roleScopesAllow({
role,
requestedScopes: scopes,
allowedScopes: pairedScopes,
});
if (!scopesAllowed) {
logUpgradeAudit("scope-upgrade", pairedRoles, pairedScopes);
const ok = await requirePairing("scope-upgrade");
if (!ok) {
return;
}
}
}
}
}