From 15a4ca5e7688586033fa44d4ebae264b0d908378 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 14:56:02 +0000 Subject: [PATCH] Fix password state sync for OAuth users --- src/components/settings/SecurityTab.tsx | 48 ++++++++++++++++--------- src/lib/identityService.ts | 36 +++++++++++++++++++ 2 files changed, 67 insertions(+), 17 deletions(-) diff --git a/src/components/settings/SecurityTab.tsx b/src/components/settings/SecurityTab.tsx index dda9b028..846b6c95 100644 --- a/src/components/settings/SecurityTab.tsx +++ b/src/components/settings/SecurityTab.tsx @@ -28,6 +28,7 @@ export function SecurityTab() { const [passwordSetupProvider, setPasswordSetupProvider] = useState(null); const [hasPassword, setHasPassword] = useState(false); const [addPasswordMode, setAddPasswordMode] = useState<'standalone' | 'disconnect'>('standalone'); + const [addingPassword, setAddingPassword] = useState(false); // Load user identities on mount useEffect(() => { @@ -120,22 +121,28 @@ export function SecurityTab() { }; const handlePasswordSetupSuccess = async () => { - // Refresh identities to show email provider - await loadIdentities(); + setAddingPassword(true); - if (addPasswordMode === 'disconnect' && passwordSetupProvider) { - 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.', - }); - setPasswordSetupProvider(null); + try { + // Refresh identities - should now include email provider after waiting in addPasswordToAccount + await loadIdentities(); + + if (addPasswordMode === 'disconnect' && passwordSetupProvider) { + 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.', + }); + setPasswordSetupProvider(null); + } + } finally { + setAddingPassword(false); } }; @@ -204,8 +211,15 @@ export function SecurityTab() { Change Password ) : ( - )} diff --git a/src/lib/identityService.ts b/src/lib/identityService.ts index 7af742d1..68d69524 100644 --- a/src/lib/identityService.ts +++ b/src/lib/identityService.ts @@ -160,6 +160,29 @@ export async function connectIdentity( } } +/** + * Wait for email provider to be created after password addition + * Supabase takes time to create the email identity, so we poll with retries + */ +async function waitForEmailProvider(maxRetries = 4): Promise { + const delays = [500, 1000, 1500, 2000]; // Exponential backoff + + for (let i = 0; i < maxRetries; i++) { + const identities = await getUserIdentities(); + const hasEmail = identities.some(id => id.provider === 'email'); + + if (hasEmail) { + return true; + } + + if (i < maxRetries - 1) { + await new Promise(resolve => setTimeout(resolve, delays[i])); + } + } + + return false; +} + /** * Add password authentication to an OAuth-only account */ @@ -180,6 +203,19 @@ export async function addPasswordToAccount( if (error) throw error; + // Force session refresh to sync identity state + const { error: refreshError } = await supabase.auth.refreshSession(); + if (refreshError) { + console.warn('[IdentityService] Session refresh failed:', refreshError); + } + + // Wait for email provider to be created + const emailCreated = await waitForEmailProvider(); + + if (!emailCreated) { + console.warn('[IdentityService] Email provider not found after password addition'); + } + // Log audit event const { data: { user } } = await supabase.auth.getUser(); if (user) {