import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, Divider, Input, Modal, QRCode, message } from 'antd'; import * as OTPAuth from 'otpauth'; import { ClipboardManager } from '@/utils'; import { TotpCodeSchema } from '@/schemas/login'; import './TwoFactorModal.css'; type Type = 'set' | 'confirm'; interface TwoFactorModalProps { open: boolean; title?: string; description?: string; token?: string; type?: Type; onConfirm: (success: boolean, code?: string) => void; onOpenChange: (open: boolean) => void; } export default function TwoFactorModal({ open, title = '', description = '', token = '', type = 'set', onConfirm, onOpenChange, }: TwoFactorModalProps) { const { t } = useTranslation(); const [messageApi, messageContextHolder] = message.useMessage(); const [enteredCode, setEnteredCode] = useState(''); const [qrValue, setQrValue] = useState(''); const totpRef = useRef(null); useEffect(() => { if (!open) return; setEnteredCode(''); totpRef.current = null; setQrValue(''); if (token) { const totp = new OTPAuth.TOTP({ issuer: '3x-ui', label: 'Administrator', algorithm: 'SHA1', digits: 6, period: 30, secret: token, }); totpRef.current = totp; setQrValue(totp.toString()); } }, [open, token]); function close(success: boolean, code = '') { onConfirm(success, code); onOpenChange(false); setEnteredCode(''); } function onOk() { const codeOk = TotpCodeSchema.safeParse(enteredCode); if (!codeOk.success) { messageApi.error(t(codeOk.error.issues[0]?.message ?? 'pages.settings.security.twoFactorModalError')); return; } if (type === 'confirm' && !token) { close(true, codeOk.data); return; } if (!totpRef.current) return; if (totpRef.current.generate() === codeOk.data) { close(true); } else { messageApi.error(t('pages.settings.security.twoFactorModalError')); } } function onCancel() { close(false); } async function copyToken() { const ok = await ClipboardManager.copyText(token); if (ok) messageApi.success(t('copied')); } return ( <> {messageContextHolder} {t('cancel')}, , ]} > {type === 'set' ? ( <>

{t('pages.settings.security.twoFactorModalSteps')}

{t('pages.settings.security.twoFactorModalFirstStep')}

{token}

{t('pages.settings.security.twoFactorModalSecondStep')}

setEnteredCode(e.target.value)} style={{ width: '100%' }} /> ) : ( <>

{description}

setEnteredCode(e.target.value)} style={{ width: '100%' }} /> )}
); }