From ca3a99a5cfee7cb0c84a60de3aecdfefb587fa6c Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 11 Sep 2023 18:34:44 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20passkey=20(#3382)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 修改 passkey * perf: 完成 passkey * perf: 修改 passkey icon --------- Co-authored-by: ibuler --- src/i18n/langs/zh.json | 3 + src/icons/svg/passkey.svg | 1 + src/router/profile/index.js | 11 ++ src/utils/passkey.js | 80 +++++++++++++++ src/views/profile/PassKey.vue | 153 ++++++++++++++++++++++++++++ src/views/settings/Auth/Passkey.vue | 45 ++++++++ src/views/settings/Auth/index.vue | 9 +- 7 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 src/icons/svg/passkey.svg create mode 100644 src/utils/passkey.js create mode 100644 src/views/profile/PassKey.vue create mode 100644 src/views/settings/Auth/Passkey.vue diff --git a/src/i18n/langs/zh.json b/src/i18n/langs/zh.json index da7a751ff..c2b655a2d 100644 --- a/src/i18n/langs/zh.json +++ b/src/i18n/langs/zh.json @@ -465,6 +465,7 @@ "AfterChange": "变更后" }, "auth": { + "AddPassKey": "添加 Passkey(通行密钥)", "LoginRequiredMsg": "账号已退出,请重新登录", "ReLogin": "重新登录", "ReLoginTitle": "当前三方登录用户(CAS/SAML),未绑定 MFA 且不支持密码校验,请重新登录。", @@ -777,6 +778,7 @@ "nav": { "TempPassword": "临时密码", "ConnectionToken": "连接令牌", + "PassKey": "Passkey", "APIKey": "API Key", "Workbench": "工作台", "Navigation": "导航", @@ -1525,6 +1527,7 @@ "PublishStatus": "发布状态" }, "setting": { + "Passkey": "Passkey", "BlockedIPS": "已锁定的 IP", "ViewBlockedIPSHelpText": "查看已被锁定的 IP 列表", "Unblock": "解锁", diff --git a/src/icons/svg/passkey.svg b/src/icons/svg/passkey.svg new file mode 100644 index 000000000..9aac2b07d --- /dev/null +++ b/src/icons/svg/passkey.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/router/profile/index.js b/src/router/profile/index.js index b6e8687ea..fe2d4033c 100644 --- a/src/router/profile/index.js +++ b/src/router/profile/index.js @@ -76,6 +76,17 @@ export default { permissions: ['authentication.view_connectiontoken'] } }, + { + path: '/profile/passkeys', + component: () => import('@/views/profile/PassKey.vue'), + name: 'Passkey', + meta: { + title: i18n.t('common.nav.PassKey'), + icon: 'passkey', + hidden: ({ settings }) => !settings['AUTH_PASSKEY'], + permissions: ['authentication.view_connectiontoken'] + } + }, { path: '/profile/user/setting', name: 'UserSetting', diff --git a/src/utils/passkey.js b/src/utils/passkey.js new file mode 100644 index 000000000..f0c095b5a --- /dev/null +++ b/src/utils/passkey.js @@ -0,0 +1,80 @@ +const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_' + +// Use a lookup table to find the index. +const lookup = new Uint8Array(256) +for (let i = 0; i < chars.length; i++) { + lookup[chars.charCodeAt(i)] = i +} + +const encode = function(arraybuffer) { + const bytes = new Uint8Array(arraybuffer) + let i; const len = bytes.length; let base64url = '' + + for (i = 0; i < len; i += 3) { + base64url += chars[bytes[i] >> 2] + base64url += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)] + base64url += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)] + base64url += chars[bytes[i + 2] & 63] + } + + if ((len % 3) === 2) { + base64url = base64url.substring(0, base64url.length - 1) + } else if (len % 3 === 1) { + base64url = base64url.substring(0, base64url.length - 2) + } + + return base64url +} + +const decode = function(base64string) { + const bufferLength = base64string.length * 0.75 + const len = base64string.length; let i; let p = 0 + let encoded1; let encoded2; let encoded3; let encoded4 + + const bytes = new Uint8Array(bufferLength) + + for (i = 0; i < len; i += 4) { + encoded1 = lookup[base64string.charCodeAt(i)] + encoded2 = lookup[base64string.charCodeAt(i + 1)] + encoded3 = lookup[base64string.charCodeAt(i + 2)] + encoded4 = lookup[base64string.charCodeAt(i + 3)] + + bytes[p++] = (encoded1 << 2) | (encoded2 >> 4) + bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2) + bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63) + } + + return bytes.buffer +} + +const publicKeyCredentialToJSON = (pubKeyCred) => { + if (pubKeyCred instanceof Array) { + const arr = [] + for (const i of pubKeyCred) { arr.push(publicKeyCredentialToJSON(i)) } + + return arr + } + + if (pubKeyCred instanceof ArrayBuffer) { + return encode(pubKeyCred) + } + + if (pubKeyCred instanceof Object) { + const obj = {} + + for (const key in pubKeyCred) { + obj[key] = publicKeyCredentialToJSON(pubKeyCred[key]) + } + + return obj + } + + return pubKeyCred +} + +export default { + 'decode': decode, + 'encode': encode, + 'publicKeyCredentialToJSON': publicKeyCredentialToJSON +} + diff --git a/src/views/profile/PassKey.vue b/src/views/profile/PassKey.vue new file mode 100644 index 000000000..43097e12e --- /dev/null +++ b/src/views/profile/PassKey.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/src/views/settings/Auth/Passkey.vue b/src/views/settings/Auth/Passkey.vue new file mode 100644 index 000000000..e9b3f11ad --- /dev/null +++ b/src/views/settings/Auth/Passkey.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/src/views/settings/Auth/index.vue b/src/views/settings/Auth/index.vue index 0b8d533bb..564071d39 100644 --- a/src/views/settings/Auth/index.vue +++ b/src/views/settings/Auth/index.vue @@ -20,6 +20,7 @@ import WeCom from './WeCom' import SSO from './SSO' import SAML2 from './SAML2' import OAuth2 from './OAuth2' +import Passkey from './Passkey.vue' export default { components: { @@ -35,7 +36,8 @@ export default { Radius, SSO, SAML2, - OAuth2 + OAuth2, + Passkey }, data() { let extraBackends = [] @@ -96,6 +98,11 @@ export default { name: 'CAS', key: 'AUTH_CAS' }, + { + title: this.$t('setting.Passkey'), + name: 'Passkey', + key: 'AUTH_PASSKEY' + }, ...extraBackends ] }