fix(cloud): restore fragment-based Space launch callback

This commit is contained in:
dadachann
2026-08-01 04:41:44 +00:00
parent c7d14676fc
commit 161ea9b3eb
2 changed files with 45 additions and 2 deletions
+27 -2
View File
@@ -66,6 +66,10 @@ function SpaceOAuthCallbackContent() {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const { t } = useTranslation(); const { t } = useTranslation();
const isMountedRef = useRef(true); const isMountedRef = useRef(true);
const directLaunchFragmentRef = useRef<{
workspaceUuid: string | null;
launchAssertion: string | null;
} | null>(null);
const [status, setStatus] = useState< const [status, setStatus] = useState<
'loading' | 'confirm' | 'success' | 'error' 'loading' | 'confirm' | 'success' | 'error'
@@ -220,8 +224,29 @@ function SpaceOAuthCallbackContent() {
const errorDescription = searchParams.get('error_description'); const errorDescription = searchParams.get('error_description');
const mode = searchParams.get('mode'); const mode = searchParams.get('mode');
const state = searchParams.get('state'); const state = searchParams.get('state');
const workspaceUuid = searchParams.get('workspace_uuid'); if (directLaunchFragmentRef.current === null) {
const launchAssertion = searchParams.get('launch_assertion'); const fragmentParams = new URLSearchParams(
window.location.hash.startsWith('#')
? window.location.hash.slice(1)
: window.location.hash,
);
directLaunchFragmentRef.current = {
workspaceUuid: fragmentParams.get('workspace_uuid'),
launchAssertion: fragmentParams.get('launch_assertion'),
};
if (window.location.hash) {
window.history.replaceState(
null,
'',
`${window.location.pathname}${window.location.search}`,
);
}
}
const workspaceUuid =
directLaunchFragmentRef.current.workspaceUuid ??
searchParams.get('workspace_uuid');
const launchAssertion =
directLaunchFragmentRef.current.launchAssertion;
if (error) { if (error) {
setStatus('error'); setStatus('error');
@@ -0,0 +1,18 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import test from 'node:test';
const source = fs.readFileSync(
new URL('../../src/app/auth/space/callback/page.tsx', import.meta.url),
'utf8',
);
test('direct launch assertion is fragment-only and removed before exchange', () => {
assert.doesNotMatch(source, /searchParams\.get\(['"]launch_assertion['"]\)/);
const readIndex = source.indexOf("fragmentParams.get('launch_assertion')");
const clearIndex = source.indexOf('window.history.replaceState');
const exchangeIndex = source.indexOf('handleOAuthCallback(', clearIndex);
assert.ok(readIndex >= 0, 'fragment assertion read is missing');
assert.ok(clearIndex > readIndex, 'URL fragment is not cleared after copying the assertion');
assert.ok(exchangeIndex > clearIndex, 'assertion exchange starts before the fragment is cleared');
});