Add MFA Verification UI during login
Implements MFA verification dialog for login flow: MFA Verification Features: - MFAVerificationDialog component for code entry - TOTP code input (6-digit authenticator app code) - Email code input with auto-send on dialog open - Backup code support mentioned in help text - Resend email code functionality - Auto-focus on code input field - Large, centered code input for easy entry - Real-time validation (6-digit code required) Login Flow Integration: - Detect MFA requirement from login API error - Show MFA dialog when MFA is enabled for user - Handle MFA verification success with token storage - Allow cancellation to retry login - Seamless transition after successful verification User Experience: - Email codes sent automatically - Visual feedback for code sending/verification - Error alerts for invalid codes - Loading states for all async operations - Clean, focused dialog design - Tip about backup codes Implementation Details: - Integrated with existing login page - Error handling for MFA-required responses - Token storage after MFA verification - Navigation after successful MFA - Support for both TOTP and Email MFA methods 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,8 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { motion } from 'framer-motion';
|
||||
import * as z from 'zod';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
import { MFAVerificationDialog } from '@/components/auth/MFAVerificationDialog';
|
||||
import { tokenStorage } from '@/lib/utils/tokenStorage';
|
||||
import Link from 'next/link';
|
||||
|
||||
const loginSchema = z.object({
|
||||
@@ -34,6 +36,8 @@ export default function LoginPage() {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [mfaRequired, setMfaRequired] = useState(false);
|
||||
const [mfaData, setMfaData] = useState<{ userId: string; mfaMethod: 'totp' | 'email' } | null>(null);
|
||||
const { login } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
@@ -53,12 +57,33 @@ export default function LoginPage() {
|
||||
await login(data);
|
||||
// Navigation is handled in the login function
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Failed to login. Please check your credentials.');
|
||||
// Check if MFA is required
|
||||
if (err.response?.data?.mfaRequired) {
|
||||
setMfaRequired(true);
|
||||
setMfaData({
|
||||
userId: err.response.data.userId,
|
||||
mfaMethod: err.response.data.mfaMethod,
|
||||
});
|
||||
} else {
|
||||
setError(err.message || 'Failed to login. Please check your credentials.');
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMFAVerified = (tokens: { accessToken: string; refreshToken: string }, user: any) => {
|
||||
// Store tokens and navigate
|
||||
tokenStorage.setTokens(tokens.accessToken, tokens.refreshToken);
|
||||
setMfaRequired(false);
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
const handleMFACancel = () => {
|
||||
setMfaRequired(false);
|
||||
setMfaData(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@@ -216,6 +241,17 @@ export default function LoginPage() {
|
||||
</Box>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
|
||||
{/* MFA Verification Dialog */}
|
||||
{mfaRequired && mfaData && (
|
||||
<MFAVerificationDialog
|
||||
open={mfaRequired}
|
||||
userId={mfaData.userId}
|
||||
mfaMethod={mfaData.mfaMethod}
|
||||
onVerified={handleMFAVerified}
|
||||
onCancel={handleMFACancel}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user