From 354d1ae15480c76e0ebbfe98a391b206620c33d6 Mon Sep 17 00:00:00 2001 From: vapao Date: Wed, 17 Feb 2021 00:30:49 +0800 Subject: [PATCH] add MFA --- spug_api/apps/account/views.py | 2 +- spug_api/apps/setting/models.py | 24 ++++++++++ spug_api/apps/setting/urls.py | 1 + spug_api/apps/setting/utils.py | 12 ++--- spug_api/apps/setting/views.py | 10 +++-- spug_api/spug/settings.py | 1 + spug_web/package.json | 2 +- spug_web/src/index.js | 3 +- spug_web/src/pages/login/index.js | 31 ++++++++++++- spug_web/src/pages/login/login.module.css | 1 + .../src/pages/system/setting/AlarmSetting.js | 39 ++++------------ .../src/pages/system/setting/BasicSetting.js | 44 +++++++++++++++++-- .../src/pages/system/setting/KeySetting.js | 22 ++++------ .../src/pages/system/setting/LDAPSetting.js | 8 ++-- .../src/pages/system/setting/OpenService.js | 10 ++--- .../pages/system/setting/SecuritySetting.js | 36 +++++++++++---- spug_web/src/pages/system/setting/store.js | 2 +- 17 files changed, 166 insertions(+), 82 deletions(-) diff --git a/spug_api/apps/account/views.py b/spug_api/apps/account/views.py index fcb50e1..429b0f7 100644 --- a/spug_api/apps/account/views.py +++ b/spug_api/apps/account/views.py @@ -199,7 +199,7 @@ def handle_user_info(user, x_real_ip): user.last_ip = x_real_ip user.save() History.objects.create(user=user, ip=x_real_ip) - verify_ip = AppSetting.get_default('verify_ip', 'True') == 'True' + verify_ip = AppSetting.get_default('verify_ip', True) return json_response({ 'access_token': user.access_token, 'nickname': user.nickname, diff --git a/spug_api/apps/setting/models.py b/spug_api/apps/setting/models.py index 960ada4..fdac245 100644 --- a/spug_api/apps/setting/models.py +++ b/spug_api/apps/setting/models.py @@ -3,6 +3,18 @@ # Released under the AGPL-3.0 License. from django.db import models from libs import ModelMixin +import json + +KEYS_DEFAULT = { + 'MFA': {'enable': False}, + 'verify_ip': True, + 'ldap_service': {}, + 'spug_key': None, + 'api_key': None, + 'mail_service': {}, + 'private_key': None, + 'public_key': None, +} class Setting(models.Model, ModelMixin): @@ -10,6 +22,18 @@ class Setting(models.Model, ModelMixin): value = models.TextField() desc = models.CharField(max_length=255, null=True) + def to_view(self): + tmp = self.to_dict(selects=('key',)) + tmp['value'] = self.real_val + return tmp + + @property + def real_val(self): + if self.value: + return json.loads(self.value) + else: + return KEYS_DEFAULT.get(self.key) + def __repr__(self): return '' % self.key diff --git a/spug_api/apps/setting/urls.py b/spug_api/apps/setting/urls.py index b290ed6..6622927 100644 --- a/spug_api/apps/setting/urls.py +++ b/spug_api/apps/setting/urls.py @@ -7,6 +7,7 @@ from .views import * urlpatterns = [ url(r'^$', SettingView.as_view()), + url(r'^basic/$', get_basic), url(r'^ldap_test/$', ldap_test), url(r'^email_test/$', email_test), url(r'^about/$', get_about) diff --git a/spug_api/apps/setting/utils.py b/spug_api/apps/setting/utils.py index f5540a7..d33b48e 100644 --- a/spug_api/apps/setting/utils.py +++ b/spug_api/apps/setting/utils.py @@ -2,30 +2,30 @@ # Copyright: (c) # Released under the AGPL-3.0 License. from functools import lru_cache -from apps.setting.models import Setting +from apps.setting.models import Setting, KEYS_DEFAULT +import json class AppSetting: - keys = ('public_key', 'private_key', 'mail_service', 'api_key', 'spug_key', 'ldap_service', 'verify_ip') - @classmethod @lru_cache(maxsize=64) def get(cls, key): info = Setting.objects.filter(key=key).first() if not info: raise KeyError(f'no such key for {key!r}') - return info.value + return info.real_val @classmethod def get_default(cls, key, default=None): info = Setting.objects.filter(key=key).first() if not info: return default - return info.value + return info.real_val @classmethod def set(cls, key, value, desc=None): - if key in cls.keys: + if key in KEYS_DEFAULT: + value = json.dumps(value) Setting.objects.update_or_create(key=key, defaults={'value': value, 'desc': desc}) else: raise KeyError('invalid key') diff --git a/spug_api/apps/setting/views.py b/spug_api/apps/setting/views.py index 6c75e11..d43c034 100644 --- a/spug_api/apps/setting/views.py +++ b/spug_api/apps/setting/views.py @@ -15,7 +15,7 @@ import smtplib class SettingView(View): def get(self, request): data = Setting.objects.all() - return json_response(data) + return json_response([x.to_view() for x in data]) def post(self, request): form, error = JsonParser( @@ -63,8 +63,6 @@ def email_test(request): except Exception as e: error = f'{e}' - return json_response(error=error) - return json_response(error=error) @@ -77,4 +75,8 @@ def get_about(request): }) - +def get_basic(request): + keys, data = ('MFA',), {} + for item in Setting.objects.filter(key__in=keys): + data[item.key] = item.real_val + return json_response(data) diff --git a/spug_api/spug/settings.py b/spug_api/spug/settings.py index 9443490..23590a5 100644 --- a/spug_api/spug/settings.py +++ b/spug_api/spug/settings.py @@ -120,6 +120,7 @@ USE_TZ = True AUTHENTICATION_EXCLUDES = ( '/account/login/', + '/setting/basic/', re.compile('/apis/.*'), ) diff --git a/spug_web/package.json b/spug_web/package.json index e35a019..1a2ee8c 100644 --- a/spug_web/package.json +++ b/spug_web/package.json @@ -5,7 +5,7 @@ "dependencies": { "@ant-design/icons": "^4.3.0", "ace-builds": "^1.4.7", - "antd": "^4.9.4", + "antd": "^4.10.3", "axios": "^0.21.0", "bizcharts": "^3.5.9", "history": "^4.10.1", diff --git a/spug_web/src/index.js b/spug_web/src/index.js index 0332da1..08cfe70 100644 --- a/spug_web/src/index.js +++ b/spug_web/src/index.js @@ -13,10 +13,9 @@ import App from './App'; import moment from 'moment'; import 'moment/locale/zh-cn'; import * as serviceWorker from './serviceWorker'; -import { history, updatePermissions } from 'libs'; +import { history } from 'libs'; moment.locale('zh-cn'); -updatePermissions() ReactDOM.render( diff --git a/spug_web/src/pages/login/index.js b/spug_web/src/pages/login/index.js index 4aa6f3c..0f50012 100644 --- a/spug_web/src/pages/login/index.js +++ b/spug_web/src/pages/login/index.js @@ -5,7 +5,7 @@ */ import React, { useState, useEffect } from 'react'; import { Form, Input, Button, Tabs, Modal } from 'antd'; -import { UserOutlined, LockOutlined, CopyrightOutlined, GithubOutlined } from '@ant-design/icons'; +import { UserOutlined, LockOutlined, CopyrightOutlined, GithubOutlined, MailOutlined } from '@ant-design/icons'; import styles from './login.module.css'; import history from 'libs/history'; import { http, updatePermissions } from 'libs'; @@ -18,6 +18,7 @@ import hostStore from 'pages/host/store'; export default function () { const [form] = Form.useForm(); + const [counter, setCounter] = useState(0); const [loading, setLoading] = useState(false); const [loginType, setLoginType] = useState('default'); @@ -30,6 +31,14 @@ export default function () { execStore.hosts = []; }, []) + useEffect(() => { + setTimeout(() => { + if (counter > 0) { + setCounter(counter - 1) + } + }, 1000) + }, [counter]) + function handleSubmit() { setLoading(true); const formData = form.getFieldsValue(); @@ -68,6 +77,10 @@ export default function () { } } + function handleCaptcha() { + setCounter(60); + } + return (
@@ -96,6 +109,22 @@ export default function () { onPressEnter={handleSubmit} prefix={}/> + +
+ + }/> + + {counter > 0 ? ( + + ) : ( + + )} +
+
} diff --git a/spug_web/src/pages/system/setting/BasicSetting.js b/spug_web/src/pages/system/setting/BasicSetting.js index ec1b8aa..26bebc2 100644 --- a/spug_web/src/pages/system/setting/BasicSetting.js +++ b/spug_web/src/pages/system/setting/BasicSetting.js @@ -3,13 +3,51 @@ * Copyright (c) * Released under the AGPL-3.0 License. */ -import React from 'react'; +import React, { useState } from 'react'; +import { observer } from 'mobx-react'; +import { Form, Popover, Input, Button, message } from 'antd'; import styles from './index.module.css'; +import { http } from 'libs'; +import store from './store'; -export default function () { +export default observer(function () { + const [spug_key, setSpugKey] = useState(store.settings.spug_key); + + function handleSubmit() { + if (!spug_key) return message.error('请输入调用凭据'); + store.loading = true; + http.post('/api/setting/', {data: [{key: 'spug_key', value: spug_key}]}) + .then(() => { + message.success('保存成功'); + store.fetchSettings() + }) + .finally(() => store.loading = false) + } + + const spugWx = spug; return (
基本设置
+
+ 如需要使用Spug的邮件、微信和短信等内置服务,请关注公众号 + + + Spug运维 + + + 在【我的】页面获取调用凭据,否则请留空。}> + setSpugKey(e.target.value)} + placeholder="请输入Spug微信公众号获取到的Token"/> + + + + +
) -} +}) diff --git a/spug_web/src/pages/system/setting/KeySetting.js b/spug_web/src/pages/system/setting/KeySetting.js index b02610c..c6a380d 100644 --- a/spug_web/src/pages/system/setting/KeySetting.js +++ b/spug_web/src/pages/system/setting/KeySetting.js @@ -9,8 +9,6 @@ import { Form, Alert, Button, Input, Modal, message } from 'antd'; import styles from './index.module.css'; import http from 'libs/http'; import store from './store'; -import lds from 'lodash'; - export default observer(function () { function handleSubmit() { @@ -28,13 +26,11 @@ export default observer(function () { } function doModify() { - const public_key = lds.get(store.settings, 'public_key.value'); - const private_key = lds.get(store.settings, 'private_key.value'); return http.post('/api/setting/', { - data: [{key: 'public_key', value: public_key}, { - key: 'private_key', - value: private_key - }] + data: [ + {key: 'public_key', value: store.settings.public_key}, + {key: 'private_key', value: store.settings.private_key} + ] }) .then(() => { message.success('保存成功'); @@ -59,19 +55,19 @@ export default observer(function () { lds.set(store.settings, 'public_key.value', e.target.value)} + value={store.settings.public_key} + onChange={e => store.settings.public_key = e.target.value} placeholder="请输入公钥"/> lds.set(store.settings, 'private_key.value', e.target.value)} + value={store.settings.private_key} + onChange={e => store.settings.private_key = e.target.value} placeholder="请输入私钥"/> - + diff --git a/spug_web/src/pages/system/setting/LDAPSetting.js b/spug_web/src/pages/system/setting/LDAPSetting.js index b4185fa..a73390a 100644 --- a/spug_web/src/pages/system/setting/LDAPSetting.js +++ b/spug_web/src/pages/system/setting/LDAPSetting.js @@ -9,18 +9,15 @@ import { Form, Button, Input, Space, message } from 'antd'; import { http } from 'libs'; import { observer } from 'mobx-react' import store from './store'; -import lds from "lodash"; - export default observer(function () { const [form] = Form.useForm(); const [loading, setLoading] = useState(false); - const setting = JSON.parse(lds.get(store.settings, 'ldap_service.value', '{}')); function handleSubmit() { store.loading = true; const formData = form.getFieldsValue(); - http.post('/api/setting/', {data: [{key: 'ldap_service', value: JSON.stringify(formData)}]}) + http.post('/api/setting/', {data: [{key: 'ldap_service', value: formData}]}) .then(() => { message.success('保存成功'); store.fetchSettings() @@ -39,7 +36,8 @@ export default observer(function () { return (
LDAP设置
-
+ diff --git a/spug_web/src/pages/system/setting/OpenService.js b/spug_web/src/pages/system/setting/OpenService.js index 687b539..f3df567 100644 --- a/spug_web/src/pages/system/setting/OpenService.js +++ b/spug_web/src/pages/system/setting/OpenService.js @@ -9,13 +9,11 @@ import { Form, Button, Input, message } from 'antd'; import styles from './index.module.css'; import http from 'libs/http'; import store from './store'; -import lds from 'lodash'; - export default observer(function () { function handleSubmit() { store.loading = true; - const value = lds.get(store.settings, 'api_key.value'); + const value = store.settings.api_key; http.post('/api/setting/', {data: [{key: 'api_key', value}]}) .then(() => { message.success('保存成功'); @@ -30,11 +28,11 @@ export default observer(function () { lds.set(store.settings, 'api_key.value', e.target.value)} + value={store.settings.api_key} + onChange={e => store.settings.api_key = e.target.value} placeholder="请输入自定义Token"/> - + diff --git a/spug_web/src/pages/system/setting/SecuritySetting.js b/spug_web/src/pages/system/setting/SecuritySetting.js index 671967e..a69cc23 100644 --- a/spug_web/src/pages/system/setting/SecuritySetting.js +++ b/spug_web/src/pages/system/setting/SecuritySetting.js @@ -3,18 +3,19 @@ * Copyright (c) * Released under the AGPL-3.0 License. */ -import React from 'react'; +import React, { useState } from 'react'; import { observer } from 'mobx-react'; import { Form, Switch, message } from 'antd'; import styles from './index.module.css'; import http from 'libs/http'; import store from './store'; -import lds from 'lodash'; - export default observer(function () { + const [verify_ip, setVerifyIP] = useState(store.settings.verify_ip); + const [mfa, setMFA] = useState(store.settings.MFA); + function handleChangeVerifyIP(v) { - lds.set(store.settings, 'verify_ip.value', v); + setVerifyIP(v); http.post('/api/setting/', {data: [{key: 'verify_ip', value: v}]}) .then(() => { message.success('设置成功'); @@ -22,23 +23,40 @@ export default observer(function () { }) } - const checked = lds.get(store.settings, 'verify_ip.value') !== 'False' + function handleChangeMFA(v) { + if (v && !store.settings.spug_key) return message.error('开启MFA认证需要先在基本设置中配置调用凭据'); + setMFA({...mfa, enable: v}); + http.post('/api/setting/', {data: [{key: 'MFA', value: {...mfa, enable: v}}]}) + .then(() => { + message.success('设置成功'); + store.fetchSettings() + }) + } return (
安全设置
-
+
+ checked={verify_ip}/> -
+ + + +
) }) diff --git a/spug_web/src/pages/system/setting/store.js b/spug_web/src/pages/system/setting/store.js index 9f15847..e97d0b2 100644 --- a/spug_web/src/pages/system/setting/store.js +++ b/spug_web/src/pages/system/setting/store.js @@ -16,7 +16,7 @@ class Store { http.get('/api/setting/') .then(res => { for (let item of res) { - this.settings[item.key] = item; + this.settings[item.key] = item.value; } }) .finally(() => this.isFetching = false)