LoginForm.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. import React, { useContext, useEffect, useState } from 'react';
  2. import { Link, useNavigate, useSearchParams } from 'react-router-dom';
  3. import { UserContext } from '../context/User';
  4. import {
  5. API,
  6. getLogo,
  7. showError,
  8. showInfo,
  9. showSuccess,
  10. updateAPI,
  11. } from '../helpers';
  12. import {
  13. onGitHubOAuthClicked,
  14. onOIDCClicked,
  15. onLinuxDOOAuthClicked,
  16. } from './utils';
  17. import Turnstile from 'react-turnstile';
  18. import {
  19. Button,
  20. Card,
  21. Divider,
  22. Form,
  23. Icon,
  24. Layout,
  25. Modal,
  26. } from '@douyinfe/semi-ui';
  27. import Title from '@douyinfe/semi-ui/lib/es/typography/title';
  28. import Text from '@douyinfe/semi-ui/lib/es/typography/text';
  29. import TelegramLoginButton from 'react-telegram-login';
  30. import { IconGithubLogo, IconAlarm } from '@douyinfe/semi-icons';
  31. import OIDCIcon from './OIDCIcon.js';
  32. import WeChatIcon from './WeChatIcon';
  33. import { setUserData } from '../helpers/data.js';
  34. import LinuxDoIcon from './LinuxDoIcon.js';
  35. import { useTranslation } from 'react-i18next';
  36. const LoginForm = () => {
  37. const [inputs, setInputs] = useState({
  38. username: '',
  39. password: '',
  40. wechat_verification_code: '',
  41. });
  42. const [searchParams, setSearchParams] = useSearchParams();
  43. const [submitted, setSubmitted] = useState(false);
  44. const { username, password } = inputs;
  45. const [userState, userDispatch] = useContext(UserContext);
  46. const [turnstileEnabled, setTurnstileEnabled] = useState(false);
  47. const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
  48. const [turnstileToken, setTurnstileToken] = useState('');
  49. let navigate = useNavigate();
  50. const [status, setStatus] = useState({});
  51. const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false);
  52. const { t } = useTranslation();
  53. const logo = getLogo();
  54. let affCode = new URLSearchParams(window.location.search).get('aff');
  55. if (affCode) {
  56. localStorage.setItem('aff', affCode);
  57. }
  58. useEffect(() => {
  59. if (searchParams.get('expired')) {
  60. showError(t('未登录或登录已过期,请重新登录'));
  61. }
  62. let status = localStorage.getItem('status');
  63. if (status) {
  64. status = JSON.parse(status);
  65. setStatus(status);
  66. if (status.turnstile_check) {
  67. setTurnstileEnabled(true);
  68. setTurnstileSiteKey(status.turnstile_site_key);
  69. }
  70. }
  71. }, []);
  72. const onWeChatLoginClicked = () => {
  73. setShowWeChatLoginModal(true);
  74. };
  75. const onSubmitWeChatVerificationCode = async () => {
  76. if (turnstileEnabled && turnstileToken === '') {
  77. showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
  78. return;
  79. }
  80. const res = await API.get(
  81. `/api/oauth/wechat?code=${inputs.wechat_verification_code}`,
  82. );
  83. const { success, message, data } = res.data;
  84. if (success) {
  85. userDispatch({ type: 'login', payload: data });
  86. localStorage.setItem('user', JSON.stringify(data));
  87. setUserData(data);
  88. updateAPI();
  89. navigate('/');
  90. showSuccess('登录成功!');
  91. setShowWeChatLoginModal(false);
  92. } else {
  93. showError(message);
  94. }
  95. };
  96. function handleChange(name, value) {
  97. setInputs((inputs) => ({ ...inputs, [name]: value }));
  98. }
  99. async function handleSubmit(e) {
  100. if (turnstileEnabled && turnstileToken === '') {
  101. showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
  102. return;
  103. }
  104. setSubmitted(true);
  105. if (username && password) {
  106. const res = await API.post(
  107. `/api/user/login?turnstile=${turnstileToken}`,
  108. {
  109. username,
  110. password,
  111. },
  112. );
  113. const { success, message, data } = res.data;
  114. if (success) {
  115. userDispatch({ type: 'login', payload: data });
  116. setUserData(data);
  117. updateAPI();
  118. showSuccess('登录成功!');
  119. if (username === 'root' && password === '123456') {
  120. Modal.error({
  121. title: '您正在使用默认密码!',
  122. content: '请立刻修改默认密码!',
  123. centered: true,
  124. });
  125. }
  126. navigate('/token');
  127. } else {
  128. showError(message);
  129. }
  130. } else {
  131. showError('请输入用户名和密码!');
  132. }
  133. }
  134. // 添加Telegram登录处理函数
  135. const onTelegramLoginClicked = async (response) => {
  136. const fields = [
  137. 'id',
  138. 'first_name',
  139. 'last_name',
  140. 'username',
  141. 'photo_url',
  142. 'auth_date',
  143. 'hash',
  144. 'lang',
  145. ];
  146. const params = {};
  147. fields.forEach((field) => {
  148. if (response[field]) {
  149. params[field] = response[field];
  150. }
  151. });
  152. const res = await API.get(`/api/oauth/telegram/login`, { params });
  153. const { success, message, data } = res.data;
  154. if (success) {
  155. userDispatch({ type: 'login', payload: data });
  156. localStorage.setItem('user', JSON.stringify(data));
  157. showSuccess('登录成功!');
  158. setUserData(data);
  159. updateAPI();
  160. navigate('/');
  161. } else {
  162. showError(message);
  163. }
  164. };
  165. return (
  166. <div>
  167. <Layout>
  168. <Layout.Header></Layout.Header>
  169. <Layout.Content>
  170. <div
  171. style={{
  172. justifyContent: 'center',
  173. display: 'flex',
  174. marginTop: 120,
  175. }}
  176. >
  177. <div style={{ width: 500 }}>
  178. <Card>
  179. <Title heading={2} style={{ textAlign: 'center' }}>
  180. {t('用户登录')}
  181. </Title>
  182. <Form>
  183. <Form.Input
  184. field={'username'}
  185. label={t('用户名/邮箱')}
  186. placeholder={t('用户名/邮箱')}
  187. name='username'
  188. onChange={(value) => handleChange('username', value)}
  189. />
  190. <Form.Input
  191. field={'password'}
  192. label={t('密码')}
  193. placeholder={t('密码')}
  194. name='password'
  195. type='password'
  196. onChange={(value) => handleChange('password', value)}
  197. />
  198. <Button
  199. theme='solid'
  200. style={{ width: '100%' }}
  201. type={'primary'}
  202. size='large'
  203. htmlType={'submit'}
  204. onClick={handleSubmit}
  205. >
  206. {t('登录')}
  207. </Button>
  208. </Form>
  209. <div
  210. style={{
  211. display: 'flex',
  212. justifyContent: 'space-between',
  213. marginTop: 20,
  214. }}
  215. >
  216. <Text>
  217. {t('没有账户?')}{' '}
  218. <Link to='/register'>{t('点击注册')}</Link>
  219. </Text>
  220. <Text>
  221. {t('忘记密码?')} <Link to='/reset'>{t('点击重置')}</Link>
  222. </Text>
  223. </div>
  224. {status.github_oauth ||
  225. status.oidc_enabled ||
  226. status.wechat_login ||
  227. status.telegram_oauth ||
  228. status.linuxdo_oauth ? (
  229. <>
  230. <Divider margin='12px' align='center'>
  231. {t('第三方登录')}
  232. </Divider>
  233. <div
  234. style={{
  235. display: 'flex',
  236. justifyContent: 'center',
  237. marginTop: 20,
  238. }}
  239. >
  240. {status.github_oauth ? (
  241. <Button
  242. type='primary'
  243. icon={<IconGithubLogo />}
  244. onClick={() =>
  245. onGitHubOAuthClicked(status.github_client_id)
  246. }
  247. />
  248. ) : (
  249. <></>
  250. )}
  251. {status.oidc_enabled ? (
  252. <Button
  253. type='primary'
  254. icon={<OIDCIcon />}
  255. onClick={() =>
  256. onOIDCClicked(
  257. status.oidc_authorization_endpoint,
  258. status.oidc_client_id,
  259. )
  260. }
  261. />
  262. ) : (
  263. <></>
  264. )}
  265. {status.linuxdo_oauth ? (
  266. <Button
  267. icon={<LinuxDoIcon />}
  268. onClick={() =>
  269. onLinuxDOOAuthClicked(status.linuxdo_client_id)
  270. }
  271. />
  272. ) : (
  273. <></>
  274. )}
  275. {status.wechat_login ? (
  276. <Button
  277. type='primary'
  278. style={{ color: 'rgba(var(--semi-green-5), 1)' }}
  279. icon={<Icon svg={<WeChatIcon />} />}
  280. onClick={onWeChatLoginClicked}
  281. />
  282. ) : (
  283. <></>
  284. )}
  285. </div>
  286. {status.telegram_oauth ? (
  287. <>
  288. <div
  289. style={{
  290. display: 'flex',
  291. justifyContent: 'center',
  292. marginTop: 5,
  293. }}
  294. >
  295. <TelegramLoginButton
  296. dataOnauth={onTelegramLoginClicked}
  297. botName={status.telegram_bot_name}
  298. />
  299. </div>
  300. </>
  301. ) : (
  302. <></>
  303. )}
  304. </>
  305. ) : (
  306. <></>
  307. )}
  308. <Modal
  309. title={t('微信扫码登录')}
  310. visible={showWeChatLoginModal}
  311. maskClosable={true}
  312. onOk={onSubmitWeChatVerificationCode}
  313. onCancel={() => setShowWeChatLoginModal(false)}
  314. okText={t('登录')}
  315. size={'small'}
  316. centered={true}
  317. >
  318. <div
  319. style={{
  320. display: 'flex',
  321. alignItem: 'center',
  322. flexDirection: 'column',
  323. }}
  324. >
  325. <img src={status.wechat_qrcode} />
  326. </div>
  327. <div style={{ textAlign: 'center' }}>
  328. <p>
  329. {t(
  330. '微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)',
  331. )}
  332. </p>
  333. </div>
  334. <Form size='large'>
  335. <Form.Input
  336. field={'wechat_verification_code'}
  337. placeholder={t('验证码')}
  338. label={t('验证码')}
  339. value={inputs.wechat_verification_code}
  340. onChange={(value) =>
  341. handleChange('wechat_verification_code', value)
  342. }
  343. />
  344. </Form>
  345. </Modal>
  346. </Card>
  347. {turnstileEnabled ? (
  348. <div
  349. style={{
  350. display: 'flex',
  351. justifyContent: 'center',
  352. marginTop: 20,
  353. }}
  354. >
  355. <Turnstile
  356. sitekey={turnstileSiteKey}
  357. onVerify={(token) => {
  358. setTurnstileToken(token);
  359. }}
  360. />
  361. </div>
  362. ) : (
  363. <></>
  364. )}
  365. </div>
  366. </div>
  367. </Layout.Content>
  368. </Layout>
  369. </div>
  370. );
  371. };
  372. export default LoginForm;