Implement authentication improvements.
- Enable email confirmation. - Implement social login (Google). - Review brute force protection settings.
This commit is contained in:
parent
e31d990b1b
commit
cf27d1561e
@ -1,7 +1,9 @@
|
||||
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { Suspense, lazy } from 'react';
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import Auth from './pages/Auth';
|
||||
import EmailConfirmation from './pages/EmailConfirmation';
|
||||
import ProtectedRoute from './components/auth/ProtectedRoute';
|
||||
import { authStore } from './stores/authStore';
|
||||
|
||||
@ -31,6 +33,12 @@ function App() {
|
||||
element={isLoggedIn ? <Navigate to="/" replace /> : <Auth />}
|
||||
/>
|
||||
|
||||
{/* Email confirmation route - should be accessible without authentication */}
|
||||
<Route
|
||||
path="/email-confirmation"
|
||||
element={<EmailConfirmation />}
|
||||
/>
|
||||
|
||||
{/* Protected routes */}
|
||||
<Route path="/" element={
|
||||
<ProtectedRoute>
|
||||
|
||||
32
src/components/auth/AuthSecurityFeatures.tsx
Normal file
32
src/components/auth/AuthSecurityFeatures.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Shield, Mail, Lock } from 'lucide-react';
|
||||
|
||||
const AuthSecurityFeatures = () => {
|
||||
return (
|
||||
<div className="space-y-3 mt-4">
|
||||
<Alert>
|
||||
<Shield className="h-4 w-4" />
|
||||
<AlertDescription className="text-sm">
|
||||
<strong>Segurança reforçada:</strong> Sua conta está protegida contra tentativas de acesso não autorizado.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<Alert>
|
||||
<Mail className="h-4 w-4" />
|
||||
<AlertDescription className="text-sm">
|
||||
<strong>Confirmação por email:</strong> Você receberá um link de confirmação para ativar sua conta.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<Alert>
|
||||
<Lock className="h-4 w-4" />
|
||||
<AlertDescription className="text-sm">
|
||||
<strong>Proteção contra força bruta:</strong> Sistema automaticamente bloqueia tentativas suspeitas.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthSecurityFeatures;
|
||||
@ -49,7 +49,7 @@ const RegisterForm = ({ isLoading, setIsLoading }: RegisterFormProps) => {
|
||||
email,
|
||||
password: senha,
|
||||
options: {
|
||||
emailRedirectTo: `${window.location.origin}/`,
|
||||
emailRedirectTo: `${window.location.origin}/email-confirmation`,
|
||||
data: {
|
||||
nome,
|
||||
empresa: empresa || null,
|
||||
@ -69,7 +69,7 @@ const RegisterForm = ({ isLoading, setIsLoading }: RegisterFormProps) => {
|
||||
if (data.user.identities && data.user.identities.length === 0) {
|
||||
console.log('👤 Usuário já existe, reenviando confirmação');
|
||||
toast.info("E-mail já cadastrado. Verifique sua caixa de entrada!", {
|
||||
description: "Enviamos um novo link para você definir sua senha e acessar sua conta. Não se esqueça de checar a pasta de spam.",
|
||||
description: "Enviamos um novo link de confirmação para você ativar sua conta. Não se esqueça de checar a pasta de spam.",
|
||||
duration: 10000,
|
||||
});
|
||||
} else {
|
||||
@ -94,11 +94,17 @@ const RegisterForm = ({ isLoading, setIsLoading }: RegisterFormProps) => {
|
||||
console.error('❌ Erro crítico no webhook para n8n:', error);
|
||||
});
|
||||
|
||||
// Show success message and redirect to login
|
||||
toast.success("Cadastro realizado com sucesso!", {
|
||||
description: "📧 Enviamos um link de confirmação para seu email. Clique no link para ativar sua conta e fazer login.",
|
||||
duration: 10000,
|
||||
});
|
||||
|
||||
// Redirect to login page with success message
|
||||
navigate('/auth', {
|
||||
state: {
|
||||
showSuccessMessage: true,
|
||||
message: "✅ Cadastro realizado com sucesso! Agora, faça o login com o e-mail e a senha que você acabou de criar."
|
||||
message: "✅ Cadastro realizado! Verifique seu email e clique no link de confirmação para ativar sua conta."
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
96
src/components/auth/SocialLoginButtons.tsx
Normal file
96
src/components/auth/SocialLoginButtons.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { toast } from "sonner";
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
|
||||
const SocialLoginButtons = () => {
|
||||
const [loadingGoogle, setLoadingGoogle] = useState(false);
|
||||
|
||||
const handleGoogleLogin = async () => {
|
||||
setLoadingGoogle(true);
|
||||
|
||||
try {
|
||||
console.log('🔐 Iniciando login com Google...');
|
||||
|
||||
const { error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'google',
|
||||
options: {
|
||||
redirectTo: `${window.location.origin}/`,
|
||||
queryParams: {
|
||||
access_type: 'offline',
|
||||
prompt: 'consent',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('❌ Erro no login com Google:', error);
|
||||
toast.error("Erro no login com Google", {
|
||||
description: error.message || "Não foi possível conectar com o Google. Tente novamente.",
|
||||
});
|
||||
}
|
||||
// Note: Se não houver erro, o usuário será redirecionado automaticamente
|
||||
} catch (error) {
|
||||
console.error('❌ Erro geral no login com Google:', error);
|
||||
toast.error("Erro no login", {
|
||||
description: "Ocorreu um erro inesperado. Tente novamente.",
|
||||
});
|
||||
} finally {
|
||||
setLoadingGoogle(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-background px-2 text-muted-foreground">
|
||||
Ou continue com
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleGoogleLogin}
|
||||
disabled={loadingGoogle}
|
||||
className="w-full"
|
||||
>
|
||||
{loadingGoogle ? (
|
||||
<>
|
||||
<div className="mr-2 h-4 w-4 animate-spin rounded-full border-2 border-b-transparent" />
|
||||
Conectando...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg className="mr-2 h-4 w-4" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
||||
/>
|
||||
</svg>
|
||||
Continuar com Google
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SocialLoginButtons;
|
||||
@ -5,6 +5,8 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import LoginForm from '@/components/auth/LoginForm';
|
||||
import RegisterForm from '@/components/auth/RegisterForm';
|
||||
import SocialLoginButtons from '@/components/auth/SocialLoginButtons';
|
||||
import AuthSecurityFeatures from '@/components/auth/AuthSecurityFeatures';
|
||||
import { toast } from "sonner";
|
||||
|
||||
const Auth = () => {
|
||||
@ -12,10 +14,10 @@ const Auth = () => {
|
||||
const [activeTab, setActiveTab] = useState("login");
|
||||
const location = useLocation();
|
||||
|
||||
// Show success message when redirected from registration
|
||||
// Show success message when redirected from registration or email confirmation
|
||||
useEffect(() => {
|
||||
if (location.state?.showSuccessMessage && location.state?.message) {
|
||||
toast.success("Cadastro realizado!", {
|
||||
toast.success("Sucesso!", {
|
||||
description: location.state.message,
|
||||
duration: 8000,
|
||||
});
|
||||
@ -40,12 +42,15 @@ const Auth = () => {
|
||||
<TabsTrigger value="cadastro">Cadastro</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="login">
|
||||
<TabsContent value="login" className="space-y-4">
|
||||
<LoginForm isLoading={isLoading} setIsLoading={setIsLoading} />
|
||||
<SocialLoginButtons />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="cadastro">
|
||||
<TabsContent value="cadastro" className="space-y-4">
|
||||
<RegisterForm isLoading={isLoading} setIsLoading={setIsLoading} />
|
||||
<SocialLoginButtons />
|
||||
<AuthSecurityFeatures />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
|
||||
126
src/pages/EmailConfirmation.tsx
Normal file
126
src/pages/EmailConfirmation.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CheckCircle, XCircle, Loader2 } from 'lucide-react';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { toast } from "sonner";
|
||||
|
||||
const EmailConfirmation = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
|
||||
const [message, setMessage] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const handleEmailConfirmation = async () => {
|
||||
try {
|
||||
// Pegar os tokens da URL
|
||||
const token_hash = searchParams.get('token_hash');
|
||||
const type = searchParams.get('type');
|
||||
|
||||
if (token_hash && type) {
|
||||
console.log('🔐 Processando confirmação de email...');
|
||||
|
||||
const { data, error } = await supabase.auth.verifyOtp({
|
||||
token_hash,
|
||||
type: type as any,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('❌ Erro na confirmação:', error);
|
||||
setStatus('error');
|
||||
setMessage('Erro ao confirmar email. O link pode ter expirado.');
|
||||
toast.error("Erro na confirmação", {
|
||||
description: "O link de confirmação pode ter expirado. Tente fazer login novamente.",
|
||||
});
|
||||
} else {
|
||||
console.log('✅ Email confirmado com sucesso:', data);
|
||||
setStatus('success');
|
||||
setMessage('Email confirmado com sucesso! Você já pode fazer login.');
|
||||
toast.success("Email confirmado!", {
|
||||
description: "Sua conta foi ativada com sucesso. Redirecionando...",
|
||||
});
|
||||
|
||||
// Redirecionar para login após 3 segundos
|
||||
setTimeout(() => {
|
||||
navigate('/auth', {
|
||||
state: {
|
||||
showSuccessMessage: true,
|
||||
message: "✅ Email confirmado! Agora você pode fazer login com suas credenciais."
|
||||
}
|
||||
});
|
||||
}, 3000);
|
||||
}
|
||||
} else {
|
||||
setStatus('error');
|
||||
setMessage('Link de confirmação inválido.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Erro geral na confirmação:', error);
|
||||
setStatus('error');
|
||||
setMessage('Ocorreu um erro inesperado.');
|
||||
}
|
||||
};
|
||||
|
||||
handleEmailConfirmation();
|
||||
}, [searchParams, navigate]);
|
||||
|
||||
const handleBackToLogin = () => {
|
||||
navigate('/auth');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen bg-gray-50 px-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="space-y-1 text-center">
|
||||
<CardTitle className="text-2xl font-bold">Confirmação de Email</CardTitle>
|
||||
<CardDescription>
|
||||
{status === 'loading' && "Processando confirmação do seu email..."}
|
||||
{status === 'success' && "Email confirmado com sucesso!"}
|
||||
{status === 'error' && "Problema na confirmação"}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
{status === 'loading' && (
|
||||
<>
|
||||
<Loader2 className="h-12 w-12 text-blue-600 animate-spin" />
|
||||
<p className="text-center text-muted-foreground">
|
||||
Aguarde enquanto confirmamos seu email...
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
{status === 'success' && (
|
||||
<>
|
||||
<CheckCircle className="h-12 w-12 text-green-600" />
|
||||
<p className="text-center text-green-700 font-medium">
|
||||
{message}
|
||||
</p>
|
||||
<p className="text-center text-sm text-muted-foreground">
|
||||
Você será redirecionado automaticamente em alguns segundos...
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
{status === 'error' && (
|
||||
<>
|
||||
<XCircle className="h-12 w-12 text-red-600" />
|
||||
<p className="text-center text-red-700 font-medium">
|
||||
{message}
|
||||
</p>
|
||||
<Button onClick={handleBackToLogin} className="w-full">
|
||||
Voltar para Login
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmailConfirmation;
|
||||
Loading…
Reference in New Issue
Block a user