Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

247 строки
7.0 KiB

  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact support@quantumnous.com
  14. */
  15. import { API, showError, showSuccess } from '../../helpers';
  16. import {
  17. Button,
  18. Card,
  19. Divider,
  20. Form,
  21. Input,
  22. Typography,
  23. } from '@douyinfe/semi-ui';
  24. import React, { useState } from 'react';
  25. import { useTranslation } from 'react-i18next';
  26. const { Title, Text, Paragraph } = Typography;
  27. const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
  28. const { t } = useTranslation();
  29. const [loading, setLoading] = useState(false);
  30. const [useBackupCode, setUseBackupCode] = useState(false);
  31. const [verificationCode, setVerificationCode] = useState('');
  32. const handleSubmit = async () => {
  33. if (!verificationCode) {
  34. showError(t('请输入验证码'));
  35. return;
  36. }
  37. // Validate code format
  38. if (useBackupCode && verificationCode.length !== 8) {
  39. showError(t('备用码必须是8位'));
  40. return;
  41. } else if (!useBackupCode && !/^\d{6}$/.test(verificationCode)) {
  42. showError(t('验证码必须是6位数字'));
  43. return;
  44. }
  45. setLoading(true);
  46. try {
  47. const res = await API.post('/api/user/login/2fa', {
  48. code: verificationCode,
  49. });
  50. if (res.data.success) {
  51. showSuccess(t('登录成功!'));
  52. // 保存用户信息到本地存储
  53. localStorage.setItem('user', JSON.stringify(res.data.data));
  54. if (onSuccess) {
  55. onSuccess(res.data.data);
  56. }
  57. } else {
  58. showError(res.data.message);
  59. }
  60. } catch (error) {
  61. showError(t('验证失败,请重试'));
  62. } finally {
  63. setLoading(false);
  64. }
  65. };
  66. const handleKeyPress = (e) => {
  67. if (e.key === 'Enter') {
  68. handleSubmit();
  69. }
  70. };
  71. if (isModal) {
  72. return (
  73. <div className='space-y-4'>
  74. <Paragraph className='text-gray-600 dark:text-gray-300'>
  75. {t('请输入认证器应用显示的验证码完成登录')}
  76. </Paragraph>
  77. <Form onSubmit={handleSubmit}>
  78. <Form.Input
  79. field='code'
  80. label={useBackupCode ? t('备用码') : t('验证码')}
  81. placeholder={useBackupCode ? t('请输入8位备用码') : t('请输入6位验证码')}
  82. value={verificationCode}
  83. onChange={setVerificationCode}
  84. onKeyPress={handleKeyPress}
  85. size='large'
  86. style={{ marginBottom: 16 }}
  87. autoFocus
  88. />
  89. <Button
  90. htmlType='submit'
  91. type='primary'
  92. loading={loading}
  93. block
  94. size='large'
  95. style={{ marginBottom: 16 }}
  96. >
  97. {t('验证并登录')}
  98. </Button>
  99. </Form>
  100. <Divider />
  101. <div style={{ textAlign: 'center' }}>
  102. <Button
  103. theme='borderless'
  104. type='tertiary'
  105. onClick={() => {
  106. setUseBackupCode(!useBackupCode);
  107. setVerificationCode('');
  108. }}
  109. style={{ marginRight: 16, color: '#1890ff', padding: 0 }}
  110. >
  111. {useBackupCode ? t('使用认证器验证码') : t('使用备用码')}
  112. </Button>
  113. {onBack && (
  114. <Button
  115. theme='borderless'
  116. type='tertiary'
  117. onClick={onBack}
  118. style={{ color: '#1890ff', padding: 0 }}
  119. >
  120. {t('返回登录')}
  121. </Button>
  122. )}
  123. </div>
  124. <div className='bg-gray-50 dark:bg-gray-800 rounded-lg p-3'>
  125. <Text size='small' type='secondary'>
  126. <strong>{t('提示:')}</strong>
  127. <br />
  128. {t('• 验证码每30秒更新一次')}
  129. <br />
  130. {t('• 如果无法获取验证码,请使用备用码')}
  131. <br />{t('• 每个备用码只能使用一次')}
  132. </Text>
  133. </div>
  134. </div>
  135. );
  136. }
  137. return (
  138. <div
  139. style={{
  140. display: 'flex',
  141. justifyContent: 'center',
  142. alignItems: 'center',
  143. minHeight: '60vh',
  144. }}
  145. >
  146. <Card style={{ width: 400, padding: 24 }}>
  147. <div style={{ textAlign: 'center', marginBottom: 24 }}>
  148. <Title heading={3}>{t('两步验证')}</Title>
  149. <Paragraph type='secondary'>
  150. {t('请输入认证器应用显示的验证码完成登录')}
  151. </Paragraph>
  152. </div>
  153. <Form onSubmit={handleSubmit}>
  154. <Form.Input
  155. field='code'
  156. label={useBackupCode ? t('备用码') : t('验证码')}
  157. placeholder={useBackupCode ? t('请输入8位备用码') : t('请输入6位验证码')}
  158. value={verificationCode}
  159. onChange={setVerificationCode}
  160. onKeyPress={handleKeyPress}
  161. size='large'
  162. style={{ marginBottom: 16 }}
  163. autoFocus
  164. />
  165. <Button
  166. htmlType='submit'
  167. type='primary'
  168. loading={loading}
  169. block
  170. size='large'
  171. style={{ marginBottom: 16 }}
  172. >
  173. {t('验证并登录')}
  174. </Button>
  175. </Form>
  176. <Divider />
  177. <div style={{ textAlign: 'center' }}>
  178. <Button
  179. theme='borderless'
  180. type='tertiary'
  181. onClick={() => {
  182. setUseBackupCode(!useBackupCode);
  183. setVerificationCode('');
  184. }}
  185. style={{ marginRight: 16, color: '#1890ff', padding: 0 }}
  186. >
  187. {useBackupCode ? t('使用认证器验证码') : t('使用备用码')}
  188. </Button>
  189. {onBack && (
  190. <Button
  191. theme='borderless'
  192. type='tertiary'
  193. onClick={onBack}
  194. style={{ color: '#1890ff', padding: 0 }}
  195. >
  196. {t('返回登录')}
  197. </Button>
  198. )}
  199. </div>
  200. <div
  201. style={{
  202. marginTop: 24,
  203. padding: 16,
  204. background: '#f6f8fa',
  205. borderRadius: 6,
  206. }}
  207. >
  208. <Text size='small' type='secondary'>
  209. <strong>{t('提示:')}</strong>
  210. <br />
  211. {t('• 验证码每30秒更新一次')}
  212. <br />
  213. {t('• 如果无法获取验证码,请使用备用码')}
  214. <br />{t('• 每个备用码只能使用一次')}
  215. </Text>
  216. </div>
  217. </Card>
  218. </div>
  219. );
  220. };
  221. export default TwoFAVerification;