Harden auth risk controls and admin reauth
This commit is contained in:
@@ -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 || ''
|
||||
|
||||
@@ -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="" />
|
||||
|
||||
Reference in New Issue
Block a user