Skip to content

Commit 161ea9b

Browse files
committed
fix(cloud): restore fragment-based Space launch callback
1 parent c7d1467 commit 161ea9b

2 files changed

Lines changed: 45 additions & 2 deletions

File tree

web/src/app/auth/space/callback/page.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ function SpaceOAuthCallbackContent() {
6666
const [searchParams] = useSearchParams();
6767
const { t } = useTranslation();
6868
const isMountedRef = useRef(true);
69+
const directLaunchFragmentRef = useRef<{
70+
workspaceUuid: string | null;
71+
launchAssertion: string | null;
72+
} | null>(null);
6973

7074
const [status, setStatus] = useState<
7175
'loading' | 'confirm' | 'success' | 'error'
@@ -220,8 +224,29 @@ function SpaceOAuthCallbackContent() {
220224
const errorDescription = searchParams.get('error_description');
221225
const mode = searchParams.get('mode');
222226
const state = searchParams.get('state');
223-
const workspaceUuid = searchParams.get('workspace_uuid');
224-
const launchAssertion = searchParams.get('launch_assertion');
227+
if (directLaunchFragmentRef.current === null) {
228+
const fragmentParams = new URLSearchParams(
229+
window.location.hash.startsWith('#')
230+
? window.location.hash.slice(1)
231+
: window.location.hash,
232+
);
233+
directLaunchFragmentRef.current = {
234+
workspaceUuid: fragmentParams.get('workspace_uuid'),
235+
launchAssertion: fragmentParams.get('launch_assertion'),
236+
};
237+
if (window.location.hash) {
238+
window.history.replaceState(
239+
null,
240+
'',
241+
`${window.location.pathname}${window.location.search}`,
242+
);
243+
}
244+
}
245+
const workspaceUuid =
246+
directLaunchFragmentRef.current.workspaceUuid ??
247+
searchParams.get('workspace_uuid');
248+
const launchAssertion =
249+
directLaunchFragmentRef.current.launchAssertion;
225250

226251
if (error) {
227252
setStatus('error');
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import assert from 'node:assert/strict';
2+
import fs from 'node:fs';
3+
import test from 'node:test';
4+
5+
const source = fs.readFileSync(
6+
new URL('../../src/app/auth/space/callback/page.tsx', import.meta.url),
7+
'utf8',
8+
);
9+
10+
test('direct launch assertion is fragment-only and removed before exchange', () => {
11+
assert.doesNotMatch(source, /searchParams\.get\(['"]launch_assertion['"]\)/);
12+
const readIndex = source.indexOf("fragmentParams.get('launch_assertion')");
13+
const clearIndex = source.indexOf('window.history.replaceState');
14+
const exchangeIndex = source.indexOf('handleOAuthCallback(', clearIndex);
15+
assert.ok(readIndex >= 0, 'fragment assertion read is missing');
16+
assert.ok(clearIndex > readIndex, 'URL fragment is not cleared after copying the assertion');
17+
assert.ok(exchangeIndex > clearIndex, 'assertion exchange starts before the fragment is cleared');
18+
});

0 commit comments

Comments
 (0)