mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-23 00:00:52 +00:00
perf: Optimize passkey prompt information (#11798)
This commit is contained in:
@@ -1942,7 +1942,7 @@ const message = {
|
||||
passkey: 'Passkey',
|
||||
passkeyManage: 'Manage',
|
||||
passkeyHelper: 'For quick login, up to 5 passkeys can be bound',
|
||||
passkeyRequireSSL: 'Enable HTTPS to use passkeys',
|
||||
passkeyRequireSSL: 'Enable SSL with a trusted certificate and bind a domain name before using passkeys',
|
||||
passkeyNotSupported: 'Current browser or environment does not support passkeys',
|
||||
passkeyCount: 'Bound {0}/{1}',
|
||||
passkeyName: 'Name',
|
||||
|
||||
@@ -1955,8 +1955,8 @@ const message = {
|
||||
passkey: 'Passkey',
|
||||
passkeyManage: 'Administrar',
|
||||
passkeyHelper: 'Para inicio rápido, se pueden vincular hasta 5 passkeys',
|
||||
passkeyRequireSSL: 'Habilita HTTPS para usar passkeys',
|
||||
passkeyNotSupported: 'El navegador o entorno actual no admite Passkey',
|
||||
passkeyRequireSSL: 'Activa SSL con un certificado de confianza y vincula un dominio para usar passkeys',
|
||||
passkeyNotSupported: 'El navegador o entorno actual no admite passkeys',
|
||||
passkeyCount: 'Vinculadas {0}/{1}',
|
||||
passkeyName: 'Nombre',
|
||||
passkeyNameHelper: 'Introduce un nombre para distinguir dispositivos',
|
||||
|
||||
@@ -1867,7 +1867,7 @@ const message = {
|
||||
passkey: 'パスキー',
|
||||
passkeyManage: '管理',
|
||||
passkeyHelper: '素早くログインするため、最大 5 個のパスキーを登録できます',
|
||||
passkeyRequireSSL: 'HTTPS を有効にするとパスキーを使用できます',
|
||||
passkeyRequireSSL: '信頼された証明書で SSL を有効化し、ドメインをバインドしてからパスキーを使用できます',
|
||||
passkeyNotSupported: '現在のブラウザまたは環境はパスキーに対応していません',
|
||||
passkeyCount: '登録済み {0}/{1}',
|
||||
passkeyName: '名称',
|
||||
|
||||
@@ -1843,7 +1843,7 @@ const message = {
|
||||
passkey: '패스키',
|
||||
passkeyManage: '관리',
|
||||
passkeyHelper: '빠른 로그인을 위해 최대 5개의 패스키를 등록할 수 있습니다',
|
||||
passkeyRequireSSL: 'HTTPS를 활성화하면 패스키를 사용할 수 있습니다',
|
||||
passkeyRequireSSL: '신뢰할 수 있는 인증서로 SSL을 활성화하고 도메인을 바인딩한 후 패스키를 사용할 수 있습니다',
|
||||
passkeyNotSupported: '현재 브라우저 또는 환경에서 패스키를 지원하지 않습니다',
|
||||
passkeyCount: '등록됨 {0}/{1}',
|
||||
passkeyName: '이름',
|
||||
|
||||
@@ -1921,7 +1921,7 @@ const message = {
|
||||
passkey: 'Passkey',
|
||||
passkeyManage: 'Urus',
|
||||
passkeyHelper: 'Untuk log masuk pantas, maksimum 5 passkey boleh dipautkan',
|
||||
passkeyRequireSSL: 'Aktifkan HTTPS untuk menggunakan passkey',
|
||||
passkeyRequireSSL: 'Aktifkan SSL dengan sijil dipercayai dan ikat nama domain untuk menggunakan passkey',
|
||||
passkeyNotSupported: 'Pelayar atau persekitaran semasa tidak menyokong passkey',
|
||||
passkeyCount: 'Dipaut {0}/{1}',
|
||||
passkeyName: 'Nama',
|
||||
|
||||
@@ -1909,7 +1909,7 @@ const message = {
|
||||
passkey: 'Passkey',
|
||||
passkeyManage: 'Gerenciar',
|
||||
passkeyHelper: 'Para login rápido, é possível vincular até 5 passkeys',
|
||||
passkeyRequireSSL: 'Ative o HTTPS para usar passkeys',
|
||||
passkeyRequireSSL: 'Ative o SSL com um certificado confiável e vincule um domínio para usar passkeys',
|
||||
passkeyNotSupported: 'O navegador ou ambiente atual não suporta passkeys',
|
||||
passkeyCount: 'Vinculadas {0}/{1}',
|
||||
passkeyName: 'Nome',
|
||||
|
||||
@@ -1910,7 +1910,7 @@ const message = {
|
||||
passkey: 'Passkey',
|
||||
passkeyManage: 'Управление',
|
||||
passkeyHelper: 'Для быстрого входа можно привязать до 5 passkey',
|
||||
passkeyRequireSSL: 'Включите HTTPS, чтобы использовать passkey',
|
||||
passkeyRequireSSL: 'Включите SSL с доверенным сертификатом и привяжите домен, чтобы использовать passkey',
|
||||
passkeyNotSupported: 'Текущий браузер или среда не поддерживает passkey',
|
||||
passkeyCount: 'Привязано {0}/{1}',
|
||||
passkeyName: 'Название',
|
||||
|
||||
@@ -1962,7 +1962,7 @@ const message = {
|
||||
passkey: 'Passkey',
|
||||
passkeyManage: 'Yönet',
|
||||
passkeyHelper: 'Hızlı giriş için en fazla 5 passkey bağlanabilir',
|
||||
passkeyRequireSSL: 'Passkey kullanmak için HTTPS’i etkinleştirin',
|
||||
passkeyRequireSSL: 'Passkey kullanmak için güvenilir sertifika ile SSL’yi etkinleştirin ve alan adını bağlayın',
|
||||
passkeyNotSupported: 'Mevcut tarayıcı veya ortam passkey desteklemiyor',
|
||||
passkeyCount: 'Bağlı {0}/{1}',
|
||||
passkeyName: 'Ad',
|
||||
|
||||
@@ -1903,9 +1903,8 @@ const message = {
|
||||
passkey: '通行密鑰',
|
||||
passkeyManage: '管理',
|
||||
passkeyHelper: '用於快速登入,最多可綁定 5 個通行密鑰',
|
||||
passkeyRequireSSL: '啟用 HTTPS 後可使用通行密鑰',
|
||||
passkeyNotSupported:
|
||||
'目前瀏覽器或環境不支援通行密鑰 \n 請確認您已綁定域名並啟用面板 SSL, 並且訪問時使用了可信的證書',
|
||||
passkeyRequireSSL: '使用可信證書啟用 SSL 並且設定網域綁定後才可使用通行密鑰',
|
||||
passkeyNotSupported: '目前瀏覽器或環境不支援通行密鑰',
|
||||
passkeyCount: '已綁定 {0}/{1}',
|
||||
passkeyName: '名稱',
|
||||
passkeyNameHelper: '請輸入用於區分裝置的名稱',
|
||||
|
||||
@@ -1950,9 +1950,8 @@ const message = {
|
||||
passkey: '通行密钥',
|
||||
passkeyManage: '管理',
|
||||
passkeyHelper: '用于快速登录,最多可绑定 5 个通行密钥',
|
||||
passkeyRequireSSL: '开启 HTTPS 后可使用通行密钥',
|
||||
passkeyNotSupported:
|
||||
'当前浏览器或环境不支持通行密钥 \n 请确认您已经设置域名绑定并启用了面板 SSL, 并且访问时使用了可信的证书',
|
||||
passkeyRequireSSL: '使用可信证书开启 SSL 并且设置域名绑定后才可使用通行密钥',
|
||||
passkeyNotSupported: '当前浏览器或环境不支持通行密钥',
|
||||
passkeyCount: '已绑定 {0}/{1}',
|
||||
passkeyName: '名称',
|
||||
passkeyNameHelper: '请输入用于区分设备的名称',
|
||||
@@ -1961,7 +1960,7 @@ const message = {
|
||||
passkeyLastUsedAt: '最近使用',
|
||||
passkeyDeleteConfirm: '删除后将无法使用该通行密钥登录,是否继续?',
|
||||
passkeyLimit: '最多可绑定 5 个通行密钥',
|
||||
passkeyFailed: '通行密钥注册失败,请确认面板SSL证书是否为可信证书',
|
||||
passkeyFailed: '通行密钥注册失败,请确认面板 SSL 证书是否为可信证书',
|
||||
sslChangeHelper: 'https 设置修改需要重启服务,是否继续?',
|
||||
sslDisable: '禁用',
|
||||
sslDisableHelper: '禁用 https 服务,需要重启面板才能生效,是否继续?',
|
||||
|
||||
@@ -191,38 +191,7 @@
|
||||
<DomainSetting ref="domainRef" @search="search" />
|
||||
<AllowIPsSetting ref="allowIPsRef" @search="search" />
|
||||
<ResponseSetting ref="responseRef" @search="search()" />
|
||||
|
||||
<el-dialog v-model="passkeyDialogVisible" :title="$t('setting.passkey')" width="600px">
|
||||
<div class="mb-4">
|
||||
<el-form label-position="top">
|
||||
<el-form-item :label="$t('setting.passkeyName')">
|
||||
<el-input v-model.trim="passkeyForm.name" :placeholder="$t('setting.passkeyNameHelper')" />
|
||||
</el-form-item>
|
||||
<div class="flex items-center">
|
||||
<el-button type="primary" @click="registerPasskey" :disabled="!canRegisterPasskey">
|
||||
{{ $t('setting.passkeyAdd') }}
|
||||
</el-button>
|
||||
<span class="text-xs text-gray-500 ml-3">{{ passkeyCountText }}</span>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
<el-table :data="passkeyList" v-loading="passkeyLoading">
|
||||
<el-table-column prop="name" :label="$t('setting.passkeyName')" min-width="120" />
|
||||
<el-table-column prop="createdAt" :label="$t('setting.passkeyCreatedAt')" min-width="160" />
|
||||
<el-table-column :label="$t('setting.passkeyLastUsedAt')" min-width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.lastUsedAt || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.operate')" width="120">
|
||||
<template #default="scope">
|
||||
<el-button link type="danger" @click="removePasskey(scope.row.id)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
<PasskeySetting ref="passkeyRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -238,22 +207,12 @@ import TimeoutSetting from '@/views/setting/safe/timeout/index.vue';
|
||||
import EntranceSetting from '@/views/setting/safe/entrance/index.vue';
|
||||
import DomainSetting from '@/views/setting/safe/domain/index.vue';
|
||||
import AllowIPsSetting from '@/views/setting/safe/allowips/index.vue';
|
||||
import {
|
||||
updateSetting,
|
||||
getSettingInfo,
|
||||
getSystemAvailable,
|
||||
updateSSL,
|
||||
loadSSLInfo,
|
||||
passkeyRegisterBegin,
|
||||
passkeyRegisterFinish,
|
||||
passkeyList as fetchPasskeyList,
|
||||
passkeyDelete,
|
||||
} from '@/api/modules/setting';
|
||||
import PasskeySetting from '@/views/setting/safe/passkey/index.vue';
|
||||
import { updateSetting, getSettingInfo, getSystemAvailable, updateSSL, loadSSLInfo } from '@/api/modules/setting';
|
||||
import i18n from '@/lang';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { Setting } from '@/api/interface/setting';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { base64UrlToBuffer, bufferToBase64Url } from '@/utils/util';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const loading = ref(false);
|
||||
@@ -269,6 +228,7 @@ const lastSSL = ref('Disable');
|
||||
const sslInfo = ref<Setting.SSLInfo>();
|
||||
const domainRef = ref();
|
||||
const allowIPsRef = ref();
|
||||
const passkeyRef = ref();
|
||||
const mobile = computed(() => {
|
||||
return globalStore.isMobile();
|
||||
});
|
||||
@@ -292,12 +252,7 @@ const form = reactive({
|
||||
noAuthSettingValue: '200',
|
||||
});
|
||||
|
||||
const passkeyDialogVisible = ref(false);
|
||||
const passkeyLoading = ref(false);
|
||||
const passkeyList = ref<Setting.PasskeyInfo[]>([]);
|
||||
const passkeyForm = reactive({ name: '' });
|
||||
const passkeySupported = ref(false);
|
||||
const passkeyMaxCount = 5;
|
||||
const passkeyHint = computed(() => {
|
||||
if (form.ssl === 'Disable') {
|
||||
return i18n.global.t('setting.passkeyRequireSSL');
|
||||
@@ -307,18 +262,6 @@ const passkeyHint = computed(() => {
|
||||
}
|
||||
return i18n.global.t('setting.passkeyHelper');
|
||||
});
|
||||
const passkeyCountText = computed(() => {
|
||||
return i18n.global.t('setting.passkeyCount', [passkeyList.value.length, passkeyMaxCount]);
|
||||
});
|
||||
const canRegisterPasskey = computed(() => {
|
||||
return (
|
||||
form.ssl !== 'Disable' &&
|
||||
passkeySupported.value &&
|
||||
passkeyList.value.length < passkeyMaxCount &&
|
||||
passkeyForm.name.trim().length > 0
|
||||
);
|
||||
});
|
||||
|
||||
const unset = ref(i18n.global.t('setting.unSetting'));
|
||||
|
||||
const search = async () => {
|
||||
@@ -397,105 +340,7 @@ const handleMFA = async () => {
|
||||
};
|
||||
|
||||
const openPasskeyDialog = async () => {
|
||||
passkeyDialogVisible.value = true;
|
||||
await loadPasskeys();
|
||||
};
|
||||
|
||||
const loadPasskeys = async () => {
|
||||
passkeyLoading.value = true;
|
||||
try {
|
||||
const res = await fetchPasskeyList();
|
||||
passkeyList.value = res.data || [];
|
||||
} catch (error) {
|
||||
passkeyList.value = [];
|
||||
} finally {
|
||||
passkeyLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const registerPasskey = async () => {
|
||||
if (form.ssl === 'Disable') {
|
||||
MsgError(i18n.global.t('setting.passkeyRequireSSL'));
|
||||
return;
|
||||
}
|
||||
if (!passkeySupported.value) {
|
||||
MsgError(i18n.global.t('setting.passkeyNotSupported'));
|
||||
return;
|
||||
}
|
||||
if (passkeyList.value.length >= passkeyMaxCount) {
|
||||
MsgError(i18n.global.t('setting.passkeyLimit'));
|
||||
return;
|
||||
}
|
||||
if (!passkeyForm.name.trim()) {
|
||||
MsgError(i18n.global.t('commons.rule.requiredInput'));
|
||||
return;
|
||||
}
|
||||
passkeyLoading.value = true;
|
||||
try {
|
||||
const res = await passkeyRegisterBegin({ name: passkeyForm.name.trim() });
|
||||
const publicKey = normalizePasskeyCreation(res.data.publicKey);
|
||||
const credential = (await navigator.credentials.create({ publicKey })) as PublicKeyCredential | null;
|
||||
if (!credential) {
|
||||
MsgError(i18n.global.t('setting.passkeyFailed'));
|
||||
return;
|
||||
}
|
||||
const payload = buildPasskeyAttestation(credential);
|
||||
await passkeyRegisterFinish(payload, res.data.sessionId);
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
passkeyForm.name = '';
|
||||
await loadPasskeys();
|
||||
} catch (res: any) {
|
||||
if (res?.message) {
|
||||
console.log(res.message);
|
||||
MsgError(i18n.global.t('setting.passkeyFailed'));
|
||||
}
|
||||
} finally {
|
||||
passkeyLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const removePasskey = async (id: string) => {
|
||||
ElMessageBox.confirm(i18n.global.t('setting.passkeyDeleteConfirm'), i18n.global.t('setting.passkey'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
})
|
||||
.then(async () => {
|
||||
passkeyLoading.value = true;
|
||||
await passkeyDelete(id);
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
await loadPasskeys();
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
passkeyLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const normalizePasskeyCreation = (publicKey: Record<string, any>): PublicKeyCredentialCreationOptions => {
|
||||
const request = { ...publicKey };
|
||||
request.challenge = base64UrlToBuffer(request.challenge);
|
||||
request.user = { ...request.user, id: base64UrlToBuffer(request.user.id) };
|
||||
if (request.excludeCredentials && Array.isArray(request.excludeCredentials)) {
|
||||
request.excludeCredentials = request.excludeCredentials.map((item) => {
|
||||
return { ...item, id: base64UrlToBuffer(item.id) };
|
||||
});
|
||||
}
|
||||
return request as PublicKeyCredentialCreationOptions;
|
||||
};
|
||||
|
||||
const buildPasskeyAttestation = (credential: PublicKeyCredential) => {
|
||||
const response = credential.response as AuthenticatorAttestationResponse;
|
||||
return {
|
||||
id: credential.id,
|
||||
rawId: bufferToBase64Url(credential.rawId),
|
||||
type: credential.type,
|
||||
response: {
|
||||
clientDataJSON: bufferToBase64Url(response.clientDataJSON),
|
||||
attestationObject: bufferToBase64Url(response.attestationObject),
|
||||
},
|
||||
clientExtensionResults: credential.getClientExtensionResults(),
|
||||
authenticatorAttachment: credential.authenticatorAttachment,
|
||||
};
|
||||
passkeyRef.value.acceptParams({ sslStatus: form.ssl, supported: passkeySupported.value });
|
||||
};
|
||||
|
||||
const onChangeEntrance = () => {
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<DrawerPro v-model="drawerVisible" :header="$t('setting.passkey')" @close="handleClose" size="large">
|
||||
<div>
|
||||
<el-form label-position="top">
|
||||
<el-form-item :label="$t('setting.passkeyName')">
|
||||
<el-input v-model.trim="passkeyForm.name" :placeholder="$t('setting.passkeyNameHelper')" />
|
||||
</el-form-item>
|
||||
<el-button type="primary" @click="registerPasskey" :disabled="!canRegisterPasskey">
|
||||
{{ $t('setting.passkeyAdd') }}
|
||||
</el-button>
|
||||
<span class="text-xs text-gray-500 ml-3">{{ passkeyCountText }}</span>
|
||||
</el-form>
|
||||
</div>
|
||||
<el-divider />
|
||||
<el-table :data="passkeyList" v-loading="passkeyLoading">
|
||||
<el-table-column prop="name" :label="$t('setting.passkeyName')" min-width="120" />
|
||||
<el-table-column prop="createdAt" :label="$t('setting.passkeyCreatedAt')" min-width="160" />
|
||||
<el-table-column :label="$t('setting.passkeyLastUsedAt')" min-width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.lastUsedAt || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.operate')" width="120">
|
||||
<template #default="scope">
|
||||
<el-button link type="danger" @click="removePasskey(scope.row.id)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
</template>
|
||||
</DrawerPro>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import {
|
||||
passkeyRegisterBegin,
|
||||
passkeyRegisterFinish,
|
||||
passkeyList as fetchPasskeyList,
|
||||
passkeyDelete,
|
||||
} from '@/api/modules/setting';
|
||||
import i18n from '@/lang';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { Setting } from '@/api/interface/setting';
|
||||
import { base64UrlToBuffer, bufferToBase64Url } from '@/utils/util';
|
||||
|
||||
interface DrawerParams {
|
||||
sslStatus: string;
|
||||
supported: boolean;
|
||||
}
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const passkeyLoading = ref(false);
|
||||
const passkeyList = ref<Setting.PasskeyInfo[]>([]);
|
||||
const passkeyForm = reactive({ name: '' });
|
||||
const passkeySupported = ref(false);
|
||||
const sslStatus = ref('Disable');
|
||||
const passkeyMaxCount = 5;
|
||||
|
||||
const passkeyCountText = computed(() => {
|
||||
return i18n.global.t('setting.passkeyCount', [passkeyList.value.length, passkeyMaxCount]);
|
||||
});
|
||||
|
||||
const canRegisterPasskey = computed(() => {
|
||||
return (
|
||||
sslStatus.value !== 'Disable' &&
|
||||
passkeySupported.value &&
|
||||
passkeyList.value.length < passkeyMaxCount &&
|
||||
passkeyForm.name.trim().length > 0
|
||||
);
|
||||
});
|
||||
|
||||
const acceptParams = async (params: DrawerParams) => {
|
||||
sslStatus.value = params.sslStatus;
|
||||
passkeySupported.value = params.supported;
|
||||
drawerVisible.value = true;
|
||||
await loadPasskeys();
|
||||
};
|
||||
|
||||
const loadPasskeys = async () => {
|
||||
passkeyLoading.value = true;
|
||||
try {
|
||||
const res = await fetchPasskeyList();
|
||||
passkeyList.value = res.data || [];
|
||||
} catch (error) {
|
||||
passkeyList.value = [];
|
||||
} finally {
|
||||
passkeyLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const registerPasskey = async () => {
|
||||
if (sslStatus.value === 'Disable') {
|
||||
MsgError(i18n.global.t('setting.passkeyRequireSSL'));
|
||||
return;
|
||||
}
|
||||
if (!passkeySupported.value) {
|
||||
MsgError(i18n.global.t('setting.passkeyNotSupported'));
|
||||
return;
|
||||
}
|
||||
if (passkeyList.value.length >= passkeyMaxCount) {
|
||||
MsgError(i18n.global.t('setting.passkeyLimit'));
|
||||
return;
|
||||
}
|
||||
if (!passkeyForm.name.trim()) {
|
||||
MsgError(i18n.global.t('commons.rule.requiredInput'));
|
||||
return;
|
||||
}
|
||||
passkeyLoading.value = true;
|
||||
try {
|
||||
const res = await passkeyRegisterBegin({ name: passkeyForm.name.trim() });
|
||||
const publicKey = normalizePasskeyCreation(res.data.publicKey);
|
||||
const credential = (await navigator.credentials.create({ publicKey })) as PublicKeyCredential | null;
|
||||
if (!credential) {
|
||||
MsgError(i18n.global.t('setting.passkeyFailed'));
|
||||
return;
|
||||
}
|
||||
const payload = buildPasskeyAttestation(credential);
|
||||
await passkeyRegisterFinish(payload, res.data.sessionId);
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
passkeyForm.name = '';
|
||||
await loadPasskeys();
|
||||
} catch (res: any) {
|
||||
if (res?.message) {
|
||||
console.log(res.message);
|
||||
}
|
||||
} finally {
|
||||
passkeyLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const removePasskey = async (id: string) => {
|
||||
ElMessageBox.confirm(i18n.global.t('setting.passkeyDeleteConfirm'), i18n.global.t('setting.passkey'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
})
|
||||
.then(async () => {
|
||||
passkeyLoading.value = true;
|
||||
await passkeyDelete(id);
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
await loadPasskeys();
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
passkeyLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const normalizePasskeyCreation = (publicKey: Record<string, any>): PublicKeyCredentialCreationOptions => {
|
||||
const request = { ...publicKey };
|
||||
request.challenge = base64UrlToBuffer(request.challenge);
|
||||
request.user = { ...request.user, id: base64UrlToBuffer(request.user.id) };
|
||||
if (request.excludeCredentials && Array.isArray(request.excludeCredentials)) {
|
||||
request.excludeCredentials = request.excludeCredentials.map((item) => {
|
||||
return { ...item, id: base64UrlToBuffer(item.id) };
|
||||
});
|
||||
}
|
||||
return request as PublicKeyCredentialCreationOptions;
|
||||
};
|
||||
|
||||
const buildPasskeyAttestation = (credential: PublicKeyCredential) => {
|
||||
const response = credential.response as AuthenticatorAttestationResponse;
|
||||
return {
|
||||
id: credential.id,
|
||||
rawId: bufferToBase64Url(credential.rawId),
|
||||
type: credential.type,
|
||||
response: {
|
||||
clientDataJSON: bufferToBase64Url(response.clientDataJSON),
|
||||
attestationObject: bufferToBase64Url(response.attestationObject),
|
||||
},
|
||||
clientExtensionResults: credential.getClientExtensionResults(),
|
||||
authenticatorAttachment: credential.authenticatorAttachment,
|
||||
};
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user