Fix: Implement MFA security fix

This commit is contained in:
gpt-engineer-app[bot]
2025-10-31 13:49:18 +00:00
parent 4e4876997e
commit 4bc749a843
3 changed files with 80 additions and 10 deletions

View File

@@ -5,7 +5,8 @@ import { getErrorMessage } from '@/lib/errorHandler';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp'; import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp';
import { Shield } from 'lucide-react'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Shield, AlertCircle } from 'lucide-react';
interface MFAChallengeProps { interface MFAChallengeProps {
factorId: string; factorId: string;
@@ -59,6 +60,14 @@ export function MFAChallenge({ factorId, onSuccess, onCancel }: MFAChallengeProp
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<Alert className="border-destructive/50 text-destructive dark:border-destructive dark:text-destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Security Verification Required</AlertTitle>
<AlertDescription>
Cancelling will sign you out completely. Two-factor authentication must be completed to access your account.
</AlertDescription>
</Alert>
<div className="flex items-center gap-2 text-primary"> <div className="flex items-center gap-2 text-primary">
<Shield className="w-5 h-5" /> <Shield className="w-5 h-5" />
<h3 className="font-semibold">Two-Factor Authentication</h3> <h3 className="font-semibold">Two-Factor Authentication</h3>
@@ -96,7 +105,7 @@ export function MFAChallenge({ factorId, onSuccess, onCancel }: MFAChallengeProp
className="flex-1" className="flex-1"
disabled={loading} disabled={loading}
> >
Cancel Cancel & Sign Out
</Button> </Button>
<Button <Button
onClick={handleVerify} onClick={handleVerify}

View File

@@ -12,7 +12,11 @@ interface MFAStepUpModalProps {
export function MFAStepUpModal({ open, factorId, onSuccess, onCancel }: MFAStepUpModalProps) { export function MFAStepUpModal({ open, factorId, onSuccess, onCancel }: MFAStepUpModalProps) {
return ( return (
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onCancel()}> <Dialog open={open} onOpenChange={(isOpen) => !isOpen && onCancel()}>
<DialogContent className="sm:max-w-md" onInteractOutside={(e) => e.preventDefault()}> <DialogContent
className="sm:max-w-md"
onInteractOutside={(e) => e.preventDefault()}
onEscapeKeyDown={(e) => e.preventDefault()}
>
<DialogHeader> <DialogHeader>
<div className="flex items-center gap-2 justify-center mb-2"> <div className="flex items-center gap-2 justify-center mb-2">
<Shield className="h-6 w-6 text-primary" /> <Shield className="h-6 w-6 text-primary" />

View File

@@ -8,8 +8,9 @@ import { Label } from '@/components/ui/label';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Alert, AlertDescription } from '@/components/ui/alert'; import { Alert, AlertDescription } from '@/components/ui/alert';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { Zap, Mail, Lock, User, AlertCircle, Eye, EyeOff } from 'lucide-react'; import { Zap, Mail, Lock, User, AlertCircle, Eye, EyeOff, Shield } from 'lucide-react';
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
import { useToast } from '@/hooks/use-toast'; import { useToast } from '@/hooks/use-toast';
import { getErrorMessage } from '@/lib/errorHandler'; import { getErrorMessage } from '@/lib/errorHandler';
@@ -228,9 +229,43 @@ export default function Auth() {
}); });
}; };
const handleMfaCancel = () => { const handleMfaCancel = async () => {
try {
// CRITICAL SECURITY: Log cancellation attempt
const { data: { session } } = await supabase.auth.getSession();
if (session) {
try {
const { data: aalData } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel();
await supabase.rpc('log_admin_action', {
_admin_user_id: session.user.id,
_action: 'mfa_verification_cancelled',
_target_user_id: session.user.id,
_details: {
timestamp: new Date().toISOString(),
reason: 'user_cancelled_mfa_prompt',
aal_before_cancel: aalData?.currentLevel || 'aal1'
}
});
} catch (logError) {
console.error('Failed to log MFA cancellation:', logError);
}
}
} catch (error) {
console.error('Error during MFA cancellation:', error);
}
// CRITICAL SECURITY: User cannot bypass MFA if enrolled
// Cancelling MFA prompt MUST sign the user out
await supabase.auth.signOut();
setMfaFactorId(null); setMfaFactorId(null);
setSignInCaptchaKey(prev => prev + 1); setSignInCaptchaKey(prev => prev + 1);
toast({
title: "Sign in cancelled",
description: "Two-factor authentication is required for your account. Please sign in again and complete MFA verification.",
variant: "destructive"
});
}; };
const handleSignUp = async (e: React.FormEvent) => { const handleSignUp = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
@@ -427,11 +462,33 @@ export default function Auth() {
)} )}
{mfaFactorId ? ( {mfaFactorId ? (
<MFAChallenge <Dialog open={!!mfaFactorId} onOpenChange={(isOpen) => {
factorId={mfaFactorId} if (!isOpen) {
onSuccess={handleMfaSuccess} handleMfaCancel();
onCancel={handleMfaCancel} }
/> }}>
<DialogContent
className="sm:max-w-md"
onInteractOutside={(e) => e.preventDefault()}
onEscapeKeyDown={(e) => e.preventDefault()}
>
<DialogHeader>
<div className="flex items-center gap-2 justify-center mb-2">
<Shield className="h-6 w-6 text-primary" />
<DialogTitle>Two-Factor Authentication Required</DialogTitle>
</div>
<DialogDescription className="text-center">
Your account security settings require MFA verification to continue.
</DialogDescription>
</DialogHeader>
<MFAChallenge
factorId={mfaFactorId}
onSuccess={handleMfaSuccess}
onCancel={handleMfaCancel}
/>
</DialogContent>
</Dialog>
) : ( ) : (
<> <>
<form onSubmit={handleSignIn} className="space-y-4"> <form onSubmit={handleSignIn} className="space-y-4">