Harden auth risk controls and admin reauth

This commit is contained in:
2025-12-26 21:07:47 +08:00
parent f90b0a4f11
commit e3b0c35da6
32 changed files with 741 additions and 92 deletions

View File

@@ -1,5 +1,5 @@
import axios from 'axios'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
let lastToastKey = ''
let lastToastAt = 0
@@ -24,6 +24,29 @@ export const api = axios.create({
withCredentials: true,
})
let reauthPromise = null
async function ensureReauth() {
if (reauthPromise) return reauthPromise
reauthPromise = ElMessageBox.prompt('请输入管理员密码进行二次确认', '安全确认', {
inputType: 'password',
inputPlaceholder: '管理员密码',
confirmButtonText: '确认',
cancelButtonText: '取消',
inputValidator: (v) => Boolean(String(v || '').trim()),
inputErrorMessage: '密码不能为空',
})
.then(async (res) => {
const password = String(res.value || '').trim()
await api.post('/admin/reauth', { password })
ElMessage.success('已通过安全确认')
})
.finally(() => {
reauthPromise = null
})
return reauthPromise
}
api.interceptors.request.use((config) => {
const method = String(config?.method || 'GET').toUpperCase()
if (!['GET', 'HEAD', 'OPTIONS'].includes(method)) {
@@ -38,11 +61,21 @@ api.interceptors.request.use((config) => {
api.interceptors.response.use(
(response) => response,
(error) => {
async (error) => {
const status = error?.response?.status
const payload = error?.response?.data
const message = payload?.error || payload?.message || error?.message || '请求失败'
if (payload?.code === 'reauth_required' && error?.config && !error.config.__reauth_retry) {
try {
error.config.__reauth_retry = true
await ensureReauth()
return api.request(error.config)
} catch {
return Promise.reject(error)
}
}
if (status === 401) {
toastErrorOnce('401', message || '登录已过期,请重新登录', 3000)
const pathname = window.location?.pathname || ''

View File

@@ -473,6 +473,7 @@ function emailTypeLabel(type) {
reset: '密码重置',
bind: '邮箱绑定',
task_complete: '任务完成',
security_alert: '安全告警',
}
return map[type] || type
}
@@ -701,6 +702,7 @@ onMounted(refreshAll)
<el-option label="密码重置" value="reset" />
<el-option label="邮箱绑定" value="bind" />
<el-option label="任务完成" value="task_complete" />
<el-option label="安全告警" value="security_alert" />
</el-select>
<el-select v-model="emailLogStatusFilter" style="width: 120px" @change="loadEmailLogs(1)">
<el-option label="全部状态" value="" />