From 78937d16d217d9b506379c19aff4371da0958ca6 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Mon, 2 Nov 2020 00:02:40 +0800 Subject: [PATCH] add 2fa settings --- package-lock.json | 41 +++ package.json | 1 + pkg/api/twofactor_authorizations.go | 51 +++- pkg/models/two_factor.go | 8 + src/lib/services.js | 22 ++ src/locales/en.js | 16 + src/locales/zh_Hans.js | 16 + src/mobile-main.js | 2 + src/router/mobile.js | 6 + src/views/mobile/About.vue | 6 + src/views/mobile/Settings.vue | 2 +- src/views/mobile/users/TwoFactorAuth.vue | 372 +++++++++++++++++++++++ 12 files changed, 535 insertions(+), 8 deletions(-) create mode 100644 src/views/mobile/users/TwoFactorAuth.vue diff --git a/package-lock.json b/package-lock.json index 8cb2bb70..773b0f12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3394,6 +3394,16 @@ "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true }, + "clipboard": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz", + "integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==", + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, "clipboardy": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", @@ -4387,6 +4397,11 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -5726,6 +5741,14 @@ "slash": "^2.0.0" } }, + "good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "requires": { + "delegate": "^3.1.2" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -9453,6 +9476,11 @@ "ajv-keywords": "^3.5.2" } }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -10517,6 +10545,11 @@ "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "dev": true }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -11020,6 +11053,14 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz", "integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==" }, + "vue-clipboard2": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/vue-clipboard2/-/vue-clipboard2-0.3.1.tgz", + "integrity": "sha512-H5S/agEDj0kXjUb5GP2c0hCzIXWRBygaWLN3NEFsaI9I3uWin778SFEMt8QRXiPG+7anyjqWiw2lqcxWUSfkYg==", + "requires": { + "clipboard": "^2.0.0" + } + }, "vue-eslint-parser": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz", diff --git a/package.json b/package.json index f6b57136..631c2d31 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "js-cookie": "^2.2.1", "moment": "^2.29.1", "vue": "^2.6.11", + "vue-clipboard2": "^0.3.1", "vue-i18n": "^8.22.0", "vue-i18n-filter": "^0.1.6", "vue-moment": "^4.1.0" diff --git a/pkg/api/twofactor_authorizations.go b/pkg/api/twofactor_authorizations.go index 1981dc59..fbc177cb 100644 --- a/pkg/api/twofactor_authorizations.go +++ b/pkg/api/twofactor_authorizations.go @@ -203,7 +203,29 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableConfirmHandler(c *core.Conte } func (a *TwoFactorAuthorizationsApi) TwoFactorDisableHandler(c *core.Context) (interface{}, *errs.Error) { + var disableReq models.TwoFactorDisableRequest + err := c.ShouldBindJSON(&disableReq) + + if err != nil { + log.WarnfWithRequestId(c, "[twofactor_authorizations.TwoFactorDisableHandler] parse request failed, because %s", err.Error()) + return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) + } + uid := c.GetCurrentUid() + user, err := a.users.GetUserById(uid) + + if err != nil { + if !errs.IsCustomError(err) { + log.WarnfWithRequestId(c, "[twofactor_authorizations.TwoFactorDisableHandler] failed to get user for user \"uid:%d\", because %s", uid, err.Error()) + } + + return nil, errs.ErrUserNotFound + } + + if !a.users.IsPasswordEqualsUserPassword(disableReq.Password, user) { + return nil, errs.ErrUserPasswordWrong + } + enableTwoFactor, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(uid) if err != nil { @@ -235,7 +257,29 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorDisableHandler(c *core.Context) (i } func (a *TwoFactorAuthorizationsApi) TwoFactorRecoveryCodeRegenerateHandler(c *core.Context) (interface{}, *errs.Error) { + var regenerateReq models.TwoFactorRegenerateRecoveryCodeRequest + err := c.ShouldBindJSON(®enerateReq) + + if err != nil { + log.WarnfWithRequestId(c, "[twofactor_authorizations.TwoFactorRecoveryCodeRegenerateHandler] parse request failed, because %s", err.Error()) + return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) + } + uid := c.GetCurrentUid() + user, err := a.users.GetUserById(uid) + + if err != nil { + if !errs.IsCustomError(err) { + log.WarnfWithRequestId(c, "[twofactor_authorizations.TwoFactorRecoveryCodeRegenerateHandler] failed to get user for user \"uid:%d\", because %s", uid, err.Error()) + } + + return nil, errs.ErrUserNotFound + } + + if !a.users.IsPasswordEqualsUserPassword(regenerateReq.Password, user) { + return nil, errs.ErrUserPasswordWrong + } + enableTwoFactor, err := a.twoFactorAuthorizations.ExistsTwoFactorSetting(uid) if err != nil { @@ -254,13 +298,6 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorRecoveryCodeRegenerateHandler(c *c return nil, errs.Or(err, errs.ErrOperationFailed) } - user, err := a.users.GetUserById(uid) - - if err != nil { - log.WarnfWithRequestId(c, "[twofactor_authorizations.TwoFactorRecoveryCodeRegenerateHandler] failed to get user for user \"uid:%d\", because %s", uid, err.Error()) - return nil, errs.ErrUserNotFound - } - err = a.twoFactorAuthorizations.CreateTwoFactorRecoveryCodes(uid, recoveryCodes, user.Salt) if err != nil { diff --git a/pkg/models/two_factor.go b/pkg/models/two_factor.go index 9838f0c6..0922389c 100644 --- a/pkg/models/two_factor.go +++ b/pkg/models/two_factor.go @@ -25,6 +25,14 @@ type TwoFactorEnableConfirmResponse struct { RecoveryCodes []string `json:"recoveryCodes"` } +type TwoFactorDisableRequest struct { + Password string `json:"password" binding:"omitempty,min=6,max=128"` +} + +type TwoFactorRegenerateRecoveryCodeRequest struct { + Password string `json:"password" binding:"omitempty,min=6,max=128"` +} + type TwoFactorStatusResponse struct { Enable bool `json:"enable"` CreatedAt int64 `json:"createdAt,omitempty"` diff --git a/src/lib/services.js b/src/lib/services.js index 395c0082..bf8a40ba 100644 --- a/src/lib/services.js +++ b/src/lib/services.js @@ -135,4 +135,26 @@ export default { oldPassword }); }, + get2FAStatus: () => { + return axios.get('v1/users/2fa/status.json'); + }, + enable2FA: () => { + return axios.post('v1/users/2fa/enable/request.json'); + }, + confirmEnable2FA: ({ secret, passcode }) => { + return axios.post('v1/users/2fa/enable/confirm.json', { + secret, + passcode + }); + }, + disable2FA: ({ password }) => { + return axios.post('v1/users/2fa/disable.json', { + password + }); + }, + regenerate2FARecoveryCode: ({ password }) => { + return axios.post('v1/users/2fa/recovery/regenerate.json', { + password + }); + }, }; diff --git a/src/locales/en.js b/src/locales/en.js index 0d19bd9d..585dbabc 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -46,6 +46,7 @@ export default { 'parameter': { 'username': 'Username', 'password': 'Password', + 'passcode': 'Passcode', 'email': 'Email', 'nickname': 'Nickname', 'oldPassword': 'Current Password', @@ -66,6 +67,11 @@ export default { 'Update': 'Update', 'Done': 'Done', 'Continue': 'Continue', + 'Status': 'Status', + 'Enable': 'Enable', + 'Enabled': 'Enabled', + 'Disable': 'Disable', + 'Disabled': 'Disabled', 'Version': 'Version', 'User': 'User', 'Application': 'Application', @@ -128,6 +134,16 @@ export default { 'Other Device': 'Other Device', 'Are you sure you want to logout from this session?': 'Are you sure you want to logout from this session?', 'Unable to logout from this session': 'Unable to logout from this session', + 'Regenerate Backup Codes': 'Regenerate Backup Codes', + 'Please use two factor authentication app scan the below qrcode and input current passcode': 'Please use two factor authentication app scan the below qrcode and input current passcode', + 'Please enter your current password when disable two factor authentication': 'Please enter your current password when disable two factor authentication', + 'Please enter your current password when regenerate two factor authentication backup codes. If you regenerate backup codes, the old codes will be invalidated.': 'Please enter your current password when regenerate two factor authentication backup codes. If you regenerate backup codes, the old codes will be invalidated.', + 'Please copy these backup codes to safe place, the below codes can only be shown once. If these codes were lost, you can regenerate backup codes at any time.': 'Please copy these backup codes to safe place, the below codes can only be shown once. If these codes were lost, you can regenerate backup codes at any time.', + 'Backup codes copied': 'Backup codes copied', + 'Two factor authentication has been disabled': 'Two factor authentication has been disabled', + 'Unable to get current two factor authentication status': 'Unable to get current two factor authentication status', + 'Unable to enable two factor authentication': 'Unable to enable two factor authentication', + 'Unable to disable two factor authentication': 'Unable to disable two factor authentication', 'Log Out': 'Log Out', 'Are you sure you want to log out?': 'Are you sure you want to log out?', 'Unable to logout': 'Unable to logout', diff --git a/src/locales/zh_Hans.js b/src/locales/zh_Hans.js index 98fec517..1648dfdf 100644 --- a/src/locales/zh_Hans.js +++ b/src/locales/zh_Hans.js @@ -46,6 +46,7 @@ export default { 'parameter': { 'username': '用户名', 'password': '密码', + 'passcode': '验证码', 'email': '电子邮箱', 'nickname': '昵称', 'oldPassword': '当前密码', @@ -66,6 +67,11 @@ export default { 'Update': '更新', 'Done': '完成', 'Continue': '继续', + 'Status': '状态', + 'Enable': '启用', + 'Enabled': '启用', + 'Disable': '禁用', + 'Disabled': '禁用', 'Version': '版本', 'User': '用户', 'Application': '应用', @@ -128,6 +134,16 @@ export default { 'Other Device': '其他设备', 'Are you sure you want to logout from this session?': '您确定是否要退出该会话?', 'Unable to logout from this session': '无法退出该会话', + 'Regenerate Backup Codes': '重新生成备用码', + 'Please use two factor authentication app scan the below qrcode and input current passcode': '请使用两步验证应用扫描下方的二维码并输入当前的验证码', + 'Please enter your current password when disable two factor authentication': '禁用两步验证时需要输入您的当前密码', + 'Please enter your current password when regenerate two factor authentication backup codes. If you regenerate backup codes, the old codes will be invalidated.': '重新生成两步验证备用码时需要输入您的当前密码。如果您重新生成备用码,之前的备用码将失效。', + 'Please copy these backup codes to safe place, the below codes can only be shown once. If these codes were lost, you can regenerate backup codes at any time.': '请将备用码复制到安全的地方,下列备用码只会展示一次。如果这些备用码丢失,您可以随时重新生成备用码。', + 'Backup codes copied': '备用码已复制', + 'Two factor authentication has been disabled': '两步验证已经禁用', + 'Unable to get current two factor authentication status': '无法获取当前两步验证状态', + 'Unable to enable two factor authentication': '无法启用两步验证', + 'Unable to disable two factor authentication': '无法禁用两步验证', 'Log Out': '退出登录', 'Are you sure you want to log out?': '您确定是否要退出登录?', 'Unable to logout': '无法退出登录', diff --git a/src/mobile-main.js b/src/mobile-main.js index 101ccaad..75d98f34 100644 --- a/src/mobile-main.js +++ b/src/mobile-main.js @@ -4,6 +4,7 @@ import VueI18nFilter from 'vue-i18n-filter' import Framework7 from 'framework7/framework7.esm.bundle.js'; import Framework7Vue from 'framework7-vue/framework7-vue.esm.bundle.js'; import VueMoment from 'vue-moment'; +import VueClipboard from 'vue-clipboard2'; import moment from 'moment'; import 'moment/min/locales'; @@ -21,6 +22,7 @@ import App from './Mobile.vue'; Vue.use(VueI18n); Vue.use(VueI18nFilter); Vue.use(VueMoment, { moment }); +Vue.use(VueClipboard); Framework7.use(Framework7Vue); const i18n = new VueI18n(getI18nOptions()); diff --git a/src/router/mobile.js b/src/router/mobile.js index e725a50a..b95f7e3c 100644 --- a/src/router/mobile.js +++ b/src/router/mobile.js @@ -8,6 +8,7 @@ import SignUpPage from '../views/mobile/Signup.vue'; import SettingsPage from '../views/mobile/Settings.vue'; import AboutPage from "../views/mobile/About.vue"; import UserProfilePage from "../views/mobile/users/UserProfile.vue"; +import TwoFactorAuthPage from "../views/mobile/users/TwoFactorAuth.vue"; import SessionListPage from "../views/mobile/users/SessionList.vue"; function checkLogin(to, from, resolve, reject) { @@ -73,6 +74,11 @@ const routes = [ component: UserProfilePage, beforeEnter: checkLogin }, + { + path: '/user/2fa', + component: TwoFactorAuthPage, + beforeEnter: checkLogin + }, { path: '/user/sessions', component: SessionListPage, diff --git a/src/views/mobile/About.vue b/src/views/mobile/About.vue index 65160920..314b5780 100644 --- a/src/views/mobile/About.vue +++ b/src/views/mobile/About.vue @@ -151,6 +151,12 @@ https://github.com/brockpetrie/vue-moment
License: https://github.com/brockpetrie/vue-moment/blob/master/LICENSE

+

+ vue-clipboard2
+ Copyright (c) 2017 Inndy <inndy \dot tw \at gmail \dot com>
+ https://github.com/Inndy/vue-clipboard2
+ License: https://github.com/Inndy/vue-clipboard2/blob/master/LICENSE +

core-js
Copyright (c) 2014-2020 Denis Pushkarev
diff --git a/src/views/mobile/Settings.vue b/src/views/mobile/Settings.vue index 3ad512e2..d3d8f329 100644 --- a/src/views/mobile/Settings.vue +++ b/src/views/mobile/Settings.vue @@ -4,7 +4,7 @@ {{ userNickName }} - + {{ $t('Log Out') }} diff --git a/src/views/mobile/users/TwoFactorAuth.vue b/src/views/mobile/users/TwoFactorAuth.vue new file mode 100644 index 00000000..00efdaf5 --- /dev/null +++ b/src/views/mobile/users/TwoFactorAuth.vue @@ -0,0 +1,372 @@ + + + + +