mirror of https://github.com/openspug/spug
improve
parent
105abd757f
commit
4dc4123d14
|
@ -9,6 +9,6 @@ urlpatterns = [
|
||||||
url(r'^$', SettingView.as_view()),
|
url(r'^$', SettingView.as_view()),
|
||||||
url(r'^ldap_test/$', ldap_test),
|
url(r'^ldap_test/$', ldap_test),
|
||||||
url(r'^email_test/$', email_test),
|
url(r'^email_test/$', email_test),
|
||||||
url(r'^mfa_test/$', mfa_test),
|
url(r'^mfa/$', MFAView.as_view()),
|
||||||
url(r'^about/$', get_about)
|
url(r'^about/$', get_about)
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
# Copyright: (c) <spug.dev@gmail.com>
|
# Copyright: (c) <spug.dev@gmail.com>
|
||||||
# Released under the AGPL-3.0 License.
|
# Released under the AGPL-3.0 License.
|
||||||
import django
|
import django
|
||||||
|
from django.core.cache import cache
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from libs import JsonParser, Argument, json_response
|
from libs import JsonParser, Argument, json_response
|
||||||
|
from libs.utils import generate_random_str
|
||||||
from libs.mail import Mail
|
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.utils import AppSetting
|
||||||
from apps.setting.models import Setting
|
from apps.setting.models import Setting
|
||||||
import platform
|
import platform
|
||||||
|
@ -28,6 +30,37 @@ class SettingView(View):
|
||||||
return json_response(error=error)
|
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):
|
def ldap_test(request):
|
||||||
form, error = JsonParser(
|
form, error = JsonParser(
|
||||||
Argument('server'),
|
Argument('server'),
|
||||||
|
@ -65,9 +98,11 @@ def email_test(request):
|
||||||
|
|
||||||
|
|
||||||
def mfa_test(request):
|
def mfa_test(request):
|
||||||
for user in User.objects.filter(is_supper=True):
|
if not request.user.wx_token:
|
||||||
if not user.wx_token:
|
return json_response(error='检测到当前账户未配置微信Token,请配置后再尝试启用MFA认证,否则可能造成系统无法正常登录。')
|
||||||
return json_response(error=f'检测到管理员账户 {user.nickname} 未配置微信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()
|
return json_response()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
* Copyright (c) <spug.dev@gmail.com>
|
* Copyright (c) <spug.dev@gmail.com>
|
||||||
* Released under the AGPL-3.0 License.
|
* Released under the AGPL-3.0 License.
|
||||||
*/
|
*/
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { observer } from 'mobx-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 styles from './index.module.css';
|
||||||
import http from 'libs/http';
|
import http from 'libs/http';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
|
@ -13,6 +13,19 @@ import store from './store';
|
||||||
export default observer(function () {
|
export default observer(function () {
|
||||||
const [verify_ip, setVerifyIP] = useState(store.settings.verify_ip);
|
const [verify_ip, setVerifyIP] = useState(store.settings.verify_ip);
|
||||||
const [mfa, setMFA] = useState(store.settings.MFA || {});
|
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) {
|
function handleChangeVerifyIP(v) {
|
||||||
setVerifyIP(v);
|
setVerifyIP(v);
|
||||||
|
@ -25,21 +38,26 @@ export default observer(function () {
|
||||||
|
|
||||||
function handleChangeMFA(v) {
|
function handleChangeMFA(v) {
|
||||||
if (v && !store.settings.spug_key) return message.error('开启MFA认证需要先在基本设置中配置调用凭据');
|
if (v && !store.settings.spug_key) return message.error('开启MFA认证需要先在基本设置中配置调用凭据');
|
||||||
if (v) {
|
v ? setVisible(true) : handleMFAModify(false)
|
||||||
http.get('/api/setting/mfa_test/')
|
|
||||||
.then(() => _doModify(v))
|
|
||||||
} else {
|
|
||||||
_doModify(v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _doModify(v) {
|
function handleCaptcha() {
|
||||||
setMFA({...mfa, enable: v});
|
setLoading(true)
|
||||||
http.post('/api/setting/', {data: [{key: 'MFA', value: {...mfa, enable: v}}]})
|
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(() => {
|
.then(() => {
|
||||||
|
setMFA({enable: v});
|
||||||
|
setVisible(false);
|
||||||
message.success('设置成功');
|
message.success('设置成功');
|
||||||
store.fetchSettings()
|
store.fetchSettings()
|
||||||
})
|
})
|
||||||
|
.finally(() => setLoading2(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -58,13 +76,31 @@ export default observer(function () {
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="登录MFA(两步)认证"
|
label="登录MFA(两步)认证"
|
||||||
style={{marginTop: 24}}
|
style={{marginTop: 24}}
|
||||||
help={<span>建议开启,登录时额外使用验证码进行身份验证。开启前至少要确保管理员账户配置了微信Token(账户管理/用户/编辑),开启后未配置微信Token的账户将无法登录,<a
|
extra={visible ? '输入验证码,通过验证后开启。' :
|
||||||
target="_blank" rel="noopener noreferrer" href="https://spug.cc/docs/wx-token/">什么是微信Token?</a></span>}>
|
<span>建议开启,登录时额外使用验证码进行身份验证。开启前至少要确保管理员账户配置了微信Token(账户管理/编辑),开启后未配置微信Token的账户将无法登录,<a
|
||||||
<Switch
|
target="_blank" rel="noopener noreferrer" href="https://spug.cc/docs/wx-token/">什么是微信Token?</a></span>}>
|
||||||
checkedChildren="开启"
|
{visible ? (
|
||||||
unCheckedChildren="关闭"
|
<div style={{display: 'flex', width: 490}}>
|
||||||
onChange={handleChangeMFA}
|
<Form.Item noStyle help="验证通过后开启MFA(两步验证)。">
|
||||||
checked={mfa.enable}/>
|
<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.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
Loading…
Reference in New Issue