mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 02:31:13 -05:00
Fix: Implement MFA enforcement and critical bug fix
This commit is contained in:
@@ -29,6 +29,31 @@ export function AccountDeletionDialog({ open, onOpenChange, userEmail, onDeletio
|
||||
const handleRequestDeletion = async () => {
|
||||
if (!canRequestDeletion(state)) return;
|
||||
|
||||
// Phase 4: AAL2 check for security-critical operations
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (session) {
|
||||
// Check if user has MFA enrolled
|
||||
const { data: factorsData } = await supabase.auth.mfa.listFactors();
|
||||
const hasMFA = factorsData?.totp?.some(f => f.status === 'verified') || false;
|
||||
|
||||
if (hasMFA) {
|
||||
const jwt = session.access_token;
|
||||
const payload = JSON.parse(atob(jwt.split('.')[1]));
|
||||
const currentAal = payload.aal || 'aal1';
|
||||
|
||||
if (currentAal !== 'aal2') {
|
||||
handleError(
|
||||
new Error('Please verify your identity with MFA first'),
|
||||
{ action: 'Request account deletion' }
|
||||
);
|
||||
sessionStorage.setItem('mfa_step_up_required', 'true');
|
||||
sessionStorage.setItem('mfa_intended_path', '/settings?tab=privacy');
|
||||
window.location.href = '/auth';
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispatch({ type: 'SET_LOADING', payload: true });
|
||||
|
||||
try {
|
||||
|
||||
@@ -95,6 +95,34 @@ export function EmailChangeDialog({ open, onOpenChange, currentEmail, userId }:
|
||||
return;
|
||||
}
|
||||
|
||||
// Phase 4: AAL2 check for security-critical operations
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (session) {
|
||||
// Check if user has MFA enrolled
|
||||
const { data: factorsData } = await supabase.auth.mfa.listFactors();
|
||||
const hasMFA = factorsData?.totp?.some(f => f.status === 'verified') || false;
|
||||
|
||||
if (hasMFA) {
|
||||
const jwt = session.access_token;
|
||||
const payload = JSON.parse(atob(jwt.split('.')[1]));
|
||||
const currentAal = payload.aal || 'aal1';
|
||||
|
||||
if (currentAal !== 'aal2') {
|
||||
handleError(
|
||||
new AppError(
|
||||
'Please verify your identity with MFA first',
|
||||
'AAL2_REQUIRED'
|
||||
),
|
||||
{ action: 'Change email', userId, metadata: { step: 'aal2_check' } }
|
||||
);
|
||||
sessionStorage.setItem('mfa_step_up_required', 'true');
|
||||
sessionStorage.setItem('mfa_intended_path', '/settings?tab=security');
|
||||
window.location.href = '/auth';
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
// Step 1: Validate email is not disposable
|
||||
|
||||
@@ -101,6 +101,30 @@ export function PasswordUpdateDialog({ open, onOpenChange, onSuccess }: Password
|
||||
return;
|
||||
}
|
||||
|
||||
// Phase 4: AAL2 check for security-critical operations
|
||||
if (hasMFA) {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (session) {
|
||||
const jwt = session.access_token;
|
||||
const payload = JSON.parse(atob(jwt.split('.')[1]));
|
||||
const currentAal = payload.aal || 'aal1';
|
||||
|
||||
if (currentAal !== 'aal2') {
|
||||
handleError(
|
||||
new AppError(
|
||||
'Please verify your identity with MFA first',
|
||||
'AAL2_REQUIRED'
|
||||
),
|
||||
{ action: 'Change password', userId, metadata: { step: 'aal2_check' } }
|
||||
);
|
||||
sessionStorage.setItem('mfa_step_up_required', 'true');
|
||||
sessionStorage.setItem('mfa_intended_path', '/settings?tab=security');
|
||||
window.location.href = '/auth';
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
// Step 1: Reauthenticate with current password to get a nonce
|
||||
|
||||
Reference in New Issue
Block a user