fix: 按评审报告止血支付漏洞并加固授权链路
废除浏览器回跳自助结算,通用 webhook 强制密钥鉴权,Waffo 支持验签与未知事件忽略;重构订单状态机/激活占座/SQLite WAL,并补备份、readyz、限流与前端导出/登录页安全修复。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
8cc2c83f9d
commit
bac72e6945
24 files changed
+682
-391
No files matched your search
@@ -14,7 +14,7 @@
|
||||
"pinia": "^2.1.7",
|
||||
"element-plus": "^2.5.0",
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"axios": "^1.6.5",
|
||||
"axios": "^1.7.9",
|
||||
"echarts": "^5.4.3",
|
||||
"vue-echarts": "^6.6.8",
|
||||
"dayjs": "^1.11.10"
|
||||
|
||||
+37
-25
@@ -1,5 +1,6 @@
|
||||
import axios from 'axios'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import router from '@/router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
// 创建 axios 实例 // Logan.Li
|
||||
@@ -11,6 +12,26 @@ const request = axios.create({
|
||||
},
|
||||
})
|
||||
|
||||
let handling401 = false
|
||||
|
||||
// 统一强制重新登录,避免并发 401 叠加弹窗 // Logan.Li
|
||||
async function forceReLogin() {
|
||||
if (handling401) return Promise.reject(new Error('relogin'))
|
||||
handling401 = true
|
||||
try {
|
||||
const userStore = useUserStore()
|
||||
userStore.logout()
|
||||
ElMessage.warning('登录状态已过期,请重新登录')
|
||||
const redirect = router.currentRoute.value.fullPath
|
||||
await router.push({ path: '/login', query: redirect && redirect !== '/login' ? { redirect } : {} })
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
handling401 = false
|
||||
}, 1000)
|
||||
}
|
||||
return Promise.reject(new Error('relogin'))
|
||||
}
|
||||
|
||||
// 请求拦截器:注入 Bearer token // Logan.Li
|
||||
request.interceptors.request.use(
|
||||
(config) => {
|
||||
@@ -26,6 +47,10 @@ request.interceptors.request.use(
|
||||
// 响应拦截器:统一处理 code 与 HTTP 错误 // Logan.Li
|
||||
request.interceptors.response.use(
|
||||
(response) => {
|
||||
// 文件下载(blob)直接放行,避免被业务码拦截 // Logan.Li
|
||||
if (['blob', 'arraybuffer'].includes(response.config.responseType)) {
|
||||
return response
|
||||
}
|
||||
const res = response.data
|
||||
|
||||
// code 为 0 或 200 视为成功 // Logan.Li
|
||||
@@ -33,53 +58,40 @@ request.interceptors.response.use(
|
||||
return res
|
||||
}
|
||||
|
||||
// 401:登录态失效,引导重新登录 // Logan.Li
|
||||
// 401:登录态失效 // Logan.Li
|
||||
if (res.code === 401) {
|
||||
ElMessageBox.confirm('登录状态已过期,请重新登录', '系统提示', {
|
||||
confirmButtonText: '重新登录',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
const userStore = useUserStore()
|
||||
userStore.logout()
|
||||
window.location.href = '/login'
|
||||
})
|
||||
return Promise.reject(new Error(res.message || '未授权'))
|
||||
return forceReLogin()
|
||||
}
|
||||
|
||||
// 其他业务错误:仅错误用 ElMessage 提示(常规操作不打扰) // Logan.Li
|
||||
ElMessage.error(res.message || '请求失败')
|
||||
return Promise.reject(new Error(res.message || 'Error'))
|
||||
},
|
||||
(error) => {
|
||||
let message = '网络错误'
|
||||
async (error) => {
|
||||
if (error.response?.status === 401) {
|
||||
return forceReLogin()
|
||||
}
|
||||
|
||||
let message = '网络错误'
|
||||
if (error.response) {
|
||||
switch (error.response.status) {
|
||||
case 400:
|
||||
message = error.response.data?.message || '请求参数错误'
|
||||
break
|
||||
case 401:
|
||||
message = '未授权,请重新登录'
|
||||
{
|
||||
const userStore = useUserStore()
|
||||
userStore.logout()
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login'
|
||||
}, 1500)
|
||||
}
|
||||
break
|
||||
case 403:
|
||||
message = '拒绝访问'
|
||||
break
|
||||
case 404:
|
||||
message = '请求的资源不存在'
|
||||
break
|
||||
case 429:
|
||||
message = '请求过于频繁,请稍后再试'
|
||||
break
|
||||
case 500:
|
||||
message = '服务器内部错误'
|
||||
break
|
||||
default:
|
||||
message = `连接错误 ${error.response.status}`
|
||||
message = error.response.data?.message || `连接错误 ${error.response.status}`
|
||||
}
|
||||
} else if (error.request) {
|
||||
message = '网络连接失败,请检查网络'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import ElementPlus from 'element-plus'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
import 'element-plus/dist/index.css'
|
||||
|
||||
@@ -17,7 +18,7 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(ElementPlus, { locale: undefined }) // 默认中文
|
||||
app.use(ElementPlus, { locale: zhCn })
|
||||
|
||||
// 从 localStorage 恢复登录状态 // Logan.Li
|
||||
const userStore = useUserStore()
|
||||
|
||||
@@ -41,8 +41,6 @@
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="login-tip">默认账号:admin / admin</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -60,8 +58,8 @@ const userStore = useUserStore()
|
||||
const loginFormRef = ref(null)
|
||||
const loading = ref(false)
|
||||
const loginForm = reactive({
|
||||
username: 'admin',
|
||||
password: 'admin',
|
||||
username: '',
|
||||
password: '',
|
||||
})
|
||||
const loginRules = {
|
||||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||
@@ -117,10 +115,4 @@ async function handleLogin() {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
.login-tip {
|
||||
text-align: center;
|
||||
color: #c0c4cc;
|
||||
font-size: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user