Refactor: Implement email confirmation for password auth

This commit is contained in:
gpt-engineer-app[bot]
2025-10-14 15:39:05 +00:00
parent 92a0bf1257
commit 677f14ee7b
6 changed files with 49 additions and 26 deletions

View File

@@ -18,7 +18,7 @@ import type { OAuthProvider } from '@/types/identity';
interface PasswordSetupDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSuccess: (email?: string) => void;
onSuccess: (email?: string, needsConfirmation?: boolean) => void;
provider?: OAuthProvider;
mode?: 'standalone' | 'disconnect';
}
@@ -61,8 +61,8 @@ export function PasswordSetupDialog({
setConfirmPassword('');
if (result.needsRelogin && result.email) {
// Pass email to parent for redirect handling
onSuccess(result.email);
// Pass email and confirmation flag to parent for redirect handling
onSuccess(result.email, result.needsEmailConfirmation);
} else {
onSuccess();
}

View File

@@ -122,14 +122,22 @@ export function SecurityTab() {
}
};
const handlePasswordSetupSuccess = async (email?: string) => {
const handlePasswordSetupSuccess = (email?: string, needsConfirmation?: boolean) => {
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,
});
// 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`);
@@ -137,16 +145,15 @@ export function SecurityTab() {
// Normal password change flow (user already had email identity)
setAddingPassword(true);
try {
await loadIdentities();
loadIdentities().then(() => {
toast({
title: 'Password Updated',
description: 'Your password has been successfully updated.',
});
setPasswordSetupProvider(null);
} finally {
}).finally(() => {
setAddingPassword(false);
}
});
}
};
@@ -185,7 +192,7 @@ export function SecurityTab() {
<PasswordSetupDialog
open={!!passwordSetupProvider}
onOpenChange={(open) => !open && setPasswordSetupProvider(null)}
onSuccess={handlePasswordSetupSuccess}
onSuccess={(email, needsConfirmation) => handlePasswordSetupSuccess(email, needsConfirmation)}
provider={passwordSetupProvider}
mode={addPasswordMode}
/>

View File

@@ -213,9 +213,13 @@ export async function addPasswordToAccount(
};
}
// Step 1: Update password
console.log('[IdentityService] Setting password for user');
const { error: updateError } = await supabase.auth.updateUser({ password });
// Step 1: Update password AND trigger email confirmation
// Re-confirming the email will trigger Supabase to create the email identity
console.log('[IdentityService] Setting password and triggering email confirmation');
const { error: updateError } = await supabase.auth.updateUser({
password,
email: userEmail // Re-confirm email to create email identity provider
});
if (updateError) throw updateError;
// Step 2: Get user profile for email personalization
@@ -248,17 +252,18 @@ export async function addPasswordToAccount(
// Step 4: Log the password addition
await logIdentityChange(user!.id, 'password_added', {
method: 'oauth_with_relogin_required'
method: 'oauth_with_email_confirmation_required'
});
// Step 5: Sign the user out so they can sign back in with email/password
console.log('[IdentityService] Signing user out to force re-login');
// Step 5: Sign the user out so they can confirm email
console.log('[IdentityService] Signing user out to complete email confirmation');
await supabase.auth.signOut();
// Return success with relogin flag
// Return success with relogin and email confirmation flags
return {
success: true,
needsRelogin: true,
needsEmailConfirmation: true,
email: userEmail
};

View File

@@ -390,7 +390,7 @@ export default function Auth() {
<Alert className="mb-4">
<AlertCircle className="h-4 w-4" />
<AlertDescription>
Your password has been set. Please sign in with your email and password to complete the setup.
<strong>Password setup in progress.</strong> Check your email for a confirmation link. After confirming your email, sign in below with your email and password.
</AlertDescription>
</Alert>
)}

View File

@@ -31,5 +31,6 @@ export interface IdentityOperationResult {
success: boolean;
error?: string;
needsRelogin?: boolean;
needsEmailConfirmation?: boolean;
email?: string;
}

View File

@@ -76,12 +76,22 @@ serve(async (req) => {
</ul>
<h3>🔐 Complete Your Setup</h3>
<p>To activate your password authentication, please sign in with your new credentials:</p>
<p><strong>Important:</strong> To complete your password setup, you need to confirm your email address.</p>
<a href="${siteUrl}/auth?email=${encodeURIComponent(email)}" class="button">
Sign In Now
<ol style="padding-left: 20px; margin: 15px 0; line-height: 1.8;">
<li style="margin-bottom: 8px;">Check your inbox for a <strong>confirmation email</strong> from ThrillWiki</li>
<li style="margin-bottom: 8px;">Click the confirmation link in that email</li>
<li style="margin-bottom: 8px;">Return to the sign-in page and log in with your email and password</li>
</ol>
<a href="${siteUrl}/auth?email=${encodeURIComponent(email)}&message=complete-password-setup" class="button">
Go to Sign In Page
</a>
<p style="margin-top: 15px; font-size: 14px; color: #666;">
<strong>Note:</strong> You must confirm your email before you can sign in with your password.
</p>
<div class="security-notice">
<strong>⚠️ Security Notice</strong><br>
If you didn't add a password to your account, please contact our support team immediately at <strong>support@thrillwiki.com</strong>