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 { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||||
import { Suspense, lazy } from 'react';
|
import { Suspense, lazy } from 'react';
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import Auth from './pages/Auth';
|
import Auth from './pages/Auth';
|
||||||
|
import EmailConfirmation from './pages/EmailConfirmation';
|
||||||
import ProtectedRoute from './components/auth/ProtectedRoute';
|
import ProtectedRoute from './components/auth/ProtectedRoute';
|
||||||
import { authStore } from './stores/authStore';
|
import { authStore } from './stores/authStore';
|
||||||
|
|
||||||
@ -31,6 +33,12 @@ function App() {
|
|||||||
element={isLoggedIn ? <Navigate to="/" replace /> : <Auth />}
|
element={isLoggedIn ? <Navigate to="/" replace /> : <Auth />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Email confirmation route - should be accessible without authentication */}
|
||||||
|
<Route
|
||||||
|
path="/email-confirmation"
|
||||||
|
element={<EmailConfirmation />}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Protected routes */}
|
{/* Protected routes */}
|
||||||
<Route path="/" element={
|
<Route path="/" element={
|
||||||
<ProtectedRoute>
|
<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,
|
email,
|
||||||
password: senha,
|
password: senha,
|
||||||
options: {
|
options: {
|
||||||
emailRedirectTo: `${window.location.origin}/`,
|
emailRedirectTo: `${window.location.origin}/email-confirmation`,
|
||||||
data: {
|
data: {
|
||||||
nome,
|
nome,
|
||||||
empresa: empresa || null,
|
empresa: empresa || null,
|
||||||
@ -69,7 +69,7 @@ const RegisterForm = ({ isLoading, setIsLoading }: RegisterFormProps) => {
|
|||||||
if (data.user.identities && data.user.identities.length === 0) {
|
if (data.user.identities && data.user.identities.length === 0) {
|
||||||
console.log('👤 Usuário já existe, reenviando confirmação');
|
console.log('👤 Usuário já existe, reenviando confirmação');
|
||||||
toast.info("E-mail já cadastrado. Verifique sua caixa de entrada!", {
|
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,
|
duration: 10000,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -94,11 +94,17 @@ const RegisterForm = ({ isLoading, setIsLoading }: RegisterFormProps) => {
|
|||||||
console.error('❌ Erro crítico no webhook para n8n:', error);
|
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
|
// Redirect to login page with success message
|
||||||
navigate('/auth', {
|
navigate('/auth', {
|
||||||
state: {
|
state: {
|
||||||
showSuccessMessage: true,
|
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 { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import LoginForm from '@/components/auth/LoginForm';
|
import LoginForm from '@/components/auth/LoginForm';
|
||||||
import RegisterForm from '@/components/auth/RegisterForm';
|
import RegisterForm from '@/components/auth/RegisterForm';
|
||||||
|
import SocialLoginButtons from '@/components/auth/SocialLoginButtons';
|
||||||
|
import AuthSecurityFeatures from '@/components/auth/AuthSecurityFeatures';
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
const Auth = () => {
|
const Auth = () => {
|
||||||
@ -12,10 +14,10 @@ const Auth = () => {
|
|||||||
const [activeTab, setActiveTab] = useState("login");
|
const [activeTab, setActiveTab] = useState("login");
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
// Show success message when redirected from registration
|
// Show success message when redirected from registration or email confirmation
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (location.state?.showSuccessMessage && location.state?.message) {
|
if (location.state?.showSuccessMessage && location.state?.message) {
|
||||||
toast.success("Cadastro realizado!", {
|
toast.success("Sucesso!", {
|
||||||
description: location.state.message,
|
description: location.state.message,
|
||||||
duration: 8000,
|
duration: 8000,
|
||||||
});
|
});
|
||||||
@ -40,12 +42,15 @@ const Auth = () => {
|
|||||||
<TabsTrigger value="cadastro">Cadastro</TabsTrigger>
|
<TabsTrigger value="cadastro">Cadastro</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="login">
|
<TabsContent value="login" className="space-y-4">
|
||||||
<LoginForm isLoading={isLoading} setIsLoading={setIsLoading} />
|
<LoginForm isLoading={isLoading} setIsLoading={setIsLoading} />
|
||||||
|
<SocialLoginButtons />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="cadastro">
|
<TabsContent value="cadastro" className="space-y-4">
|
||||||
<RegisterForm isLoading={isLoading} setIsLoading={setIsLoading} />
|
<RegisterForm isLoading={isLoading} setIsLoading={setIsLoading} />
|
||||||
|
<SocialLoginButtons />
|
||||||
|
<AuthSecurityFeatures />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</CardContent>
|
</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