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

View File

@@ -122,14 +122,22 @@ export function SecurityTab() {
} }
}; };
const handlePasswordSetupSuccess = async (email?: string) => { const handlePasswordSetupSuccess = (email?: string, needsConfirmation?: boolean) => {
if (email) { if (email) {
// Password was set, user was logged out, needs to sign in with email/password // Password was set, user was logged out, needs to confirm email
toast({ if (needsConfirmation) {
title: 'Password Set Successfully', toast({
description: 'Please sign in with your email and password to complete setup.', title: 'Check Your Email',
duration: 6000, 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 // Redirect to auth page with email pre-filled
navigate(`/auth?email=${encodeURIComponent(email)}&message=complete-password-setup`); 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) // Normal password change flow (user already had email identity)
setAddingPassword(true); setAddingPassword(true);
try { loadIdentities().then(() => {
await loadIdentities();
toast({ toast({
title: 'Password Updated', title: 'Password Updated',
description: 'Your password has been successfully updated.', description: 'Your password has been successfully updated.',
}); });
setPasswordSetupProvider(null); setPasswordSetupProvider(null);
} finally { }).finally(() => {
setAddingPassword(false); setAddingPassword(false);
} });
} }
}; };
@@ -185,7 +192,7 @@ export function SecurityTab() {
<PasswordSetupDialog <PasswordSetupDialog
open={!!passwordSetupProvider} open={!!passwordSetupProvider}
onOpenChange={(open) => !open && setPasswordSetupProvider(null)} onOpenChange={(open) => !open && setPasswordSetupProvider(null)}
onSuccess={handlePasswordSetupSuccess} onSuccess={(email, needsConfirmation) => handlePasswordSetupSuccess(email, needsConfirmation)}
provider={passwordSetupProvider} provider={passwordSetupProvider}
mode={addPasswordMode} mode={addPasswordMode}
/> />

View File

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

View File

@@ -390,7 +390,7 @@ export default function Auth() {
<Alert className="mb-4"> <Alert className="mb-4">
<AlertCircle className="h-4 w-4" /> <AlertCircle className="h-4 w-4" />
<AlertDescription> <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> </AlertDescription>
</Alert> </Alert>
)} )}

View File

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

View File

@@ -76,12 +76,22 @@ serve(async (req) => {
</ul> </ul>
<h3>🔐 Complete Your Setup</h3> <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"> <ol style="padding-left: 20px; margin: 15px 0; line-height: 1.8;">
Sign In Now <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> </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"> <div class="security-notice">
<strong>⚠️ Security Notice</strong><br> <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> If you didn't add a password to your account, please contact our support team immediately at <strong>support@thrillwiki.com</strong>