From badcd7c2c1378cf54962c2386973d3cfbe3722cc Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Tue, 18 Aug 2026 11:56:59 +0200 Subject: [PATCH] wip gate transit code exchange in the frontend only in embedded context --- .../src/features/auth/api/exchangeAccessToken.ts | 10 +++++++++- .../features/auth/components/TransitCodeGate.tsx | 10 +++++++--- src/frontend/src/features/auth/utils/transitCode.ts | 13 +++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/frontend/src/features/auth/api/exchangeAccessToken.ts b/src/frontend/src/features/auth/api/exchangeAccessToken.ts index 35ff4ffe..d907eb8d 100644 --- a/src/frontend/src/features/auth/api/exchangeAccessToken.ts +++ b/src/frontend/src/features/auth/api/exchangeAccessToken.ts @@ -1,6 +1,9 @@ import { fetchApi } from '@/api/fetchApi' import { setAccessToken } from '@/stores/accessToken' -import { consumeTransitCodeFromFragment } from '../utils/transitCode' +import { + consumeTransitCodeFromFragment, + isEmbedded, +} from '../utils/transitCode' type ApiAccessToken = { access_token: string @@ -28,6 +31,11 @@ const runInitialization = async (): Promise => { return } + if (!isEmbedded()) { + console.warn('Transit code ignored outside an embedded context') + return + } + try { const { access_token } = await exchangeAccessToken(code) setAccessToken(access_token) diff --git a/src/frontend/src/features/auth/components/TransitCodeGate.tsx b/src/frontend/src/features/auth/components/TransitCodeGate.tsx index 48069d53..838519f9 100644 --- a/src/frontend/src/features/auth/components/TransitCodeGate.tsx +++ b/src/frontend/src/features/auth/components/TransitCodeGate.tsx @@ -23,9 +23,13 @@ export const TransitCodeGate = ({ }) => { const hash = useHash() - // Latch the decision on the initial hash: the bootstrap scrubs the - // fragment as soon as it starts, and the gate must not flip back to the - // fast path while the exchange is still in flight. + // Note: the exchange only happens in an embedding context. This check lives + // in initializeAccessTokenFromFragment, the single funnel for all bootstrap paths. + // The gate still mounts top-level to scrub the fragment, but bootstrap then resolves + // immediately without exchanging. + // + // Latch the decision on the initial hash: bootstrap scrubs it immediately, and the + // gate must not switch back to the fast path while the exchange is in flight. const [needsExchange] = useState(() => hasTransitCodeInFragment(hash)) if (!needsExchange) { diff --git a/src/frontend/src/features/auth/utils/transitCode.ts b/src/frontend/src/features/auth/utils/transitCode.ts index 95b72fdd..08dda7fb 100644 --- a/src/frontend/src/features/auth/utils/transitCode.ts +++ b/src/frontend/src/features/auth/utils/transitCode.ts @@ -1,5 +1,18 @@ const TRANSIT_CODE_FRAGMENT_PARAM = 'transit_code' +/** + * Whether the app is rendered inside an embedding context (iframe). + * + * Comparing window references never throws, even when the parent is + * cross-origin. Defaults to false outside a browser environment. + */ +export const isEmbedded = (): boolean => { + if (typeof window === 'undefined') { + return false + } + return window.self !== window.top +} + /** * Whether a URL fragment carries a transit code. Pure check, does not * consume anything.