From bfd5730661907c6be99f54dcc1f7d25f2486e4aa Mon Sep 17 00:00:00 2001 From: 7w1 Date: Wed, 22 Jul 2026 11:46:56 -0500 Subject: [PATCH 1/3] fix sso deeplinking behaviors hopefully --- src/app/pages/Router.tsx | 10 ++++++++-- src/app/pages/TauriDeepLinkBridge.test.tsx | 6 ++++++ src/app/pages/auth/SSOTauri.test.ts | 15 ++++++++++++++- src/app/pages/auth/SSOTauri.ts | 12 +++++++++++- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/app/pages/Router.tsx b/src/app/pages/Router.tsx index 1976ae289..8834efbcd 100644 --- a/src/app/pages/Router.tsx +++ b/src/app/pages/Router.tsx @@ -156,7 +156,14 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) const mobile = screenSize === ScreenSize.Mobile; const routes = createRoutesFromElements( - + + + + + } + > { @@ -192,7 +199,6 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) > {null}}> <> - diff --git a/src/app/pages/TauriDeepLinkBridge.test.tsx b/src/app/pages/TauriDeepLinkBridge.test.tsx index bacec3cf2..5167e6248 100644 --- a/src/app/pages/TauriDeepLinkBridge.test.tsx +++ b/src/app/pages/TauriDeepLinkBridge.test.tsx @@ -38,6 +38,12 @@ describe('mapDeepLinkToLoginPath', () => { expect(path).toContain('state=s1'); }); + it('passes normalized moe.sable.app://login OIDC callback through', () => { + const path = mapDeepLinkToLoginPath('moe.sable.app://login?code=c1&state=s1'); + expect(path).toContain('code=c1'); + expect(path).toContain('state=s1'); + }); + it('returns undefined for an unrelated url', () => { expect(mapDeepLinkToLoginPath('https://example.com/whatever')).toBeUndefined(); }); diff --git a/src/app/pages/auth/SSOTauri.test.ts b/src/app/pages/auth/SSOTauri.test.ts index c16e9082b..1ade97576 100644 --- a/src/app/pages/auth/SSOTauri.test.ts +++ b/src/app/pages/auth/SSOTauri.test.ts @@ -79,14 +79,27 @@ describe('parseTauriSsoCallback', () => { }); describe('parseTauriOidcCallback', () => { - it('parses code and state', () => { + it('parses code and state from single-slash format', () => { expect(parseTauriOidcCallback('moe.sable.app:/login?code=c1&state=s1')).toEqual({ code: 'c1', state: 's1', }); }); + it('parses code and state from authority/hostname format (moe.sable.app://login)', () => { + expect(parseTauriOidcCallback('moe.sable.app://login?code=c1&state=s1')).toEqual({ + code: 'c1', + state: 's1', + }); + }); + it('rejects the wrong path', () => { expect(parseTauriOidcCallback('moe.sable.app:/other?code=c1&state=s1')).toBeUndefined(); + expect(parseTauriOidcCallback('moe.sable.app://other?code=c1&state=s1')).toBeUndefined(); + }); + + it('rejects a different protocol', () => { + expect(parseTauriOidcCallback('sable://login?code=c1&state=s1')).toBeUndefined(); + expect(parseTauriOidcCallback('https://login?code=c1&state=s1')).toBeUndefined(); }); }); diff --git a/src/app/pages/auth/SSOTauri.ts b/src/app/pages/auth/SSOTauri.ts index 4100a75d4..633edd59c 100644 --- a/src/app/pages/auth/SSOTauri.ts +++ b/src/app/pages/auth/SSOTauri.ts @@ -75,7 +75,17 @@ export const parseTauriOidcCallback = ( try { const callbackUrl = new URL(rawUrl); if (callbackUrl.protocol !== TAURI_OIDC_PROTOCOL) return undefined; - if (callbackUrl.pathname !== TAURI_OIDC_PATH) return undefined; + + // `moe.sable.app:/login?...` → pathname === '/login' + // `moe.sable.app://login?...` → hostname === 'login', pathname === '/' or '' + // Some platforms may also deliver a trailing slash variant. + const isValidTarget = + callbackUrl.pathname === TAURI_OIDC_PATH || + callbackUrl.pathname === `${TAURI_OIDC_PATH}/` || + (callbackUrl.hostname === 'login' && + (callbackUrl.pathname === '/' || callbackUrl.pathname === '')); + + if (!isValidTarget) return undefined; const code = callbackUrl.searchParams.get('code'); const state = callbackUrl.searchParams.get('state'); From 6ff9c02c396d0dd46022b465e9bdb4c4a8848e36 Mon Sep 17 00:00:00 2001 From: 7w1 Date: Wed, 22 Jul 2026 11:49:57 -0500 Subject: [PATCH 2/3] harden parsing --- src/app/pages/auth/SSOTauri.test.ts | 6 ++++++ src/app/pages/auth/SSOTauri.ts | 17 ++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/app/pages/auth/SSOTauri.test.ts b/src/app/pages/auth/SSOTauri.test.ts index 1ade97576..d63f94c15 100644 --- a/src/app/pages/auth/SSOTauri.test.ts +++ b/src/app/pages/auth/SSOTauri.test.ts @@ -102,4 +102,10 @@ describe('parseTauriOidcCallback', () => { expect(parseTauriOidcCallback('sable://login?code=c1&state=s1')).toBeUndefined(); expect(parseTauriOidcCallback('https://login?code=c1&state=s1')).toBeUndefined(); }); + + it('rejects an unrelated hostname with a login path', () => { + expect( + parseTauriOidcCallback('moe.sable.app://evil/login?code=c1&state=s1') + ).toBeUndefined(); + }); }); diff --git a/src/app/pages/auth/SSOTauri.ts b/src/app/pages/auth/SSOTauri.ts index 633edd59c..da495f7c5 100644 --- a/src/app/pages/auth/SSOTauri.ts +++ b/src/app/pages/auth/SSOTauri.ts @@ -76,16 +76,19 @@ export const parseTauriOidcCallback = ( const callbackUrl = new URL(rawUrl); if (callbackUrl.protocol !== TAURI_OIDC_PROTOCOL) return undefined; - // `moe.sable.app:/login?...` → pathname === '/login' + // `moe.sable.app:/login?...` → hostname === '', pathname === '/login' // `moe.sable.app://login?...` → hostname === 'login', pathname === '/' or '' - // Some platforms may also deliver a trailing slash variant. - const isValidTarget = + const hasLoginPath = callbackUrl.pathname === TAURI_OIDC_PATH || - callbackUrl.pathname === `${TAURI_OIDC_PATH}/` || - (callbackUrl.hostname === 'login' && - (callbackUrl.pathname === '/' || callbackUrl.pathname === '')); + callbackUrl.pathname === `${TAURI_OIDC_PATH}/`; - if (!isValidTarget) return undefined; + const isSingleSlashFormat = callbackUrl.hostname === '' && hasLoginPath; + + const isAuthorityFormat = + callbackUrl.hostname === 'login' && + (callbackUrl.pathname === '/' || callbackUrl.pathname === ''); + + if (!isSingleSlashFormat && !isAuthorityFormat) return undefined; const code = callbackUrl.searchParams.get('code'); const state = callbackUrl.searchParams.get('state'); From bc526c83fefc12fd925a780821945fea9f68352b Mon Sep 17 00:00:00 2001 From: 7w1 Date: Wed, 22 Jul 2026 11:50:51 -0500 Subject: [PATCH 3/3] formatting --- src/app/pages/auth/SSOTauri.test.ts | 4 +--- src/app/pages/auth/SSOTauri.ts | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/app/pages/auth/SSOTauri.test.ts b/src/app/pages/auth/SSOTauri.test.ts index d63f94c15..577cfa841 100644 --- a/src/app/pages/auth/SSOTauri.test.ts +++ b/src/app/pages/auth/SSOTauri.test.ts @@ -104,8 +104,6 @@ describe('parseTauriOidcCallback', () => { }); it('rejects an unrelated hostname with a login path', () => { - expect( - parseTauriOidcCallback('moe.sable.app://evil/login?code=c1&state=s1') - ).toBeUndefined(); + expect(parseTauriOidcCallback('moe.sable.app://evil/login?code=c1&state=s1')).toBeUndefined(); }); }); diff --git a/src/app/pages/auth/SSOTauri.ts b/src/app/pages/auth/SSOTauri.ts index da495f7c5..11fb3d221 100644 --- a/src/app/pages/auth/SSOTauri.ts +++ b/src/app/pages/auth/SSOTauri.ts @@ -79,8 +79,7 @@ export const parseTauriOidcCallback = ( // `moe.sable.app:/login?...` → hostname === '', pathname === '/login' // `moe.sable.app://login?...` → hostname === 'login', pathname === '/' or '' const hasLoginPath = - callbackUrl.pathname === TAURI_OIDC_PATH || - callbackUrl.pathname === `${TAURI_OIDC_PATH}/`; + callbackUrl.pathname === TAURI_OIDC_PATH || callbackUrl.pathname === `${TAURI_OIDC_PATH}/`; const isSingleSlashFormat = callbackUrl.hostname === '' && hasLoginPath;