refactor: remove passkey login

This commit is contained in:
237899745
2026-05-27 22:32:42 +08:00
parent 89cb98233f
commit 0443c976fc
105 changed files with 410 additions and 2505 deletions
-161
View File
@@ -5,32 +5,19 @@ import QrcodeVue from 'qrcode.vue'
import {
createAdminSocialLoginUrl,
createAdminPasskeyOptions,
createAdminPasskeyVerify,
fetchAdminSocialBindings,
deleteAdminPasskey,
fetchAdminPasskeys,
logout,
pollAdminSocialLogin,
reportAdminPasskeyClientError,
unbindAdminSocial,
updateAdminPassword,
updateAdminUsername,
} from '../api/admin'
import { createPasskey, getPasskeyClientErrorMessage, isPasskeyAvailable } from '../utils/passkey'
const username = ref('')
const currentPassword = ref('')
const password = ref('')
const confirmPassword = ref('')
const submitting = ref(false)
const passkeyLoading = ref(false)
const passkeyAddLoading = ref(false)
const passkeyDeviceName = ref('')
const passkeyItems = ref([])
const passkeyRegisterOptions = ref(null)
const passkeyRegisterOptionsAt = ref(0)
const PASSKEY_OPTIONS_PREFETCH_MAX_AGE_MS = 240000
const socialBindingsLoading = ref(false)
const socialBindLoadingProvider = ref('')
const socialBindings = ref([])
@@ -148,113 +135,6 @@ async function savePassword() {
}
}
async function loadPasskeys() {
passkeyLoading.value = true
try {
const data = await fetchAdminPasskeys()
passkeyItems.value = Array.isArray(data?.items) ? data.items : []
if (passkeyItems.value.length < 3) {
await prefetchPasskeyRegisterOptions()
} else {
passkeyRegisterOptions.value = null
passkeyRegisterOptionsAt.value = 0
}
} catch {
passkeyItems.value = []
passkeyRegisterOptions.value = null
passkeyRegisterOptionsAt.value = 0
} finally {
passkeyLoading.value = false
}
}
function getCachedPasskeyRegisterOptions() {
if (!passkeyRegisterOptions.value) return null
if (Date.now() - Number(passkeyRegisterOptionsAt.value || 0) > PASSKEY_OPTIONS_PREFETCH_MAX_AGE_MS) return null
return passkeyRegisterOptions.value
}
async function prefetchPasskeyRegisterOptions() {
try {
const res = await createAdminPasskeyOptions({})
passkeyRegisterOptions.value = res
passkeyRegisterOptionsAt.value = Date.now()
} catch {
passkeyRegisterOptions.value = null
passkeyRegisterOptionsAt.value = 0
}
}
async function addPasskey() {
if (!isPasskeyAvailable()) {
ElMessage.error('当前浏览器或环境不支持Passkey(需 HTTPS')
return
}
if (passkeyItems.value.length >= 3) {
ElMessage.error('最多可绑定3台设备')
return
}
passkeyAddLoading.value = true
try {
let optionsRes = getCachedPasskeyRegisterOptions()
if (!optionsRes) {
optionsRes = await createAdminPasskeyOptions({})
}
const credential = await createPasskey(optionsRes?.publicKey || {})
await createAdminPasskeyVerify({ credential, device_name: passkeyDeviceName.value.trim() })
passkeyRegisterOptions.value = null
passkeyRegisterOptionsAt.value = 0
passkeyDeviceName.value = ''
ElMessage.success('Passkey设备添加成功')
await loadPasskeys()
} catch (e) {
try {
await reportAdminPasskeyClientError({
stage: 'register',
source: 'admin-settings',
name: e?.name || '',
message: e?.message || '',
code: e?.code || '',
user_agent: navigator.userAgent || '',
})
} catch {
// ignore report failure
}
passkeyRegisterOptions.value = null
passkeyRegisterOptionsAt.value = 0
await prefetchPasskeyRegisterOptions()
const data = e?.response?.data
const message =
data?.error ||
getPasskeyClientErrorMessage(e, 'Passkey注册')
ElMessage.error(message)
} finally {
passkeyAddLoading.value = false
}
}
async function removePasskey(item) {
try {
await ElMessageBox.confirm(`确定删除设备「${item?.device_name || '未命名设备'}」吗?`, '删除Passkey设备', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
await deleteAdminPasskey(item.id)
ElMessage.success('设备已删除')
await loadPasskeys()
} catch (e) {
const data = e?.response?.data
ElMessage.error(data?.error || '删除失败')
}
}
function socialBindRedirectUri() {
const url = new URL(window.location.href)
url.pathname = '/yuyx/admin-social-bind-callback'
@@ -380,7 +260,6 @@ async function unbindSocial(item) {
}
onMounted(() => {
loadPasskeys()
loadSocialBindings()
})
@@ -443,46 +322,6 @@ onBeforeUnmount(() => {
<div class="help">建议使用更强密码至少8位且包含字母与数字</div>
</el-card>
<el-card shadow="never" :body-style="{ padding: '16px' }" class="card">
<h3 class="section-title">Passkey设备</h3>
<el-alert
type="info"
:closable="false"
title="最多可绑定3台设备,可用于管理员无密码登录。"
show-icon
class="help-alert"
/>
<el-form inline>
<el-form-item label="设备备注">
<el-input
v-model="passkeyDeviceName"
placeholder="例如:值班iPhone / 办公Mac"
maxlength="40"
show-word-limit
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="passkeyAddLoading" @click="addPasskey">添加Passkey设备</el-button>
</el-form-item>
</el-form>
<div v-loading="passkeyLoading">
<el-empty v-if="passkeyItems.length === 0" description="暂无Passkey设备" />
<el-table v-else :data="passkeyItems" size="small" style="width: 100%">
<el-table-column prop="device_name" label="设备备注" min-width="160" />
<el-table-column prop="credential_id_preview" label="凭据ID" min-width="180" />
<el-table-column prop="last_used_at" label="最近使用" min-width="140" />
<el-table-column prop="created_at" label="创建时间" min-width="140" />
<el-table-column label="操作" width="100" fixed="right">
<template #default="{ row }">
<el-button type="danger" text @click="removePasskey(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-card>
<el-card shadow="never" :body-style="{ padding: '16px' }" class="card">
<div class="section-head">
<h3 class="section-title">快捷登录绑定</h3>