From a5968c76f177aaffab0868293b5b18523c28843f Mon Sep 17 00:00:00 2001 From: Marcos Reifonas Date: Mon, 17 Aug 2026 19:14:49 +0000 Subject: [PATCH] =?UTF-8?q?feat(auth):=20migra=C3=A7=C3=A3o=20final=20do?= =?UTF-8?q?=20UI=20frontend=20para=20Logto=20OIDC=20via=20GoTrue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useAuth.tsx | 14 +- src/main.tsx | 28 ++-- src/pages/Auth.tsx | 364 ++++-------------------------------------- 3 files changed, 60 insertions(+), 346 deletions(-) diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx index fc41b3a..ec90b65 100644 --- a/src/hooks/useAuth.tsx +++ b/src/hooks/useAuth.tsx @@ -7,7 +7,7 @@ interface AuthContextType { user: User | null; session: Session | null; loading: boolean; - signIn: (email: string, password: string) => Promise<{ error: unknown }>; + signIn: () => Promise<{ error: unknown }>; signUp: (email: string, password: string) => Promise<{ error: unknown }>; signOut: () => Promise; updatePassword: (password: string) => Promise<{ error: unknown }>; @@ -334,15 +334,17 @@ export function AuthProvider({ children }: { children: ReactNode }) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [user?.id, authInitialized]); - const signIn = async (email: string, password: string) => { + const signIn = async () => { try { - const { error } = await supabase.auth.signInWithPassword({ - email, - password, + const { error } = await supabase.auth.signInWithOAuth({ + provider: 'keycloak', + options: { + scopes: 'openid email profile', + } }); return { error }; } catch (error) { - logger.error('Erro crítico no login:', error); + logger.error('Erro crítico no login SSO:', error); return { error }; } }; diff --git a/src/main.tsx b/src/main.tsx index e548786..ff28edb 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -9,6 +9,7 @@ import { AuthProvider } from './hooks/useAuth' import { IconStyleProvider } from './hooks/useIconStyle' import { ErrorBoundary } from './components/ErrorBoundary' import { AppRoutes } from './components/AppRoutes' +import { LogtoProvider, LogtoConfig } from '@logto/react' import './index.css' @@ -28,6 +29,11 @@ const queryClient = new QueryClient({ }, }) +const config: LogtoConfig = { + endpoint: 'https://logto-bzlued1boxl3t8ewsyn99an9.187.77.227.172.sslip.io', + appId: '4qun0u1tfce1fdn3pxrgq', +}; + // ============================================================================ // Render da aplicação // ============================================================================ @@ -36,16 +42,18 @@ ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - - - - - - + + + + + + + + + + + + , diff --git a/src/pages/Auth.tsx b/src/pages/Auth.tsx index dc0b9fc..7591ab2 100644 --- a/src/pages/Auth.tsx +++ b/src/pages/Auth.tsx @@ -16,57 +16,35 @@ import { Eye, EyeOff, Mail, Lock } from 'lucide-react'; const Auth = () => { const [isLoading, setIsLoading] = useState(false); - const [showPassword, setShowPassword] = useState(false); - const [showForgotPassword, setShowForgotPassword] = useState(false); - const [loginData, setLoginData] = useState({ email: '', password: '' }); - const [signupData, setSignupData] = useState({ email: '', password: '', confirmPassword: '' }); - const { signIn, signUp, user, loading, isRecoveryFlow, session } = useAuth(); + const { signIn, user, loading, isRecoveryFlow, session } = useAuth(); const { brandSettings } = useBrandSettings(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const location = useLocation(); + const recoveryType = searchParams.get('type'); + const hashParams = new URLSearchParams(location.hash.substring(1)); + const hashType = hashParams.get('type'); + const accessToken = hashParams.get('access_token'); + const refreshToken = hashParams.get('refresh_token'); + + const shouldShowReset = Boolean( + recoveryType === 'recovery' || + hashType === 'recovery' || + isRecoveryFlow || + (accessToken && refreshToken) + ); + // Estado para controlar se está em modo de recuperação - const [showPasswordReset, setShowPasswordReset] = useState(false); + const [showPasswordReset, setShowPasswordReset] = useState(shouldShowReset); useEffect(() => { - console.log('🔍 Auth.tsx - Verificando fluxo de recuperação...'); - console.log('Location:', { - pathname: location.pathname, - search: location.search, - hash: location.hash - }); - console.log('Auth states:', { - user: !!user, - session: !!session, - isRecoveryFlow, - loading - }); - - // Verificar se é fluxo de recuperação - const recoveryType = searchParams.get('type'); - const hashParams = new URLSearchParams(location.hash.substring(1)); - const hashType = hashParams.get('type'); - const accessToken = hashParams.get('access_token'); - const refreshToken = hashParams.get('refresh_token'); - - console.log('🔍 Parâmetros de recuperação:', { - recoveryType, - hashType, - accessToken: !!accessToken, - refreshToken: !!refreshToken, - isRecoveryFlow - }); - - // Se há type=recovery OU isRecoveryFlow OU tokens, mostrar tela de reset - const shouldShowReset = recoveryType === 'recovery' || - hashType === 'recovery' || - isRecoveryFlow || - (accessToken && refreshToken); - - console.log('🔑 Deve mostrar reset de senha:', shouldShowReset); - setShowPasswordReset(Boolean(shouldShowReset)); - }, [searchParams, location.hash, location.search, isRecoveryFlow, user, session]); + if (shouldShowReset !== showPasswordReset) { + // eslint-disable-next-line react-hooks/exhaustive-deps + setShowPasswordReset(shouldShowReset); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [shouldShowReset, showPasswordReset, loading, location.pathname]); // Redirecionar usuários autenticados para página principal (exceto em fluxo de recuperação) useEffect(() => { @@ -139,112 +117,18 @@ const Auth = () => { ); } - const validatePassword = (password: string) => { - const minLength = password.length >= 8; - const hasUpperCase = /[A-Z]/.test(password); - const hasLowerCase = /[a-z]/.test(password); - const hasNumbers = /\d/.test(password); - const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password); - - const score = [minLength, hasUpperCase, hasLowerCase, hasNumbers, hasSpecialChar].filter(Boolean).length; - - return { - score, - minLength, - hasUpperCase, - hasLowerCase, - hasNumbers, - hasSpecialChar, - isValid: score >= 4 && minLength - }; - }; - - const passwordStrength = validatePassword(signupData.password); - - // Email validation - const isValidEmail = (email: string) => { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return emailRegex.test(email); - }; - - const handleLogin = async (e: React.FormEvent) => { - e.preventDefault(); - - if (!loginData.email || !loginData.password) { - toast.error('Por favor, preencha todos os campos'); - return; - } - - if (!isValidEmail(loginData.email)) { - toast.error('Por favor, insira um email válido'); - return; - } - + const handleLogin = async () => { setIsLoading(true); - const { error } = await signIn(loginData.email, loginData.password); + const { error } = await signIn(); if (error) { - if (error.message.includes('Invalid login credentials')) { - toast.error('Email ou senha incorretos'); - } else { - toast.error('Erro ao fazer login: ' + error.message); - } - } else { - toast.success('Login realizado com sucesso!'); - navigate('/'); + toast.error('Erro ao redirecionar para o login: ' + (error as Error).message); + setIsLoading(false); } - setIsLoading(false); + // No else block needed as it redirects to external OIDC provider }; - const handleSignup = async (e: React.FormEvent) => { - e.preventDefault(); - - if (!signupData.email || !signupData.password || !signupData.confirmPassword) { - toast.error('Por favor, preencha todos os campos'); - return; - } - if (!isValidEmail(signupData.email)) { - toast.error('Por favor, insira um email válido'); - return; - } - - if (!passwordStrength.isValid) { - toast.error('A senha deve ter pelo menos 8 caracteres e incluir maiúsculas, minúsculas, números e símbolos'); - return; - } - - if (signupData.password !== signupData.confirmPassword) { - toast.error('As senhas não coincidem'); - return; - } - - setIsLoading(true); - const { error } = await signUp(signupData.email, signupData.password); - - if (error) { - if (error.message.includes('User already registered')) { - toast.error('Este email já está cadastrado'); - } else { - toast.error('Erro ao criar conta: ' + error.message); - } - } else { - toast.success('Conta criada com sucesso! Verifique seu email.'); - } - setIsLoading(false); - }; - - const getPasswordStrengthColor = (score: number) => { - if (score <= 2) return 'bg-red-500'; - if (score <= 3) return 'bg-yellow-500'; - return 'bg-green-500'; - }; - - const getPasswordStrengthText = (score: number) => { - if (score <= 2) return 'Fraca'; - if (score <= 3) return 'Média'; - return 'Forte'; - }; console.log('📱 Renderizando página de login/cadastro'); return ( @@ -281,188 +165,14 @@ const Auth = () => { - - - - Entrar - Cadastrar - - - -
-
- -
- - setLoginData({ ...loginData, email: e.target.value })} - className="pl-10 h-12" - disabled={isLoading} - /> -
-
- -
- -
- - setLoginData({ ...loginData, password: e.target.value })} - className="pl-10 pr-10 h-12" - disabled={isLoading} - /> - -
-
- - - - {/* Botão Esqueci a Senha */} -
- -
-
-
- - -
-
- -
- - setSignupData({ ...signupData, email: e.target.value })} - className="pl-10 h-12" - disabled={isLoading} - /> -
-
- -
- -
- - setSignupData({ ...signupData, password: e.target.value })} - className="pl-10 pr-10 h-12" - disabled={isLoading} - /> - -
- - {/* Password Strength Indicator */} - {signupData.password && ( -
-
-
-
-
- - {getPasswordStrengthText(passwordStrength.score)} - -
-
-
- - Mínimo 8 caracteres -
-
- - Letra maiúscula -
-
- - Número -
-
- - Símbolo especial -
-
-
- )} -
- -
- -
- - setSignupData({ ...signupData, confirmPassword: e.target.value })} - className="pl-10 h-12" - disabled={isLoading} - /> -
-
- - -
-
-
+ + @@ -471,12 +181,6 @@ const Auth = () => {

- - {/* Modal Esqueci a Senha */} - setShowForgotPassword(false)} - /> ); };