Fix: Implement MFA enforcement and critical bug fix

This commit is contained in:
gpt-engineer-app[bot]
2025-10-17 19:43:43 +00:00
parent ba11773eb6
commit 8a36c71edb
7 changed files with 547 additions and 0 deletions

View File

@@ -2,6 +2,9 @@ import { ReactNode } from 'react';
import { SidebarProvider } from '@/components/ui/sidebar';
import { AdminSidebar } from './AdminSidebar';
import { AdminTopBar } from './AdminTopBar';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { AlertTriangle } from 'lucide-react';
import { useSessionMonitor } from '@/hooks/useSessionMonitor';
interface AdminLayoutProps {
children: ReactNode;
@@ -20,6 +23,8 @@ export function AdminLayout({
lastUpdated,
isRefreshing
}: AdminLayoutProps) {
const { aalWarning } = useSessionMonitor();
return (
<SidebarProvider defaultOpen={true}>
<div className="flex min-h-screen w-full">
@@ -34,6 +39,15 @@ export function AdminLayout({
/>
<div className="flex-1 overflow-y-auto bg-muted/30">
<div className="container mx-auto px-6 py-8 max-w-7xl">
{aalWarning && (
<Alert variant="destructive" className="mb-6">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Session Verification Required</AlertTitle>
<AlertDescription>
Your session requires re-verification. You will be redirected to verify your identity in 30 seconds.
</AlertDescription>
</Alert>
)}
{children}
</div>
</div>

View File

@@ -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 {

View File

@@ -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

View File

@@ -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