diff --git a/src/components/settings/SecurityTab.tsx b/src/components/settings/SecurityTab.tsx index 7efd12f8..5413c482 100644 --- a/src/components/settings/SecurityTab.tsx +++ b/src/components/settings/SecurityTab.tsx @@ -223,9 +223,16 @@ export function SecurityTab() { {hasPassword ? ( - <>Update your password to keep your account secure. {user?.identities?.some(i => i.provider === 'totp') && 'Two-factor authentication will be required.'} + <>Update your password to keep your account secure. ) : ( - 'Add password authentication to your account for increased security and backup access.' + <> + Add password authentication to your account for increased security and backup access. + {identities.length > 0 && ( + + If you've previously set a password but don't see it here, click "Add Password" to re-verify your authentication. + + )} + )} diff --git a/src/lib/identityService.ts b/src/lib/identityService.ts index 4fc09fec..f9bc6e62 100644 --- a/src/lib/identityService.ts +++ b/src/lib/identityService.ts @@ -189,6 +189,7 @@ async function waitForEmailProvider(maxRetries = 6): Promise { /** * Add password authentication to an OAuth-only account + * Also handles re-creating email identity for orphaned passwords */ export async function addPasswordToAccount( password: string @@ -202,7 +203,7 @@ export async function addPasswordToAccount( }; } - // Update user with password + // Update user with password (works for both new and existing passwords) const { error } = await supabase.auth.updateUser({ password }); if (error) throw error; @@ -217,6 +218,34 @@ export async function addPasswordToAccount( const emailCreated = await waitForEmailProvider(); if (!emailCreated) { + // Password was set but identity verification failed + // Try one more aggressive approach: sign in with the new password + console.log('[IdentityService] Attempting sign-in to trigger identity creation'); + + const { data: { user } } = await supabase.auth.getUser(); + if (user?.email) { + // Attempt to sign in (this might create the identity) + const { error: signInError } = await supabase.auth.signInWithPassword({ + email: user.email, + password: password + }); + + if (!signInError) { + // Sign-in successful, check identities again + const retriedEmailCreated = await waitForEmailProvider(2); // Quick retry + if (retriedEmailCreated) { + console.log('[IdentityService] Email provider created after sign-in'); + + // Log audit event + await logIdentityChange(user.id, 'password_added', { + method: 'oauth_fallback_with_signin_retry' + }); + + return { success: true }; + } + } + } + return { success: false, error: 'Password was set but email provider verification failed. Please refresh the page and try signing in with your email and password.' @@ -241,6 +270,54 @@ export async function addPasswordToAccount( } } +/** + * Check if user has an orphaned password (password exists but no email identity) + */ +export async function hasOrphanedPassword(): Promise { + const identities = await getUserIdentities(); + const hasEmailIdentity = identities.some(i => i.provider === 'email'); + + if (hasEmailIdentity) return false; + + // If user has OAuth identities but no email identity, they might have an orphaned password + return identities.length > 0 && !hasEmailIdentity; +} + +/** + * Re-verify password authentication by attempting sign-in + * This forces Supabase to create the email identity if it's missing + */ +export async function reverifyPasswordAuth( + email: string, + password: string +): Promise { + try { + const { error } = await supabase.auth.signInWithPassword({ + email, + password + }); + + if (error) throw error; + + // Check if email identity was created + const emailCreated = await waitForEmailProvider(3); + + if (!emailCreated) { + return { + success: false, + error: 'Sign-in successful but identity verification failed. Please contact support.' + }; + } + + return { success: true }; + } catch (error: any) { + return { + success: false, + error: error.message || 'Failed to verify password authentication' + }; + } +} + /** * Log identity changes to audit log */