mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 16:11:13 -05:00
Refactor: Secure email confirmation flow
This commit is contained in:
@@ -117,15 +117,8 @@ export function EmailChangeDialog({ open, onOpenChange, currentEmail, userId }:
|
|||||||
|
|
||||||
if (updateError) throw updateError;
|
if (updateError) throw updateError;
|
||||||
|
|
||||||
// Step 3: Update Novu subscriber (non-blocking)
|
// Step 3: Novu subscriber will be updated automatically after both emails are confirmed
|
||||||
if (notificationService.isEnabled()) {
|
// This happens in the useAuth hook when the email change is fully verified
|
||||||
notificationService.updateSubscriber({
|
|
||||||
subscriberId: userId,
|
|
||||||
email: data.newEmail,
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('Failed to update Novu subscriber:', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4: Log the email change attempt
|
// Step 4: Log the email change attempt
|
||||||
supabase.from('admin_audit_log').insert({
|
supabase.from('admin_audit_log').insert({
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
const [profile, setProfile] = useState<Profile | null>(null);
|
const [profile, setProfile] = useState<Profile | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [pendingEmail, setPendingEmail] = useState<string | null>(null);
|
const [pendingEmail, setPendingEmail] = useState<string | null>(null);
|
||||||
|
const [previousEmail, setPreviousEmail] = useState<string | null>(null);
|
||||||
|
|
||||||
const fetchProfile = async (userId: string) => {
|
const fetchProfile = async (userId: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -61,13 +62,72 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
// Listen for auth changes
|
// Listen for auth changes
|
||||||
const {
|
const {
|
||||||
data: { subscription },
|
data: { subscription },
|
||||||
} = supabase.auth.onAuthStateChange((event, session) => {
|
} = supabase.auth.onAuthStateChange(async (event, session) => {
|
||||||
|
const currentEmail = session?.user?.email;
|
||||||
|
const newEmailPending = session?.user?.new_email;
|
||||||
|
|
||||||
setSession(session);
|
setSession(session);
|
||||||
setUser(session?.user ?? null);
|
setUser(session?.user ?? null);
|
||||||
|
|
||||||
// Track pending email changes
|
// Track pending email changes
|
||||||
const newEmail = session?.user?.new_email;
|
setPendingEmail(newEmailPending ?? null);
|
||||||
setPendingEmail(newEmail ?? null);
|
|
||||||
|
// Detect confirmed email change: email changed AND no longer pending
|
||||||
|
if (
|
||||||
|
session?.user &&
|
||||||
|
previousEmail &&
|
||||||
|
currentEmail &&
|
||||||
|
currentEmail !== previousEmail &&
|
||||||
|
!newEmailPending
|
||||||
|
) {
|
||||||
|
console.log('Email change confirmed:', { from: previousEmail, to: currentEmail });
|
||||||
|
|
||||||
|
// Defer Novu update and notifications to avoid blocking auth
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
// Update Novu subscriber with confirmed email
|
||||||
|
const { notificationService } = await import('@/lib/notificationService');
|
||||||
|
if (notificationService.isEnabled()) {
|
||||||
|
await notificationService.updateSubscriber({
|
||||||
|
subscriberId: session.user.id,
|
||||||
|
email: currentEmail,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the confirmed email change
|
||||||
|
await supabase.from('admin_audit_log').insert({
|
||||||
|
admin_user_id: session.user.id,
|
||||||
|
target_user_id: session.user.id,
|
||||||
|
action: 'email_change_completed',
|
||||||
|
details: {
|
||||||
|
old_email: previousEmail,
|
||||||
|
new_email: currentEmail,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send final security notification
|
||||||
|
if (notificationService.isEnabled()) {
|
||||||
|
await notificationService.trigger({
|
||||||
|
workflowId: 'email-changed',
|
||||||
|
subscriberId: session.user.id,
|
||||||
|
payload: {
|
||||||
|
oldEmail: previousEmail,
|
||||||
|
newEmail: currentEmail,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating Novu after email confirmation:', error);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update tracked email
|
||||||
|
if (currentEmail) {
|
||||||
|
setPreviousEmail(currentEmail);
|
||||||
|
}
|
||||||
|
|
||||||
if (session?.user) {
|
if (session?.user) {
|
||||||
// Defer profile fetch to avoid deadlock
|
// Defer profile fetch to avoid deadlock
|
||||||
|
|||||||
Reference in New Issue
Block a user