diff --git a/src/components/auth/PasswordSetupDialog.tsx b/src/components/auth/PasswordSetupDialog.tsx index accdba40..f4022608 100644 --- a/src/components/auth/PasswordSetupDialog.tsx +++ b/src/components/auth/PasswordSetupDialog.tsx @@ -18,7 +18,7 @@ import type { OAuthProvider } from '@/types/identity'; interface PasswordSetupDialogProps { open: boolean; onOpenChange: (open: boolean) => void; - onSuccess: () => void; + onSuccess: (email?: string) => void; provider?: OAuthProvider; mode?: 'standalone' | 'disconnect'; } @@ -59,7 +59,13 @@ export function PasswordSetupDialog({ if (result.success) { setPassword(''); setConfirmPassword(''); - onSuccess(); + + if (result.needsRelogin && result.email) { + // Pass email to parent for redirect handling + onSuccess(result.email); + } else { + onSuccess(); + } onOpenChange(false); } else { setError(result.error || 'Failed to set password'); diff --git a/src/components/settings/SecurityTab.tsx b/src/components/settings/SecurityTab.tsx index 154ecb4d..82c70d85 100644 --- a/src/components/settings/SecurityTab.tsx +++ b/src/components/settings/SecurityTab.tsx @@ -1,11 +1,12 @@ 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, RefreshCw } from 'lucide-react'; +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'; @@ -21,6 +22,7 @@ import type { UserIdentity, OAuthProvider } from '@/types/identity'; 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); @@ -120,56 +122,34 @@ export function SecurityTab() { } }; - const handlePasswordSetupSuccess = async () => { - setAddingPassword(true); - - try { - // Refresh identities - should now include email provider after waiting in addPasswordToAccount - await loadIdentities(); + const handlePasswordSetupSuccess = async (email?: string) => { + if (email) { + // Password was set, user was logged out, needs to sign in with email/password + toast({ + title: 'Password Set Successfully', + description: 'Please sign in with your email and password to complete setup.', + duration: 6000, + }); - // Check if password was actually verified - if (!hasPassword && addPasswordMode === 'standalone') { - // Password addition failed verification - toast({ - title: 'Verification Failed', - description: 'Password was set but identity verification failed. Please refresh the page and check your Security settings.', - variant: 'destructive' - }); - return; // Don't close dialog or clear provider - } + // 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); - if (addPasswordMode === 'disconnect' && passwordSetupProvider) { + try { + await loadIdentities(); toast({ - title: "Password Set", - description: "You can now disconnect your social login." - }); - await handleUnlinkSocial(passwordSetupProvider); - setPasswordSetupProvider(null); - } else { - toast({ - title: 'Password Added', - description: 'You can now sign in using your email and password.', + title: 'Password Updated', + description: 'Your password has been successfully updated.', }); setPasswordSetupProvider(null); + } finally { + setAddingPassword(false); } - } finally { - setAddingPassword(false); } }; - const handleRefreshIdentities = async () => { - setLoadingIdentities(true); - await loadIdentities(); - setLoadingIdentities(false); - - toast({ - title: 'Identities Refreshed', - description: hasPassword - ? 'Your password authentication is now active.' - : 'Identity status updated.', - }); - }; - const handleAddPassword = () => { setAddPasswordMode('standalone'); setPasswordSetupProvider('google' as OAuthProvider); @@ -230,33 +210,22 @@ export function SecurityTab() { -
- {hasPassword ? ( - - ) : ( - - )} - -
+ ) : ( + + )}
diff --git a/src/lib/identityService.ts b/src/lib/identityService.ts index 6faa8e05..e5ea27b0 100644 --- a/src/lib/identityService.ts +++ b/src/lib/identityService.ts @@ -204,7 +204,9 @@ export async function addPasswordToAccount( } const { data: { user } } = await supabase.auth.getUser(); - if (!user?.email) { + const userEmail = user?.email; + + if (!userEmail) { return { success: false, error: 'No email address found on your account' @@ -216,56 +218,21 @@ export async function addPasswordToAccount( const { error: updateError } = await supabase.auth.updateUser({ password }); if (updateError) throw updateError; - // Step 2: IMMEDIATELY attempt sign-in to force identity creation - // This is the ONLY reliable way to create the email identity - console.log('[IdentityService] Attempting sign-in to create email identity'); - const { error: signInError } = await supabase.auth.signInWithPassword({ - email: user.email, - password: password + // Step 2: Log the password addition + await logIdentityChange(user!.id, 'password_added', { + method: 'oauth_with_relogin_required' }); - if (signInError) { - // Sign-in failed, but password was set - console.error('[IdentityService] Sign-in failed:', signInError); - - // Check if it's just an email confirmation issue - if (signInError.message?.includes('Email not confirmed')) { - // Password is set, identity might be created, just needs confirmation - console.log('[IdentityService] Email confirmation required, checking identity'); - const emailCreated = await waitForEmailProvider(3); - if (emailCreated) { - await logIdentityChange(user.id, 'password_added', { - method: 'oauth_fallback_unconfirmed' - }); - return { success: true }; - } - } - - return { - success: false, - error: `Password was set but authentication failed: ${signInError.message}. Please try signing out and signing back in with your email and password.` - }; - } + // Step 3: Sign the user out so they can sign back in with email/password + console.log('[IdentityService] Signing user out to force re-login'); + await supabase.auth.signOut(); - // Step 3: Verify identity was created - console.log('[IdentityService] Sign-in successful, verifying identity creation'); - const emailCreated = await waitForEmailProvider(4); - - if (!emailCreated) { - console.error('[IdentityService] Identity not found after successful sign-in'); - return { - success: false, - error: 'Password authentication was successful but identity verification failed. Please refresh the page.' - }; - } - - // Step 4: Log success - console.log('[IdentityService] Email identity successfully created'); - await logIdentityChange(user.id, 'password_added', { - method: 'oauth_fallback_signin' - }); - - return { success: true }; + // Return success with relogin flag + return { + success: true, + needsRelogin: true, + email: userEmail + }; } catch (error: any) { console.error('[IdentityService] Failed to add password:', error); diff --git a/src/pages/Auth.tsx b/src/pages/Auth.tsx index 8ce3831d..8acb7df2 100644 --- a/src/pages/Auth.tsx +++ b/src/pages/Auth.tsx @@ -33,8 +33,13 @@ export default function Auth() { const [signInCaptchaToken, setSignInCaptchaToken] = useState(null); const [signInCaptchaKey, setSignInCaptchaKey] = useState(0); const [mfaFactorId, setMfaFactorId] = useState(null); + + const emailParam = searchParams.get('email'); + const messageParam = searchParams.get('message'); + const showPasswordSetupMessage = messageParam === 'complete-password-setup'; + const [formData, setFormData] = useState({ - email: '', + email: emailParam || '', password: '', confirmPassword: '', username: '', @@ -43,6 +48,13 @@ export default function Auth() { const defaultTab = searchParams.get('tab') || 'signin'; const { user } = useAuth(); + // Pre-fill email from query param + useEffect(() => { + if (emailParam) { + setFormData(prev => ({ ...prev, email: emailParam })); + } + }, [emailParam]); + // Auto-redirect when user is authenticated useEffect(() => { if (user) { @@ -374,6 +386,15 @@ export default function Auth() { + {showPasswordSetupMessage && ( + + + + Your password has been set. Please sign in with your email and password to complete the setup. + + + )} + {mfaFactorId ? (