mirror of https://github.com/openspug/spug
improve
parent
105abd757f
commit
4dc4123d14
|
@ -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)
|
||||
]
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
# Copyright: (c) <spug.dev@gmail.com>
|
||||
# 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()
|
||||
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
* Copyright (c) <spug.dev@gmail.com>
|
||||
* 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 () {
|
|||
<Form.Item
|
||||
label="登录MFA(两步)认证"
|
||||
style={{marginTop: 24}}
|
||||
help={<span>建议开启,登录时额外使用验证码进行身份验证。开启前至少要确保管理员账户配置了微信Token(账户管理/用户/编辑),开启后未配置微信Token的账户将无法登录,<a
|
||||
target="_blank" rel="noopener noreferrer" href="https://spug.cc/docs/wx-token/">什么是微信Token?</a></span>}>
|
||||
<Switch
|
||||
checkedChildren="开启"
|
||||
unCheckedChildren="关闭"
|
||||
onChange={handleChangeMFA}
|
||||
checked={mfa.enable}/>
|
||||
extra={visible ? '输入验证码,通过验证后开启。' :
|
||||
<span>建议开启,登录时额外使用验证码进行身份验证。开启前至少要确保管理员账户配置了微信Token(账户管理/编辑),开启后未配置微信Token的账户将无法登录,<a
|
||||
target="_blank" rel="noopener noreferrer" href="https://spug.cc/docs/wx-token/">什么是微信Token?</a></span>}>
|
||||
{visible ? (
|
||||
<div style={{display: 'flex', width: 490}}>
|
||||
<Form.Item noStyle help="验证通过后开启MFA(两步验证)。">
|
||||
<Input placeholder="请输入验证码" onChange={e => setCode(e.target.value)}/>
|
||||
</Form.Item>
|
||||
{counter > 0 ? (
|
||||
<Button disabled style={{marginLeft: 8}}>{counter} 秒后重新获取</Button>
|
||||
) : (
|
||||
<Button loading={loading} style={{marginLeft: 8}} onClick={handleCaptcha}>获取验证码</Button>
|
||||
)}
|
||||
<Space style={{marginLeft: 48}}>
|
||||
<Button onClick={() => setVisible(false)}>取消</Button>
|
||||
<Button type="primary" loading={loading2} onClick={() => handleMFAModify(true)}>确认</Button>
|
||||
</Space>
|
||||
</div>
|
||||
) : (
|
||||
<Switch
|
||||
checkedChildren="开启"
|
||||
unCheckedChildren="关闭"
|
||||
onChange={handleChangeMFA}
|
||||
checked={mfa.enable}/>
|
||||
)}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</React.Fragment>
|
||||
|
|
Loading…
Reference in New Issue