mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 17:31:12 -05:00
Refactor identity management
This commit is contained in:
@@ -160,49 +160,13 @@ 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 = 6): Promise<boolean> {
|
||||
const delays = [500, 1000, 1500, 2000, 2500, 3000]; // ~10.5s total
|
||||
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
const identities = await getUserIdentities();
|
||||
const hasEmail = identities.some(id => id.provider === 'email');
|
||||
|
||||
if (hasEmail) {
|
||||
console.log(`[IdentityService] Email provider found after ${i + 1} attempts`);
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log(`[IdentityService] Email provider not found, attempt ${i + 1}/${maxRetries}`);
|
||||
|
||||
if (i < maxRetries - 1) {
|
||||
await new Promise(resolve => setTimeout(resolve, delays[i]));
|
||||
}
|
||||
}
|
||||
|
||||
console.error('[IdentityService] Email provider not found after max retries');
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add password authentication to an OAuth-only account
|
||||
* Automatically creates email identity by signing in immediately after setting password
|
||||
* Triggers Supabase password reset flow - user sets password via email link
|
||||
*/
|
||||
export async function addPasswordToAccount(
|
||||
password: string
|
||||
): Promise<IdentityOperationResult> {
|
||||
export async function addPasswordToAccount(): Promise<IdentityOperationResult> {
|
||||
try {
|
||||
// Validate password strength
|
||||
if (password.length < 8) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Password must be at least 8 characters long'
|
||||
};
|
||||
}
|
||||
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
const userEmail = user?.email;
|
||||
|
||||
@@ -213,61 +177,30 @@ export async function addPasswordToAccount(
|
||||
};
|
||||
}
|
||||
|
||||
console.log('[IdentityService] Initiating password setup via reset flow');
|
||||
console.log('[IdentityService] Sending password reset email');
|
||||
|
||||
// Step 1: Store the desired password temporarily in session storage
|
||||
// This will be used after clicking the reset link
|
||||
sessionStorage.setItem('pending_password_setup', password);
|
||||
console.log('[IdentityService] Stored pending password in session storage');
|
||||
|
||||
// Step 2: Trigger Supabase password reset email
|
||||
// This creates the email identity when user clicks link and sets password
|
||||
// Trigger Supabase password reset email
|
||||
// User clicks link and sets password, which automatically creates email identity
|
||||
const { error: resetError } = await supabase.auth.resetPasswordForEmail(
|
||||
userEmail,
|
||||
{
|
||||
redirectTo: `${window.location.origin}/auth/callback?action=password-setup`
|
||||
redirectTo: `${window.location.origin}/auth/callback?type=recovery`
|
||||
}
|
||||
);
|
||||
|
||||
if (resetError) {
|
||||
console.error('[IdentityService] Failed to send password reset email:', resetError);
|
||||
sessionStorage.removeItem('pending_password_setup'); // Cleanup on failure
|
||||
throw resetError;
|
||||
}
|
||||
|
||||
console.log('[IdentityService] Password reset email sent successfully');
|
||||
|
||||
// Step 3: Get user profile for custom notification email
|
||||
const { data: profile } = await supabase
|
||||
.from('profiles')
|
||||
.select('display_name, username')
|
||||
.eq('user_id', user!.id)
|
||||
.single();
|
||||
|
||||
// Step 4: Send custom "Password Setup Instructions" email (informational)
|
||||
console.log('[IdentityService] Sending custom notification email');
|
||||
try {
|
||||
await supabase.functions.invoke('send-password-added-email', {
|
||||
body: {
|
||||
email: userEmail,
|
||||
displayName: profile?.display_name,
|
||||
username: profile?.username,
|
||||
},
|
||||
});
|
||||
console.log('[IdentityService] Custom notification email sent');
|
||||
} catch (emailError) {
|
||||
console.error('[IdentityService] Custom email failed (non-blocking):', emailError);
|
||||
// Don't fail the whole operation
|
||||
}
|
||||
|
||||
// Step 5: Log the action
|
||||
// Log the action
|
||||
await logIdentityChange(user!.id, 'password_setup_initiated', {
|
||||
method: 'reset_password_flow',
|
||||
reset_email_sent: true,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// Return success - user needs to check email and click reset link
|
||||
return {
|
||||
success: true,
|
||||
needsEmailConfirmation: true,
|
||||
@@ -275,98 +208,6 @@ export async function addPasswordToAccount(
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('[IdentityService] Failed to initiate password setup:', error);
|
||||
// Cleanup on error
|
||||
sessionStorage.removeItem('pending_password_setup');
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || 'Failed to initiate password setup'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has an orphaned password (password exists but no email identity)
|
||||
*/
|
||||
export async function hasOrphanedPassword(): Promise<boolean> {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger email confirmation for orphaned password
|
||||
* Direct trigger without requiring password re-entry
|
||||
*/
|
||||
export async function triggerOrphanedPasswordConfirmation(
|
||||
source?: string
|
||||
): Promise<IdentityOperationResult> {
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
|
||||
if (!user?.email) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'No email found for current user'
|
||||
};
|
||||
}
|
||||
|
||||
console.log('[IdentityService] Resending password reset email for orphaned password');
|
||||
|
||||
// Send Supabase password reset email
|
||||
// The user's password is already set in auth.users, they just need to click
|
||||
// the reset link to create the email identity
|
||||
const { error: resetError } = await supabase.auth.resetPasswordForEmail(
|
||||
user.email,
|
||||
{
|
||||
redirectTo: `${window.location.origin}/auth/callback?action=confirm-password`
|
||||
}
|
||||
);
|
||||
|
||||
if (resetError) {
|
||||
console.error('[IdentityService] Failed to send password reset email:', resetError);
|
||||
throw resetError;
|
||||
}
|
||||
|
||||
console.log('[IdentityService] Password reset email sent successfully');
|
||||
|
||||
// Optional: Get profile for custom notification
|
||||
const { data: profile } = await supabase
|
||||
.from('profiles')
|
||||
.select('display_name, username')
|
||||
.eq('user_id', user.id)
|
||||
.single();
|
||||
|
||||
// Optional: Send custom notification email (non-blocking)
|
||||
try {
|
||||
await supabase.functions.invoke('send-password-added-email', {
|
||||
body: {
|
||||
email: user.email,
|
||||
displayName: profile?.display_name,
|
||||
username: profile?.username,
|
||||
},
|
||||
});
|
||||
} catch (emailError) {
|
||||
console.error('[IdentityService] Custom email failed (non-blocking):', emailError);
|
||||
}
|
||||
|
||||
// Log the action
|
||||
await logIdentityChange(user.id, 'orphaned_password_confirmation_triggered', {
|
||||
method: source || 'manual_button_click',
|
||||
timestamp: new Date().toISOString(),
|
||||
reset_email_sent: true
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
needsEmailConfirmation: true,
|
||||
email: user.email
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('[IdentityService] Failed to trigger password reset:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || 'Failed to send password reset email'
|
||||
@@ -374,6 +215,7 @@ export async function triggerOrphanedPasswordConfirmation(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log identity changes to audit log
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user