强化用户安全信息编辑确认
Some checks are pending
Security Checks / security (push) Waiting to run

This commit is contained in:
smail 2026-09-08 12:57:27 +08:00
parent 998b68079a
commit f32b81f3f3
4 changed files with 66 additions and 16 deletions

View File

@ -8,19 +8,7 @@ export function isRecentAuthenticationCancelled(error) {
return error?.code === cancelledCode
}
export async function requestWithRecentAuthentication(request) {
let initialResponse
let requiresRecentAuthentication = false
try {
initialResponse = await request()
} catch (error) {
if (error?.apiErrorCode !== recentAuthenticationRequired) throw error
requiresRecentAuthentication = true
}
if (!requiresRecentAuthentication && initialResponse?.data?.code !== recentAuthenticationRequired) {
return initialResponse
}
export async function confirmRecentAuthentication() {
let password = ''
try {
const result = await ElMessageBox.prompt(
@ -50,6 +38,22 @@ export async function requestWithRecentAuthentication(request) {
} finally {
password = ''
}
}
export async function requestWithRecentAuthentication(request) {
let initialResponse
let requiresRecentAuthentication = false
try {
initialResponse = await request()
} catch (error) {
if (error?.apiErrorCode !== recentAuthenticationRequired) throw error
requiresRecentAuthentication = true
}
if (!requiresRecentAuthentication && initialResponse?.data?.code !== recentAuthenticationRequired) {
return initialResponse
}
await confirmRecentAuthentication()
return request()
}

View File

@ -9,6 +9,7 @@ import {ElNotification} from "element-plus";
import {Plus} from "@element-plus/icons-vue";
import {useGlobalStatusStore} from "@/stores/useGlobalStatusStore";
import {confirmRecentAuthentication, isRecentAuthenticationCancelled} from '@/utils/recentAuthentication'
const roleOptions = {
super_admin: { label: '超级管理员', type: 'danger' },
@ -74,6 +75,8 @@ export function usePage() {
const externalEmailDialogVisible = ref(false)
const securityDialogOpening = ref('')
const editingCurrentUser = computed(() => isSameUser(selectedUser.value, globalStatus.userInfos))
const recoveryCodeWebManaged = computed(() => canManageRecoveryCode(selectedUser.value))
@ -122,13 +125,35 @@ export function usePage() {
userInfoDialog.value = true
}
const openRecoveryCodeDialog = function () {
const confirmSecurityDialogAccess = async function (dialogName) {
if (securityDialogOpening.value) return false
securityDialogOpening.value = dialogName
try {
await confirmRecentAuthentication()
return true
} catch (error) {
if (!isRecentAuthenticationCancelled(error)) {
ElNotification({
title: lang.fail,
message: error?.message || '身份确认失败',
type: 'error',
})
}
return false
} finally {
securityDialogOpening.value = ''
}
}
const openRecoveryCodeDialog = async function () {
if (!recoveryCodeWebManaged.value) return
if (!await confirmSecurityDialogAccess('recovery-code')) return
recoveryCodeDialogVisible.value = true
}
const openExternalEmailDialog = function () {
const openExternalEmailDialog = async function () {
if (!selectedUser.value?.ID) return
if (!await confirmSecurityDialogAccess('external-email')) return
externalEmailDialogVisible.value = true
}
@ -200,6 +225,7 @@ export function usePage() {
recoveryCodeWebManaged,
recoveryCodeDialogVisible,
externalEmailDialogVisible,
securityDialogOpening,
openRecoveryCodeDialog,
openExternalEmailDialog,
handleSecurityChanged,

View File

@ -122,6 +122,7 @@
</div>
<el-button
:disabled="!recoveryCodeWebManaged"
:loading="securityDialogOpening === 'recovery-code'"
@click="openRecoveryCodeDialog"
>
{{ recoveryCodeWebManaged ? '管理安全码' : '仅支持本机管理' }}
@ -137,7 +138,12 @@
).label }}
</span>
</div>
<el-button @click="openExternalEmailDialog">编辑外部邮箱</el-button>
<el-button
:loading="securityDialogOpening === 'external-email'"
@click="openExternalEmailDialog"
>
编辑外部邮箱
</el-button>
</div>
</div>
@ -201,6 +207,7 @@ const {
recoveryCodeWebManaged,
recoveryCodeDialogVisible,
externalEmailDialogVisible,
securityDialogOpening,
openRecoveryCodeDialog,
openExternalEmailDialog,
handleSecurityChanged,

View File

@ -75,6 +75,19 @@ test('用户安全信息使用独立弹窗并调用固定管理接口', () => {
assert.doesNotMatch(externalSource, /\/api\/v1\/security\/external-email\/bind/)
})
test('打开安全码或外部邮箱编辑弹窗前必须确认当前登录密码', () => {
const source = read('UserManagement.js')
assert.match(source, /confirmRecentAuthentication/)
assert.match(source, /confirmSecurityDialogAccess\('recovery-code'\)/)
assert.match(source, /confirmSecurityDialogAccess\('external-email'\)/)
assert.match(source, /if \(!await confirmSecurityDialogAccess\('recovery-code'\)\) return/)
assert.match(source, /if \(!await confirmSecurityDialogAccess\('external-email'\)\) return/)
const template = read('UserManagement.vue')
assert.match(template, /securityDialogOpening === 'recovery-code'/)
assert.match(template, /securityDialogOpening === 'external-email'/)
})
test('当前用户判断优先使用用户 ID 并保留账号兼容回退', () => {
const { isSameUser } = loadStatusHelpers()
assert.equal(isSameUser({ ID: 7, Account: 'same' }, { id: 7, account: 'other' }), true)