diff --git a/spug_api/apps/setting/urls.py b/spug_api/apps/setting/urls.py index 781345e..3ac1c94 100644 --- a/spug_api/apps/setting/urls.py +++ b/spug_api/apps/setting/urls.py @@ -9,6 +9,6 @@ urlpatterns = [ url(r'^$', SettingView.as_view()), url(r'^ldap_test/$', ldap_test), url(r'^email_test/$', email_test), - url(r'^mfa_test/$', mfa_test), + url(r'^mfa/$', MFAView.as_view()), url(r'^about/$', get_about) ] diff --git a/spug_api/apps/setting/views.py b/spug_api/apps/setting/views.py index fe1f35c..c9c79ed 100644 --- a/spug_api/apps/setting/views.py +++ b/spug_api/apps/setting/views.py @@ -2,11 +2,13 @@ # Copyright: (c) # Released under the AGPL-3.0 License. import django +from django.core.cache import cache from django.views.generic import View from django.conf import settings from libs import JsonParser, Argument, json_response +from libs.utils import generate_random_str from libs.mail import Mail -from apps.account.models import User +from libs.spug import send_login_wx_code from apps.setting.utils import AppSetting from apps.setting.models import Setting import platform @@ -28,6 +30,37 @@ class SettingView(View): return json_response(error=error) +class MFAView(View): + def get(self, request): + if not request.user.wx_token: + return json_response(error='检测到当前账户未配置微信Token,请配置后再尝试启用MFA认证,否则可能造成系统无法正常登录。') + code = generate_random_str(6) + send_login_wx_code(request.user.wx_token, code) + cache.set(f'{request.user.username}:code', code, 300) + return json_response() + + def post(self, request): + form, error = JsonParser( + Argument('enable', type=bool, help='参数错误'), + Argument('code', required=False) + ).parse(request.body) + if error is None: + if form.enable: + if not form.code: + return json_response(error='请输入验证码') + key = f'{request.user.username}:code' + code = cache.get(key) + if not code: + return json_response(error='验证码已失效,请重新获取') + if code != form.code: + ttl = cache.ttl(key) + cache.expire(key, ttl - 100) + return json_response(error='验证码错误') + cache.delete(key) + AppSetting.set('MFA', {'enable': form.enable}) + return json_response(error=error) + + def ldap_test(request): form, error = JsonParser( Argument('server'), @@ -65,9 +98,11 @@ def email_test(request): def mfa_test(request): - for user in User.objects.filter(is_supper=True): - if not user.wx_token: - return json_response(error=f'检测到管理员账户 {user.nickname} 未配置微信Token,请配置后再尝试启用MFA认证,否则可能造成系统无法正常登录。') + if not request.user.wx_token: + return json_response(error='检测到当前账户未配置微信Token,请配置后再尝试启用MFA认证,否则可能造成系统无法正常登录。') + code = generate_random_str(6) + send_login_wx_code(request.user.wx_token, code) + cache.set(f'{request.user.username}:code', code, 300) return json_response() diff --git a/spug_web/src/pages/system/setting/SecuritySetting.js b/spug_web/src/pages/system/setting/SecuritySetting.js index 68dbd3d..2f5c55b 100644 --- a/spug_web/src/pages/system/setting/SecuritySetting.js +++ b/spug_web/src/pages/system/setting/SecuritySetting.js @@ -3,9 +3,9 @@ * Copyright (c) * Released under the AGPL-3.0 License. */ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { observer } from 'mobx-react'; -import { Form, Switch, message } from 'antd'; +import { Form, Switch, Input, Space, message, Button } from 'antd'; import styles from './index.module.css'; import http from 'libs/http'; import store from './store'; @@ -13,6 +13,19 @@ import store from './store'; export default observer(function () { const [verify_ip, setVerifyIP] = useState(store.settings.verify_ip); const [mfa, setMFA] = useState(store.settings.MFA || {}); + const [code, setCode] = useState(); + const [visible, setVisible] = useState(false); + const [counter, setCounter] = useState(0); + const [loading, setLoading] = useState(false); + const [loading2, setLoading2] = useState(false); + + useEffect(() => { + setTimeout(() => { + if (counter > 0) { + setCounter(counter - 1) + } + }, 1000) + }, [counter]) function handleChangeVerifyIP(v) { setVerifyIP(v); @@ -25,21 +38,26 @@ export default observer(function () { function handleChangeMFA(v) { if (v && !store.settings.spug_key) return message.error('开启MFA认证需要先在基本设置中配置调用凭据'); - if (v) { - http.get('/api/setting/mfa_test/') - .then(() => _doModify(v)) - } else { - _doModify(v) - } + v ? setVisible(true) : handleMFAModify(false) } - function _doModify(v) { - setMFA({...mfa, enable: v}); - http.post('/api/setting/', {data: [{key: 'MFA', value: {...mfa, enable: v}}]}) + function handleCaptcha() { + setLoading(true) + http.get('/api/setting/mfa/') + .then(() => setCounter(60)) + .finally(() => setLoading(false)) + } + + function handleMFAModify(v) { + setLoading2(true) + http.post('/api/setting/mfa/', {enable: v, code}) .then(() => { + setMFA({enable: v}); + setVisible(false); message.success('设置成功'); store.fetchSettings() }) + .finally(() => setLoading2(false)) } return ( @@ -58,13 +76,31 @@ export default observer(function () { 建议开启,登录时额外使用验证码进行身份验证。开启前至少要确保管理员账户配置了微信Token(账户管理/用户/编辑),开启后未配置微信Token的账户将无法登录,什么是微信Token?}> - + extra={visible ? '输入验证码,通过验证后开启。' : + 建议开启,登录时额外使用验证码进行身份验证。开启前至少要确保管理员账户配置了微信Token(账户管理/编辑),开启后未配置微信Token的账户将无法登录,什么是微信Token?}> + {visible ? ( +
+ + setCode(e.target.value)}/> + + {counter > 0 ? ( + + ) : ( + + )} + + + + +
+ ) : ( + + )}