fix(auth): require a hashed poll secret for desktop handoff (#2357)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-02 17:25:54 +02:00
1 parent b82681b77a
commit f703969e80
45 files changed
+382 -188

No files matched your search

@@ -113,6 +113,7 @@ export type ResetPasswordResponse = AuthTokenResponse | MfaLoginResponse;
interface DesktopHandoffInitiateResponse {
code: string;
expires_at: string;
poll_secret?: string;
}
interface DesktopHandoffStatusResponse {
@@ -537,9 +538,19 @@ export async function initiateDesktopHandoff(): Promise<DesktopHandoffInitiateRe
return response.body;
}
export async function pollDesktopHandoffStatus(code: string): Promise<DesktopHandoffStatusResponse> {
const response = await http.get<DesktopHandoffStatusResponse>(Endpoints.AUTH_HANDOFF_STATUS(code), {
export async function pollDesktopHandoffStatus(
code: string,
pollSecret?: string | null,
): Promise<DesktopHandoffStatusResponse> {
if (pollSecret == null || pollSecret.length === 0) {
const response = await http.get<DesktopHandoffStatusResponse>(Endpoints.AUTH_HANDOFF_STATUS(code), {
auth: 'none',
});
return response.body;
}
const response = await http.post<DesktopHandoffStatusResponse>(Endpoints.AUTH_HANDOFF_STATUS(code), {
auth: 'none',
body: {poll_secret: pollSecret},
});
return response.body;
}
@@ -38,6 +38,7 @@ const BrowserLoginHandoffModal = observer(({onSuccess, prefillEmail}: BrowserLog
const [isGenerating, setIsGenerating] = useState(false);
const [error, setError] = useState<string | null>(null);
const pollingRef = useRef(false);
const handoffPollSecretRef = useRef<string | null>(null);
const completedRef = useRef(false);
const generateCode = useCallback(async () => {
setIsGenerating(true);
@@ -46,6 +47,7 @@ const BrowserLoginHandoffModal = observer(({onSuccess, prefillEmail}: BrowserLog
setHandoffExpiresAt(null);
try {
const result = await AuthenticationCommands.initiateDesktopHandoff();
handoffPollSecretRef.current = result.poll_secret ?? null;
setHandoffCode(result.code);
setHandoffExpiresAt(result.expires_at);
} catch (e) {
@@ -63,7 +65,7 @@ const BrowserLoginHandoffModal = observer(({onSuccess, prefillEmail}: BrowserLog
const timer = setInterval(async () => {
if (!pollingRef.current) return;
try {
const result = await AuthenticationCommands.pollDesktopHandoffStatus(handoffCode);
const result = await AuthenticationCommands.pollDesktopHandoffStatus(handoffCode, handoffPollSecretRef.current);
if (result.status === 'completed' && result.token && result.user_id) {
pollingRef.current = false;
completedRef.current = true;