fix(desktop): recognize the LAN-bound local server when tagging client tokens

The client-create gate added in 1.13.9 rejects client tokens without the
desktop-local kind, but the kind was only attached when the runtime
origin exactly matched the injected local origin — an empty (same-origin)
api base, loopback aliases, and the embedded server addressed via a LAN
interface (0.0.0.0 binds) all minted untagged tokens, which then hit 403
and surfaced as "Local — Auth required" plus the unreachable-server
screen. The renderer now treats same-origin and loopback targets as
local, the Electron main additionally matches any of the machine's own
interface addresses on the local server's port, and a deduped kind-tagged
mint migrates away legacy same-label tokens that predate client kinds.
The client-create gate itself is unchanged.
This commit is contained in:
Bohdan Triapitsyn
2026-07-05 09:50:53 +03:00
parent b09e073e86
commit 0820778763
3 changed files with 58 additions and 3 deletions
@@ -51,11 +51,30 @@ const shouldIssueDesktopClientToken = (): boolean => {
return isDesktopShell();
};
const isLoopbackHostname = (hostname: string): boolean => {
const clean = hostname.replace(/^\[|\]$/g, '');
return clean === 'localhost' || clean === '127.0.0.1' || clean === '::1';
};
const isLocalDesktopRuntime = (): boolean => {
if (!isDesktopShell()) return false;
const apiBaseUrl = getRuntimeApiBaseUrl();
const localOrigin = readLocalOrigin();
return Boolean(localOrigin && sameOrigin(localOrigin, apiBaseUrl));
if (!localOrigin) return false;
// An empty api base means same-origin requests against the page itself —
// which on desktop IS the embedded local server. Requiring an exact origin
// match here used to leave local client tokens untagged (no desktop-local
// clientKind), and the server's client-create gate then 403'd them.
const apiBaseUrl = getRuntimeApiBaseUrl();
const effectiveTarget = apiBaseUrl || (typeof window !== 'undefined' ? window.location.origin : '');
if (sameOrigin(localOrigin, effectiveTarget)) return true;
// Loopback aliases (localhost vs 127.0.0.1) still address this machine's
// own server.
try {
const normalized = normalizeHostUrl(effectiveTarget);
return Boolean(normalized && isLoopbackHostname(new URL(normalized).hostname));
} catch {
return false;
}
};
const desktopClientAuthMetadata = (): { clientKind?: string; dedupeKey?: string } => {