import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Separator } from '@/components/ui/separator'; import { Badge } from '@/components/ui/badge'; import { useToast } from '@/hooks/use-toast'; import { useAuth } from '@/hooks/useAuth'; import { Shield, Key, Smartphone, Globe, Loader2 } from 'lucide-react'; import { TOTPSetup } from '@/components/auth/TOTPSetup'; import { GoogleIcon } from '@/components/icons/GoogleIcon'; import { DiscordIcon } from '@/components/icons/DiscordIcon'; import { PasswordUpdateDialog } from './PasswordUpdateDialog'; import { PasswordSetupDialog } from '@/components/auth/PasswordSetupDialog'; import { getUserIdentities, checkDisconnectSafety, disconnectIdentity, connectIdentity, hasOrphanedPassword, triggerOrphanedPasswordConfirmation } from '@/lib/identityService'; import type { UserIdentity, OAuthProvider } from '@/types/identity'; import { toast as sonnerToast } from 'sonner'; export function SecurityTab() { const { user } = useAuth(); const { toast } = useToast(); const navigate = useNavigate(); const [passwordDialogOpen, setPasswordDialogOpen] = useState(false); const [identities, setIdentities] = useState([]); const [loadingIdentities, setLoadingIdentities] = useState(true); const [disconnectingProvider, setDisconnectingProvider] = useState(null); const [passwordSetupProvider, setPasswordSetupProvider] = useState(null); const [hasPassword, setHasPassword] = useState(false); const [addPasswordMode, setAddPasswordMode] = useState<'standalone' | 'disconnect'>('standalone'); const [addingPassword, setAddingPassword] = useState(false); const [showOrphanedPasswordOption, setShowOrphanedPasswordOption] = useState(false); // Load user identities on mount useEffect(() => { loadIdentities(); }, []); useEffect(() => { const checkOrphanedPassword = async () => { if (!hasPassword) { const isOrphaned = await hasOrphanedPassword(); setShowOrphanedPasswordOption(isOrphaned); if (isOrphaned) { sonnerToast.info("Password Authentication Needs Activation", { description: "Click 'Send Confirmation Email' below to complete your password setup.", duration: 10000, }); } if (isOrphaned) { sonnerToast.info("Password Authentication Needs Activation", { description: "Click 'Verify Password Access' to complete your password setup.", duration: 10000, }); } } }; if (!loadingIdentities) { checkOrphanedPassword(); } }, [hasPassword, loadingIdentities]); const loadIdentities = async () => { try { setLoadingIdentities(true); const fetchedIdentities = await getUserIdentities(); setIdentities(fetchedIdentities); // Check if user has email/password auth const hasEmailProvider = fetchedIdentities.some(i => i.provider === 'email'); setHasPassword(hasEmailProvider); } catch (error) { console.error('Failed to load identities:', error); toast({ title: 'Error', description: 'Failed to load connected accounts', variant: 'destructive', }); } finally { setLoadingIdentities(false); } }; const handleSocialLogin = async (provider: OAuthProvider) => { const result = await connectIdentity(provider, '/settings?tab=security'); if (!result.success) { toast({ title: 'Connection Failed', description: result.error, variant: 'destructive' }); } else { toast({ title: 'Redirecting...', description: `Connecting your ${provider} account...` }); } }; const handleUnlinkSocial = async (provider: OAuthProvider) => { // Check if disconnect is safe const safetyCheck = await checkDisconnectSafety(provider); if (!safetyCheck.canDisconnect) { if (safetyCheck.reason === 'no_password_backup') { // Show password setup dialog setAddPasswordMode('disconnect'); setPasswordSetupProvider(provider); toast({ title: "Password Required", description: "Set a password before disconnecting your last social login to maintain account access.", variant: "default" }); return; } if (safetyCheck.reason === 'last_identity') { toast({ title: "Cannot Disconnect", description: "You cannot disconnect your only login method. Please add another authentication method first.", variant: "destructive" }); return; } } // Proceed with disconnect setDisconnectingProvider(provider); const result = await disconnectIdentity(provider); setDisconnectingProvider(null); if (result.success) { await loadIdentities(); // Refresh identities list toast({ title: "Disconnected", description: `Your ${provider} account has been successfully disconnected.` }); } else { toast({ title: "Disconnect Failed", description: result.error, variant: "destructive" }); } }; const handlePasswordSetupSuccess = (email?: string, needsConfirmation?: boolean) => { if (email) { // Password was set, user was logged out, needs to confirm email if (needsConfirmation) { toast({ title: 'Check Your Email', description: 'A confirmation link has been sent to your email. Click it to activate password authentication, then sign in.', duration: 10000, }); } else { toast({ title: 'Password Set Successfully', description: 'Please sign in with your email and password to complete setup.', duration: 6000, }); } // Redirect to auth page with email pre-filled navigate(`/auth?email=${encodeURIComponent(email)}&message=complete-password-setup`); } else { // Normal password change flow (user already had email identity) setAddingPassword(true); loadIdentities().then(() => { toast({ title: 'Password Updated', description: 'Your password has been successfully updated.', }); setPasswordSetupProvider(null); }).finally(() => { setAddingPassword(false); }); } }; const handleAddPassword = () => { setAddPasswordMode('standalone'); setPasswordSetupProvider('google' as OAuthProvider); }; const handleSendConfirmationEmail = async () => { setAddingPassword(true); const result = await triggerOrphanedPasswordConfirmation(); if (result.success) { sonnerToast.success("Confirmation Email Sent!", { description: "Check your email for a confirmation link to activate your password authentication.", duration: 15000, }); } else { toast({ title: "Failed to Send Email", description: result.error, variant: "destructive" }); } setAddingPassword(false); }; // Get connected accounts with identity data const connectedAccounts = [ { provider: 'google' as OAuthProvider, identity: identities.find(i => i.provider === 'google'), icon: }, { provider: 'discord' as OAuthProvider, identity: identities.find(i => i.provider === 'discord'), icon: } ]; return ( <> { toast({ title: 'Password updated', description: 'Your password has been successfully changed.' }); }} /> {passwordSetupProvider && ( !open && setPasswordSetupProvider(null)} onSuccess={(email, needsConfirmation) => handlePasswordSetupSuccess(email, needsConfirmation)} provider={passwordSetupProvider} mode={addPasswordMode} /> )}
{/* Password Section - Conditional based on auth method */}

{hasPassword ? 'Change Password' : 'Add Password'}

{hasPassword ? ( <>Update your password to keep your account secure. ) : ( <>Add password authentication to your account for increased security and backup access. )} {hasPassword ? ( ) : ( <> {showOrphanedPasswordOption && ( )} )}
{/* Social Login Connections */}

Connected Accounts

Manage your social login connections for easier access to your account. To enable social providers, configure them in your Supabase Auth settings. {loadingIdentities ? (
) : ( connectedAccounts.map(account => { const isConnected = !!account.identity; const isDisconnecting = disconnectingProvider === account.provider; const email = account.identity?.identity_data?.email; return (
{account.icon}

{account.provider}

{isConnected && email && (

{email}

)}
{isConnected ? ( <> Connected ) : ( )}
); }) )}
{/* Two-Factor Authentication */}
{/* Two-Factor Authentication */}

Two-Factor Authentication

{/* Login History */}

Login History

Review your recent login activity and active sessions.

Current Session

Web • {new Date().toLocaleDateString()}

Active

No other recent sessions found

); }