/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import { API, showError, showSuccess, showWarning } from '../../helpers'; import { Banner, Button, Card, Checkbox, Divider, Input, Modal, Tag, Typography, Steps, Space, Badge } from '@douyinfe/semi-ui'; import { IconShield, IconAlertTriangle, IconRefresh, IconCopy } from '@douyinfe/semi-icons'; import React, { useEffect, useState } from 'react'; import { QRCodeSVG } from 'qrcode.react'; const { Text, Paragraph } = Typography; const TwoFASetting = ({ t }) => { const [loading, setLoading] = useState(false); const [status, setStatus] = useState({ enabled: false, locked: false, backup_codes_remaining: 0 }); // 模态框状态 const [setupModalVisible, setSetupModalVisible] = useState(false); const [enableModalVisible, setEnableModalVisible] = useState(false); const [disableModalVisible, setDisableModalVisible] = useState(false); const [backupModalVisible, setBackupModalVisible] = useState(false); // 表单数据 const [setupData, setSetupData] = useState(null); const [verificationCode, setVerificationCode] = useState(''); const [backupCodes, setBackupCodes] = useState([]); const [confirmDisable, setConfirmDisable] = useState(false); const [currentStep, setCurrentStep] = useState(0); // 获取2FA状态 const fetchStatus = async () => { try { const res = await API.get('/api/user/2fa/status'); if (res.data.success) { setStatus(res.data.data); } } catch (error) { showError(t('获取2FA状态失败')); } }; useEffect(() => { fetchStatus(); }, []); // 初始化2FA设置 const handleSetup2FA = async () => { setLoading(true); try { const res = await API.post('/api/user/2fa/setup'); if (res.data.success) { setSetupData(res.data.data); setSetupModalVisible(true); setCurrentStep(0); } else { showError(res.data.message); } } catch (error) { showError(t('设置2FA失败')); } finally { setLoading(false); } }; // 启用2FA const handleEnable2FA = async () => { if (!verificationCode) { showWarning(t('请输入验证码')); return; } setLoading(true); try { const res = await API.post('/api/user/2fa/enable', { code: verificationCode }); if (res.data.success) { showSuccess(t('两步验证启用成功!')); setEnableModalVisible(false); setSetupModalVisible(false); setVerificationCode(''); setCurrentStep(0); fetchStatus(); } else { showError(res.data.message); } } catch (error) { showError(t('启用2FA失败')); } finally { setLoading(false); } }; // 禁用2FA const handleDisable2FA = async () => { if (!verificationCode) { showWarning(t('请输入验证码或备用码')); return; } if (!confirmDisable) { showWarning(t('请确认您已了解禁用两步验证的后果')); return; } setLoading(true); try { const res = await API.post('/api/user/2fa/disable', { code: verificationCode }); if (res.data.success) { showSuccess(t('两步验证已禁用')); setDisableModalVisible(false); setVerificationCode(''); setConfirmDisable(false); fetchStatus(); } else { showError(res.data.message); } } catch (error) { showError(t('禁用2FA失败')); } finally { setLoading(false); } }; // 重新生成备用码 const handleRegenerateBackupCodes = async () => { if (!verificationCode) { showWarning(t('请输入验证码')); return; } setLoading(true); try { const res = await API.post('/api/user/2fa/backup_codes', { code: verificationCode }); if (res.data.success) { setBackupCodes(res.data.data.backup_codes); showSuccess(t('备用码重新生成成功')); setVerificationCode(''); fetchStatus(); } else { showError(res.data.message); } } catch (error) { showError(t('重新生成备用码失败')); } finally { setLoading(false); } }; // 通用复制函数 const copyTextToClipboard = (text, successMessage = t('已复制到剪贴板')) => { navigator.clipboard.writeText(text).then(() => { showSuccess(successMessage); }).catch(() => { showError(t('复制失败,请手动复制')); }); }; const copyBackupCodes = () => { const codesText = backupCodes.join('\n'); copyTextToClipboard(codesText, t('备用码已复制到剪贴板')); }; // 备用码展示组件 const BackupCodesDisplay = ({ codes, title, onCopy }) => { return (
{title}
{codes.map((code, index) => (
{code} #{(index + 1).toString().padStart(2, '0')}
))}
); }; // 渲染设置模态框footer const renderSetupModalFooter = () => { return ( <> {currentStep > 0 && ( )} {currentStep < 2 ? ( ) : ( )} ); }; // 渲染禁用模态框footer const renderDisableModalFooter = () => { return ( <> ); }; // 渲染重新生成模态框footer const renderRegenerateModalFooter = () => { if (backupCodes.length > 0) { return ( ); } return ( <> ); }; return ( <>
{t('两步验证设置')} {status.enabled ? ( {t('已启用')} ) : ( {t('未启用')} )} {status.locked && ( {t('账户已锁定')} )}
{t('两步验证(2FA)为您的账户提供额外的安全保护。启用后,登录时需要输入密码和验证器应用生成的验证码。')} {status.enabled && (
{t('剩余备用码:')}{status.backup_codes_remaining || 0}{t('个')}
)}
{!status.enabled ? ( ) : (
)}
{/* 2FA设置模态框 */} {t('设置两步验证')} } visible={setupModalVisible} onCancel={() => { setSetupModalVisible(false); setSetupData(null); setCurrentStep(0); setVerificationCode(''); }} footer={renderSetupModalFooter()} width={650} style={{ maxWidth: '90vw' }} > {setupData && (
{/* 步骤进度 */} {/* 步骤内容 */}
{currentStep === 0 && (
{t('使用认证器应用(如 Google Authenticator、Microsoft Authenticator)扫描下方二维码:')}
{t('或手动输入密钥:')}{setupData.secret}
)} {currentStep === 1 && (
{/* 备用码展示 */} { const codesText = setupData.backup_codes.join('\n'); copyTextToClipboard(codesText, t('备用码已复制到剪贴板')); }} />
)} {currentStep === 2 && ( )}
)}
{/* 禁用2FA模态框 */} {t('禁用两步验证')} } visible={disableModalVisible} onCancel={() => { setDisableModalVisible(false); setVerificationCode(''); setConfirmDisable(false); }} footer={renderDisableModalFooter()} width={550} style={{ maxWidth: '90vw' }} >
{/* 警告提示 */}
{/* 内容区域 */}
{t('禁用后的影响:')}
  • {t('降低您账户的安全性')}
  • {t('需要重新完整设置才能再次启用')}
  • {t('永久删除您的两步验证设置')}
  • {t('永久删除所有备用码(包括未使用的)')}
{t('验证身份')}
setConfirmDisable(e.target.checked)} className="text-sm" > {t('我已了解禁用两步验证将永久删除所有相关设置和备用码,此操作不可撤销')}
{/* 重新生成备用码模态框 */} {t('重新生成备用码')} } visible={backupModalVisible} onCancel={() => { setBackupModalVisible(false); setVerificationCode(''); setBackupCodes([]); }} footer={renderRegenerateModalFooter()} width={500} style={{ maxWidth: '90vw' }} >
{backupCodes.length === 0 ? ( <> {/* 警告提示 */}
{/* 验证区域 */}
{t('验证身份')}
) : ( <> {/* 成功提示 */}
{t('新的备用码已生成')}
{t('旧的备用码已失效,请保存新的备用码')} {/* 备用码展示 */}
)}
); }; export default TwoFASetting;