From 3f22a86788a87f9c0588f08ed2496adf5e039c9a Mon Sep 17 00:00:00 2001 From: v_yutyi <a670003190@qq.com> Date: Fri, 25 Nov 2022 17:47:53 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20rsa=20=E5=89=8D=E7=AB=AF=E5=8A=A0?= =?UTF-8?q?=E5=AF=86=20#808?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/package.json | 1 + src/pages/src/common/rsa.js | 30 ++++++++ src/pages/src/main.js | 2 + src/pages/src/store/modules/password.js | 4 + .../organization/details/UserMaterial.vue | 37 +++++++-- src/pages/src/views/password/Modify.vue | 70 +++++++++++++++-- src/pages/src/views/password/Set.vue | 76 +++++++++++++++---- 7 files changed, 194 insertions(+), 26 deletions(-) create mode 100644 src/pages/src/common/rsa.js diff --git a/src/pages/package.json b/src/pages/package.json index 4460c5058..2fe2fb4c8 100644 --- a/src/pages/package.json +++ b/src/pages/package.json @@ -57,6 +57,7 @@ "express-art-template": "1.0.1", "intl-tel-input": "16.0.0", "js-base64": "^3.7.2", + "jsencrypt": "^3.3.1", "jsonp": "0.2.1", "query-string": "6.5.0", "sortablejs": "1.10.1", diff --git a/src/pages/src/common/rsa.js b/src/pages/src/common/rsa.js new file mode 100644 index 000000000..610c688eb --- /dev/null +++ b/src/pages/src/common/rsa.js @@ -0,0 +1,30 @@ +/** +* by making 蓝鲸智云-用户管理(Bk-User) available. +* Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +* Licensed under the MIT License (the "License"); +* you may not use this file except in compliance with the License. You may obtain a copy of the License at +* http://opensource.org/licenses/MIT +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and limitations under the License. +*/ +/* 数据RSA加密 */ +import JSEncrypt from 'jsencrypt'; + +export default { + // JSEncrypt 加密 + rsaPublicData(data, publicKey) { + const jsencrypt = new JSEncrypt(); + jsencrypt.setPublicKey(publicKey); + const result = jsencrypt.encrypt(data); + return result; + }, + // JSEncrypt 解密 + rsaPrivateData(data, privateKey) { + const jsencrypt = new JSEncrypt(); + jsencrypt.setPrivateKey(privateKey); + const result = jsencrypt.encrypt(data); + return result; + }, +}; diff --git a/src/pages/src/main.js b/src/pages/src/main.js index 918b408f8..081f9bc4f 100644 --- a/src/pages/src/main.js +++ b/src/pages/src/main.js @@ -26,6 +26,7 @@ import bus from '@/common/bus'; import cursor from '@/directives/cursor'; import { Base64 } from 'js-base64'; import xss from 'xss'; +import Rsa from '@/common/rsa'; Vue.component(VueCropper); Vue.use(vClickOutside); @@ -35,6 +36,7 @@ Vue.directive('cursor', cursor); Vue.config.devtools = true; Vue.prototype.$bus = new Vue(); Vue.use(Base64); +Vue.prototype.Rsa = Rsa; Vue.prototype.$xss = (html) => { const attrs = ['class', 'title', 'target', 'style', 'src', 'onerror']; return xss(html || '', { diff --git a/src/pages/src/store/modules/password.js b/src/pages/src/store/modules/password.js index b9edf642c..5dc789bad 100644 --- a/src/pages/src/store/modules/password.js +++ b/src/pages/src/store/modules/password.js @@ -41,5 +41,9 @@ export default { sendCode(context, params, config = {}) { return http.post('api/v1/web/passwords/reset/verification_code/verify/', params); }, + // 获取rsa公钥 + getRsa(context, params, config = {}) { + return http.get(`api/v1/web/passwords/settings/by_token/?token=${params}`); + }, }, }; diff --git a/src/pages/src/views/organization/details/UserMaterial.vue b/src/pages/src/views/organization/details/UserMaterial.vue index f5ac860a0..082a3220f 100644 --- a/src/pages/src/views/organization/details/UserMaterial.vue +++ b/src/pages/src/views/organization/details/UserMaterial.vue @@ -143,6 +143,7 @@ <script> import { dateConvert } from '@/common/util'; +const Base64 = require('js-base64').Base64; export default { directives: { focus: { @@ -200,6 +201,10 @@ export default { newPassword: 'password', }, passwordRules: null, + // 公钥 + publicKey: '', + // 是否rsa加密 + isRsaEncrypted: false, }; }, computed: { @@ -304,7 +309,7 @@ export default { this.phoneNumber = this.currentProfile.telephone; }, // 重置密码 - showResetDialog() { + async showResetDialog() { if (this.isForbid) { return; } @@ -312,6 +317,19 @@ export default { // 清空上次输入 this.oldPassword = ''; this.newPassword = ''; + const res = await this.$store.dispatch('catalog/ajaxGetPassport', { + id: this.currentCategoryId, + }); + if (res.data) { + res.data.forEach((item) => { + if (item.key === 'enable_password_rsa_encrypted') { + this.isRsaEncrypted = true; + } + if (item.key === 'password_rsa_public_key') { + this.publicKey = Base64.decode(item.value); + } + }); + } }, // 验证密码的格式 async confirmReset() { @@ -381,10 +399,19 @@ export default { if (this.isAdmin) { passwordData.old_password = this.oldPassword.trim(); }; - await this.$store.dispatch('organization/patchProfile', { - id: this.currentProfile.id, - data: passwordData, - }); + if (this.isRsaEncrypted) { + await this.$store.dispatch('organization/patchProfile', { + id: this.currentProfile.id, + data: { + password: this.Rsa.rsaPublicData(passwordData, this.publicKey), + }, + }); + } else { + await this.$store.dispatch('organization/patchProfile', { + id: this.currentProfile.id, + data: passwordData, + }); + } this.$bkMessage({ message: this.$t('重置密码成功'), theme: 'success', diff --git a/src/pages/src/views/password/Modify.vue b/src/pages/src/views/password/Modify.vue index 59357c3d0..9657fa036 100644 --- a/src/pages/src/views/password/Modify.vue +++ b/src/pages/src/views/password/Modify.vue @@ -27,7 +27,7 @@ </div> <div class="modify-content" data-test-id="passwordInfo"> <h4 class="common-title">{{$t('更改密码')}}</h4> - <p class="error-text" v-if="isConfirmError"> + <p class="error-text" v-if="isConfirmError || isCorrectPw"> <i class="icon icon-user-exclamation-circle-shape"></i> <span class="text">{{errorText}}</span> </p> @@ -42,18 +42,18 @@ <li class="input-list"> <input type="password" - :class="['select-text', { 'input-error': isConfirmError }]" + :class="['select-text', { 'input-error': isConfirmError || isCorrectPw }]" :placeholder="$t('新密码')" v-model="newPassword" - @focus="isConfirmError = false" /> + @focus="handleFocus" /> </li> <li class="input-list"> <input type="password" - :class="['select-text', { 'input-error': isConfirmError }]" + :class="['select-text', { 'input-error': isConfirmError || isCorrectPw }]" :placeholder="$t('确认新密码')" v-model="confirmPassword" - @focus="isConfirmError = false" /> + @focus="handleFocus" /> </li> </ul> <bk-button @@ -85,7 +85,7 @@ export default { data() { return { isConfirmError: false, - errorText: this.$t('两次输入的密码不一致,请重新输入'), + errorText: '', oldPassword: '', newPassword: '', confirmPassword: '', @@ -93,18 +93,68 @@ export default { isShow: false, title: this.$t('密码修改成功'), }, + // 公钥 + publicKey: '', + // 是否rsa加密 + isRsaEncrypted: false, + passwordRules: { + passwordMinLength: 0, + passwordMustIncludes: [], + }, + isCorrectPw: false, }; }, + mounted() { + this.initRsa(); + }, methods: { + async initRsa() { + try { + const res = await this.$store.dispatch('password/getRsa', this.$route.query.token); + if (res.data) { + res.data.forEach((item) => { + switch (item.key) { + case 'enable_password_rsa_encrypted': + return this.isRsaEncrypted = true; + case 'password_rsa_public_key': + return this.publicKey = Base64.decode(item.value); + case 'password_min_length': + return this.passwordRules.passwordMinLength = item.value; + case 'password_must_includes': + return this.passwordRules.passwordMustIncludes = item.value; + } + }); + } + } catch (e) { + console.warn(e); + } + }, async handlePush() { try { + // 确认密码是否一致 if (this.newPassword !== this.confirmPassword) { this.isConfirmError = true; + this.errorText = this.$t('两次输入的密码不一致,请重新输入'); + return; + } + // 校验密码规则 + this.isCorrectPw = !this.$validatePassportByRules(this.newPassword, this.passwordRules); + if (this.isCorrectPw) { + this.errorText = this.$getMessageByRules(this, this.passwordRules); return; } + if (this.isRsaEncrypted) { + this.oldPassword = Base64.encode(this.Rsa.rsaPublicData(this.oldPassword.trim(), this.publicKey)); + this.newPassword = Base64.encode(this.Rsa.rsaPublicData(this.newPassword.trim(), this.publicKey)); + this.confirmPassword = Base64.encode(this.Rsa.rsaPublicData(this.confirmPassword.trim(), this.publicKey)); + } else { + this.oldPassword = Base64.encode(this.oldPassword.trim()); + this.newPassword = Base64.encode(this.newPassword.trim()); + this.confirmPassword = Base64.encode(this.confirmPassword.trim()); + } const modifyParams = { - old_password: Base64.encode(this.oldPassword), - new_password: Base64.encode(this.confirmPassword.trim()), + old_password: this.oldPassword, + new_password: this.confirmPassword, }; await this.$store.dispatch('password/modify', modifyParams); this.successDialog.isShow = true; @@ -115,6 +165,10 @@ export default { register() { window.location.href = window.login_url; }, + handleFocus() { + this.isError = false; + this.isCorrectPw = false; + }, }, }; </script> diff --git a/src/pages/src/views/password/Set.vue b/src/pages/src/views/password/Set.vue index 34e6632b6..492d36917 100644 --- a/src/pages/src/views/password/Set.vue +++ b/src/pages/src/views/password/Set.vue @@ -31,25 +31,26 @@ <h4 class="common-title">{{$t('设置新密码')}}</h4> <p v-if="setPasswordText" - :class="['text', isError && 'show-error-info']"> - {{setPasswordText}}{{$t('_需要设置新密码')}} - </p> - <p class="error-text" v-if="isError"> + :class="['text', isError && 'show-error-info']">{{setPasswordText}}{{$t('_需要设置新密码')}}</p> + <p + v-else + :class="['text', isError && 'show-error-info']">{{$t('请输入新密码进行密码重设')}}</p> + <p class="error-text" v-if="isError || isCorrectPw"> <i class="icon icon-user-exclamation-circle-shape"></i> <span class="text">{{errorText}}</span> </p> <input type="password" - :class="['select-text', { 'input-error': isError }]" + :class="['select-text', { 'input-error': isError || isCorrectPw }]" :placeholder="$t('请输入新密码')" v-model="password" - @focus="isError = false" /> + @focus="handleFocus" /> <input type="password" - :class="['select-text', { 'input-error': isError }]" + :class="['select-text', { 'input-error': isError || isCorrectPw }]" :placeholder="$t('请再次确认新密码')" v-model="confirmPassword" - @focus="isError = false" /> + @focus="handleFocus" /> <bk-button theme="primary" class="submit" :disabled="!password || !confirmPassword" @click="handlePush">{{$t('提交')}}</bk-button> @@ -88,17 +89,27 @@ export default { password: '', confirmPassword: '', isError: false, - errorText: this.$t('两次输入的密码不一致,请重新输入'), + errorText: '', successDialog: { isShow: false, title: this.$t('密码修改成功'), }, setPasswordText: (this.$route.query.data || '').substring(1, (this.$route.query.data || '').length - 1), + // 是否rsa加密 + isRsaEncrypted: false, + // 公钥 + publicKey: '', + passwordRules: { + passwordMinLength: 0, + passwordMustIncludes: [], + }, + isCorrectPw: false, }; }, - // mounted () { - // this.initToken() - // }, + mounted() { + this.initRsa(); + // this.initToken() + }, methods: { // async initToken () { // try { @@ -116,16 +127,51 @@ export default { // }) // } // }, + async initRsa() { + try { + const res = await this.$store.dispatch('password/getRsa', this.$route.query.token); + if (res.data) { + res.data.forEach((item) => { + switch (item.key) { + case 'enable_password_rsa_encrypted': + return this.isRsaEncrypted = true; + case 'password_rsa_public_key': + return this.publicKey = Base64.decode(item.value); + case 'password_min_length': + return this.passwordRules.passwordMinLength = item.value; + case 'password_must_includes': + return this.passwordRules.passwordMustIncludes = item.value; + } + }); + } + } catch (e) { + console.warn(e); + } + }, async handlePush() { try { // 确认密码是否一致 if (this.password !== this.confirmPassword) { this.isError = true; + this.errorText = this.$t('两次输入的密码不一致,请重新输入'); return; } + // 校验密码规则 + this.isCorrectPw = !this.$validatePassportByRules(this.password, this.passwordRules); + if (this.isCorrectPw) { + this.errorText = this.$getMessageByRules(this, this.passwordRules); + return; + } + if (this.isRsaEncrypted) { + this.password = Base64.encode(this.Rsa.rsaPublicData(this.password.trim(), this.publicKey)); + this.confirmPassword = Base64.encode(this.Rsa.rsaPublicData(this.confirmPassword.trim(), this.publicKey)); + } else { + this.password = Base64.encode(this.password.trim()); + this.confirmPassword = Base64.encode(this.confirmPassword.trim()); + } const sureParam = { token: this.$route.query.token, - password: Base64.encode(this.password.trim()), + password: this.password, }; await this.$store.dispatch('password/setByToken', sureParam); this.successDialog.isShow = true; @@ -137,6 +183,10 @@ export default { register() { window.location.href = window.login_url; }, + handleFocus() { + this.isError = false; + this.isCorrectPw = false; + }, }, }; </script>