diff --git a/src/server/__tests__/h5-access-auth.test.ts b/src/server/__tests__/h5-access-auth.test.ts index 402fc025..c3ebb4e2 100644 --- a/src/server/__tests__/h5-access-auth.test.ts +++ b/src/server/__tests__/h5-access-auth.test.ts @@ -176,12 +176,31 @@ describe('remote H5 auth and CORS integration', () => { }) }) + test('allows the Tauri desktop WebView origin to control the local sidecar without H5 token', async () => { + const response = await fetch(`${baseUrl}/api/status`, { + headers: { + Origin: 'http://tauri.localhost', + }, + }) + + expect(response.status).toBe(200) + expect(response.headers.get('Access-Control-Allow-Origin')).toBe('http://tauri.localhost') + await expect(response.json()).resolves.toMatchObject({ + status: 'ok', + }) + }) + test('only lets localhost WebUI origin bypass H5 auth for loopback or private server hosts', () => { expect(canBypassRemoteAuthForLocalBrowser('http://127.0.0.1:5179', '127.0.0.1')).toBe(true) expect(canBypassRemoteAuthForLocalBrowser('http://localhost:5179', '192.168.0.102')).toBe(true) + expect(canBypassRemoteAuthForLocalBrowser('http://tauri.localhost', '127.0.0.1')).toBe(true) + expect(canBypassRemoteAuthForLocalBrowser('tauri://localhost', '127.0.0.1')).toBe(true) + expect(canBypassRemoteAuthForLocalBrowser('asset://localhost', '127.0.0.1')).toBe(true) expect(canBypassRemoteAuthForLocalBrowser('http://localhost:5179', '10.0.0.5')).toBe(true) expect(canBypassRemoteAuthForLocalBrowser('http://localhost:5179', '172.20.1.8')).toBe(true) expect(canBypassRemoteAuthForLocalBrowser('http://localhost:5179', 'public.example.com')).toBe(false) + expect(canBypassRemoteAuthForLocalBrowser('http://tauri.localhost', 'public.example.com')).toBe(false) + expect(canBypassRemoteAuthForLocalBrowser('http://tauri.localhost', '192.168.0.102')).toBe(false) expect(canBypassRemoteAuthForLocalBrowser('http://192.168.0.50:5179', '192.168.0.102')).toBe(false) }) diff --git a/src/server/index.ts b/src/server/index.ts index 6143a82e..b0d3ffa7 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -64,6 +64,18 @@ function isLocalBrowserOrigin(origin: string | null): boolean { } } +function isTauriWebViewOrigin(origin: string | null): boolean { + if (!origin) return false + + try { + const url = new URL(origin) + return url.hostname === 'tauri.localhost' || + ((url.protocol === 'tauri:' || url.protocol === 'asset:') && url.hostname === 'localhost') + } catch { + return false + } +} + function isPrivateNetworkHost(host: string): boolean { const normalized = host.trim().replace(/^\[/, '').replace(/\]$/, '').toLowerCase() @@ -93,6 +105,10 @@ function isPrivateNetworkHost(host: string): boolean { } export function canBypassRemoteAuthForLocalBrowser(origin: string | null, requestHost: string): boolean { + if (isTauriWebViewOrigin(origin)) { + return isLocalServerHost(requestHost) + } + return isLocalBrowserOrigin(origin) && (isLocalServerHost(requestHost) || isPrivateNetworkHost(requestHost)) }