mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 14:51:13 -05:00
Refactor identity management
This commit is contained in:
@@ -16,8 +16,7 @@ import {
|
|||||||
checkDisconnectSafety,
|
checkDisconnectSafety,
|
||||||
disconnectIdentity,
|
disconnectIdentity,
|
||||||
connectIdentity,
|
connectIdentity,
|
||||||
hasOrphanedPassword,
|
addPasswordToAccount
|
||||||
triggerOrphanedPasswordConfirmation
|
|
||||||
} from '@/lib/identityService';
|
} from '@/lib/identityService';
|
||||||
import type { UserIdentity, OAuthProvider } from '@/types/identity';
|
import type { UserIdentity, OAuthProvider } from '@/types/identity';
|
||||||
import { toast as sonnerToast } from '@/components/ui/sonner';
|
import { toast as sonnerToast } from '@/components/ui/sonner';
|
||||||
@@ -33,26 +32,12 @@ export function SecurityTab() {
|
|||||||
const [disconnectingProvider, setDisconnectingProvider] = useState<OAuthProvider | null>(null);
|
const [disconnectingProvider, setDisconnectingProvider] = useState<OAuthProvider | null>(null);
|
||||||
const [hasPassword, setHasPassword] = useState(false);
|
const [hasPassword, setHasPassword] = useState(false);
|
||||||
const [addingPassword, setAddingPassword] = useState(false);
|
const [addingPassword, setAddingPassword] = useState(false);
|
||||||
const [showOrphanedPasswordOption, setShowOrphanedPasswordOption] = useState(false);
|
|
||||||
|
|
||||||
// Load user identities on mount
|
// Load user identities on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadIdentities();
|
loadIdentities();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const checkOrphanedPassword = async () => {
|
|
||||||
if (!hasPassword) {
|
|
||||||
const isOrphaned = await hasOrphanedPassword();
|
|
||||||
setShowOrphanedPasswordOption(isOrphaned);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!loadingIdentities) {
|
|
||||||
checkOrphanedPassword();
|
|
||||||
}
|
|
||||||
}, [hasPassword, loadingIdentities]);
|
|
||||||
|
|
||||||
const loadIdentities = async () => {
|
const loadIdentities = async () => {
|
||||||
try {
|
try {
|
||||||
setLoadingIdentities(true);
|
setLoadingIdentities(true);
|
||||||
@@ -140,50 +125,11 @@ export function SecurityTab() {
|
|||||||
const handleAddPassword = async () => {
|
const handleAddPassword = async () => {
|
||||||
setAddingPassword(true);
|
setAddingPassword(true);
|
||||||
|
|
||||||
const { data: { user } } = await supabase.auth.getUser();
|
const result = await addPasswordToAccount();
|
||||||
|
|
||||||
if (!user?.email) {
|
|
||||||
toast({
|
|
||||||
title: "No Email Found",
|
|
||||||
description: "Your account doesn't have an email address associated with it.",
|
|
||||||
variant: "destructive"
|
|
||||||
});
|
|
||||||
setAddingPassword(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger password reset email directly (no modal needed!)
|
|
||||||
const { error } = await supabase.auth.resetPasswordForEmail(
|
|
||||||
user.email,
|
|
||||||
{
|
|
||||||
redirectTo: `${window.location.origin}/auth/callback?action=password-setup-direct`
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
toast({
|
|
||||||
title: "Failed to Send Email",
|
|
||||||
description: error.message,
|
|
||||||
variant: "destructive"
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
sonnerToast.success("Password Reset Email Sent!", {
|
|
||||||
description: "Check your email for a password reset link. Click it to set your password on ThrillWiki.",
|
|
||||||
duration: 15000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setAddingPassword(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSendConfirmationEmail = async () => {
|
|
||||||
setAddingPassword(true);
|
|
||||||
|
|
||||||
const result = await triggerOrphanedPasswordConfirmation('security_settings');
|
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
sonnerToast.success("Reset Email Sent!", {
|
sonnerToast.success("Password Setup Email Sent!", {
|
||||||
description: "Check your email for a password reset link from Supabase to activate your password authentication. You'll also receive a notification email from ThrillWiki.",
|
description: `Check ${result.email} for a password reset link. Click it to set your password.`,
|
||||||
duration: 15000,
|
duration: 15000,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -247,7 +193,6 @@ export function SecurityTab() {
|
|||||||
Change Password
|
Change Password
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<>
|
|
||||||
<Button onClick={handleAddPassword} disabled={addingPassword}>
|
<Button onClick={handleAddPassword} disabled={addingPassword}>
|
||||||
{addingPassword ? (
|
{addingPassword ? (
|
||||||
<>
|
<>
|
||||||
@@ -258,28 +203,6 @@ export function SecurityTab() {
|
|||||||
'Add Password'
|
'Add Password'
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{showOrphanedPasswordOption && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={handleSendConfirmationEmail}
|
|
||||||
disabled={addingPassword}
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
{addingPassword ? (
|
|
||||||
<>
|
|
||||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
||||||
Sending Email...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Key className="w-4 h-4 mr-2" />
|
|
||||||
Send Confirmation Email
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -113,74 +113,6 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
|
|||||||
} else {
|
} else {
|
||||||
setAal(null);
|
setAal(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for orphaned password on SIGNED_IN events
|
|
||||||
if (event === 'SIGNED_IN' && session?.user) {
|
|
||||||
try {
|
|
||||||
// Import sessionFlags
|
|
||||||
const { isOrphanedPasswordDismissed, setOrphanedPasswordDismissed } =
|
|
||||||
await import('@/lib/sessionFlags');
|
|
||||||
|
|
||||||
// Skip if already shown in this auth cycle or dismissed this session
|
|
||||||
if (orphanedPasswordToastShownRef.current || isOrphanedPasswordDismissed()) {
|
|
||||||
authLog('[Auth] Skipping orphaned password toast - already shown or dismissed');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import identityService functions
|
|
||||||
const { getUserIdentities, hasOrphanedPassword, triggerOrphanedPasswordConfirmation } =
|
|
||||||
await import('@/lib/identityService');
|
|
||||||
|
|
||||||
// Check if user has email identity
|
|
||||||
const identities = await getUserIdentities();
|
|
||||||
const hasEmailIdentity = identities.some(i => i.provider === 'email');
|
|
||||||
|
|
||||||
// If no email identity but has other identities, check for orphaned password
|
|
||||||
if (!hasEmailIdentity && identities.length > 0) {
|
|
||||||
const isOrphaned = await hasOrphanedPassword();
|
|
||||||
|
|
||||||
if (isOrphaned) {
|
|
||||||
// Mark as shown to prevent duplicates
|
|
||||||
orphanedPasswordToastShownRef.current = true;
|
|
||||||
|
|
||||||
// Show persistent toast with Resend button
|
|
||||||
const { toast: sonnerToast } = await import('sonner');
|
|
||||||
|
|
||||||
sonnerToast.warning("Password Activation Pending", {
|
|
||||||
description: "Check your email for a password reset link to complete activation. You'll receive two emails: one from Supabase with the reset link, and one from ThrillWiki with instructions.",
|
|
||||||
duration: Infinity,
|
|
||||||
action: {
|
|
||||||
label: "Resend Email",
|
|
||||||
onClick: async () => {
|
|
||||||
const result = await triggerOrphanedPasswordConfirmation('signin_toast');
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
sonnerToast.success("Reset Email Sent!", {
|
|
||||||
description: `Check ${result.email} for the password reset link from Supabase.`,
|
|
||||||
duration: 10000,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
sonnerToast.error("Failed to Send Email", {
|
|
||||||
description: result.error,
|
|
||||||
duration: 8000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cancel: {
|
|
||||||
label: "Dismiss",
|
|
||||||
onClick: () => {
|
|
||||||
setOrphanedPasswordDismissed();
|
|
||||||
authLog('[Auth] User dismissed orphaned password warning');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
authError('[Auth] Failed to check for orphaned password:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
// Detect confirmed email change: email changed AND no longer pending
|
// Detect confirmed email change: email changed AND no longer pending
|
||||||
|
|||||||
@@ -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
|
* 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(
|
export async function addPasswordToAccount(): Promise<IdentityOperationResult> {
|
||||||
password: string
|
|
||||||
): Promise<IdentityOperationResult> {
|
|
||||||
try {
|
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 { data: { user } } = await supabase.auth.getUser();
|
||||||
const userEmail = user?.email;
|
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
|
// Trigger Supabase password reset email
|
||||||
// This will be used after clicking the reset link
|
// User clicks link and sets password, which automatically creates email identity
|
||||||
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
|
|
||||||
const { error: resetError } = await supabase.auth.resetPasswordForEmail(
|
const { error: resetError } = await supabase.auth.resetPasswordForEmail(
|
||||||
userEmail,
|
userEmail,
|
||||||
{
|
{
|
||||||
redirectTo: `${window.location.origin}/auth/callback?action=password-setup`
|
redirectTo: `${window.location.origin}/auth/callback?type=recovery`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (resetError) {
|
if (resetError) {
|
||||||
console.error('[IdentityService] Failed to send password reset email:', resetError);
|
console.error('[IdentityService] Failed to send password reset email:', resetError);
|
||||||
sessionStorage.removeItem('pending_password_setup'); // Cleanup on failure
|
|
||||||
throw resetError;
|
throw resetError;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[IdentityService] Password reset email sent successfully');
|
console.log('[IdentityService] Password reset email sent successfully');
|
||||||
|
|
||||||
// Step 3: Get user profile for custom notification email
|
// Log the action
|
||||||
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
|
|
||||||
await logIdentityChange(user!.id, 'password_setup_initiated', {
|
await logIdentityChange(user!.id, 'password_setup_initiated', {
|
||||||
method: 'reset_password_flow',
|
method: 'reset_password_flow',
|
||||||
reset_email_sent: true,
|
|
||||||
timestamp: new Date().toISOString()
|
timestamp: new Date().toISOString()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Return success - user needs to check email and click reset link
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
needsEmailConfirmation: true,
|
needsEmailConfirmation: true,
|
||||||
@@ -275,98 +208,6 @@ export async function addPasswordToAccount(
|
|||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[IdentityService] Failed to initiate password setup:', error);
|
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 {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: error.message || 'Failed to send password reset email'
|
error: error.message || 'Failed to send password reset email'
|
||||||
@@ -374,6 +215,7 @@ export async function triggerOrphanedPasswordConfirmation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log identity changes to audit log
|
* Log identity changes to audit log
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ export const SessionFlags = {
|
|||||||
MFA_INTENDED_PATH: 'mfa_intended_path',
|
MFA_INTENDED_PATH: 'mfa_intended_path',
|
||||||
MFA_CHALLENGE_ID: 'mfa_challenge_id',
|
MFA_CHALLENGE_ID: 'mfa_challenge_id',
|
||||||
AUTH_METHOD: 'auth_method',
|
AUTH_METHOD: 'auth_method',
|
||||||
ORPHANED_PASSWORD_DISMISSED: 'orphaned_password_dismissed',
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type SessionFlagKey = typeof SessionFlags[keyof typeof SessionFlags];
|
export type SessionFlagKey = typeof SessionFlags[keyof typeof SessionFlags];
|
||||||
@@ -74,27 +73,6 @@ export function clearAuthMethod(): void {
|
|||||||
sessionStorage.removeItem(SessionFlags.AUTH_METHOD);
|
sessionStorage.removeItem(SessionFlags.AUTH_METHOD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the orphaned password dismissed flag
|
|
||||||
*/
|
|
||||||
export function setOrphanedPasswordDismissed(): void {
|
|
||||||
sessionStorage.setItem(SessionFlags.ORPHANED_PASSWORD_DISMISSED, 'true');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if orphaned password warning has been dismissed this session
|
|
||||||
*/
|
|
||||||
export function isOrphanedPasswordDismissed(): boolean {
|
|
||||||
return sessionStorage.getItem(SessionFlags.ORPHANED_PASSWORD_DISMISSED) === 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the orphaned password dismissed flag
|
|
||||||
*/
|
|
||||||
export function clearOrphanedPasswordDismissed(): void {
|
|
||||||
sessionStorage.removeItem(SessionFlags.ORPHANED_PASSWORD_DISMISSED);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear all authentication-related session flags
|
* Clear all authentication-related session flags
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -49,116 +49,6 @@ export default function AuthCallback() {
|
|||||||
const user = session.user;
|
const user = session.user;
|
||||||
console.log('[AuthCallback] User authenticated:', user.id);
|
console.log('[AuthCallback] User authenticated:', user.id);
|
||||||
|
|
||||||
// Check for password setup actions from reset flow
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
const action = urlParams.get('action');
|
|
||||||
|
|
||||||
if (action === 'password-setup-direct') {
|
|
||||||
console.log('[AuthCallback] Processing password-setup-direct action (direct reset flow)');
|
|
||||||
|
|
||||||
// User set password via Supabase's hosted page
|
|
||||||
// Email identity is already created automatically
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "Password Set Successfully!",
|
|
||||||
description: "Your email identity has been created. You can now sign in with your email and password.",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Redirect to auth page for sign-in
|
|
||||||
setTimeout(() => {
|
|
||||||
navigate('/auth');
|
|
||||||
}, 1500);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'password-setup') {
|
|
||||||
console.log('[AuthCallback] Processing password-setup action');
|
|
||||||
|
|
||||||
// Retrieve the password from session storage
|
|
||||||
const pendingPassword = sessionStorage.getItem('pending_password_setup');
|
|
||||||
|
|
||||||
if (pendingPassword) {
|
|
||||||
try {
|
|
||||||
console.log('[AuthCallback] Setting password from pending setup');
|
|
||||||
|
|
||||||
// Set the password - this creates the email identity
|
|
||||||
const { error: passwordError } = await supabase.auth.updateUser({
|
|
||||||
password: pendingPassword
|
|
||||||
});
|
|
||||||
|
|
||||||
if (passwordError) {
|
|
||||||
console.error('[AuthCallback] Failed to set password:', passwordError);
|
|
||||||
throw passwordError;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear session storage
|
|
||||||
sessionStorage.removeItem('pending_password_setup');
|
|
||||||
console.log('[AuthCallback] Password set successfully, email identity created');
|
|
||||||
|
|
||||||
// Show success message
|
|
||||||
toast({
|
|
||||||
title: "Password Set Successfully!",
|
|
||||||
description: "You can now sign in with your email and password.",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Redirect to auth page for sign-in
|
|
||||||
setTimeout(() => {
|
|
||||||
navigate('/auth');
|
|
||||||
}, 1500);
|
|
||||||
|
|
||||||
return;
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('[AuthCallback] Password setup error:', error);
|
|
||||||
sessionStorage.removeItem('pending_password_setup'); // Cleanup
|
|
||||||
|
|
||||||
toast({
|
|
||||||
variant: 'destructive',
|
|
||||||
title: 'Password Setup Failed',
|
|
||||||
description: error.message || 'Failed to set password. Please try again.',
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
navigate('/settings?tab=security');
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn('[AuthCallback] No pending password found in session storage');
|
|
||||||
toast({
|
|
||||||
variant: 'destructive',
|
|
||||||
title: 'Password Setup Incomplete',
|
|
||||||
description: 'Please try setting your password again from Security Settings.',
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
navigate('/settings?tab=security');
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'confirm-password') {
|
|
||||||
console.log('[AuthCallback] Processing confirm-password action (orphaned password)');
|
|
||||||
|
|
||||||
// For orphaned password, the password is already set in auth.users
|
|
||||||
// The reset link just needed to be clicked to create the email identity
|
|
||||||
// Supabase handles this automatically when the reset link is clicked
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "Password Activated!",
|
|
||||||
description: "Your password authentication is now fully active. You can sign in with email and password.",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Redirect to auth page for sign-in
|
|
||||||
setTimeout(() => {
|
|
||||||
navigate('/auth');
|
|
||||||
}, 1500);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this is a new OAuth user (created within last minute)
|
// Check if this is a new OAuth user (created within last minute)
|
||||||
const createdAt = new Date(user.created_at);
|
const createdAt = new Date(user.created_at);
|
||||||
|
|||||||
Reference in New Issue
Block a user