Compare commits

..

2 Commits

Author SHA1 Message Date
Marcos 08b782a488 fix(auth): prevent concurrent token exchange in Logto callback during Strict Mode 2026-08-18 10:55:31 +00:00
Marcos 337b397772 aaa 2026-08-18 10:52:03 +00:00
3 changed files with 12 additions and 3 deletions
+5
View File
@@ -7,6 +7,7 @@ import React, {
useEffect, useEffect,
useState, useState,
useCallback, useCallback,
useRef,
ReactNode, ReactNode,
} from 'react'; } from 'react';
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
@@ -56,11 +57,15 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [authInitialized, setAuthInitialized] = useState(false); const [authInitialized, setAuthInitialized] = useState(false);
const [isRecoveryFlow, setIsRecoveryFlow] = useState(false); const [isRecoveryFlow, setIsRecoveryFlow] = useState(false);
const callbackHandled = useRef(false);
// Detecta callback URL e processa // Detecta callback URL e processa
useEffect(() => { useEffect(() => {
const url = new URL(window.location.href); const url = new URL(window.location.href);
if (url.searchParams.has('code')) { if (url.searchParams.has('code')) {
if (callbackHandled.current) return;
callbackHandled.current = true;
setLoading(true); setLoading(true);
handleCallback().then((ok) => { handleCallback().then((ok) => {
if (ok) { if (ok) {
+6 -2
View File
@@ -126,16 +126,20 @@ export async function handleCallback(): Promise<boolean> {
const code = url.searchParams.get('code'); const code = url.searchParams.get('code');
const state = url.searchParams.get('state'); const state = url.searchParams.get('state');
console.log('[Logto Callback] Recebido:', { hasCode: !!code, hasState: !!state });
if (!code) return false; if (!code) return false;
const expectedState = sessionStorage.getItem('logto_state'); const expectedState = sessionStorage.getItem('logto_state');
const verifier = sessionStorage.getItem('logto_verifier') || '';
console.log('[Logto Callback] State match?', { received: state, expected: expectedState, hasVerifier: !!verifier });
if (state !== expectedState) { if (state !== expectedState) {
console.error('Logto: state mismatch'); console.error('Logto: state mismatch');
return false; return false;
} }
const verifier = sessionStorage.getItem('logto_verifier') || '';
const body = new URLSearchParams({ const body = new URLSearchParams({
grant_type: 'authorization_code', grant_type: 'authorization_code',
client_id: APP_ID, client_id: APP_ID,
+1 -1
View File
@@ -24,7 +24,7 @@ const Callback = () => {
} else { } else {
setStatus('error'); setStatus('error');
setMessage('Falha no login. Tente novamente.'); setMessage('Falha no login. Tente novamente.');
setTimeout(() => navigate('/auth'), 2500); // Não navega automaticamente - deixa o user ver o erro
} }
} catch (err) { } catch (err) {
if (cancelled) return; if (cancelled) return;