fix(security): close unauthenticated CMA user API surface + audit tool (cma-api-auth-surface-audit-20260914)
Root cause: main.py include_router had no global auth dependency; user routers (products/cost/predict/...) relied on assert_entity_scope, which treats a missing session token as the Bot service channel and lets the request through. Result: 46 user endpoints returned data without any token on a public site. Changes: - auth_middleware.py: add require_auth_401 (missing Bearer -> 401, delegates to require_auth otherwise); 401/403 semantics are explicit. - router-level Depends(require_auth_401) on products, cost, predict, notifications, permissions, tenant, alignment; endpoint-level on /auth/roles and /admin/cache/clear. - deps.py: document that get_entity_id/assert_entity_scope are NOT auth. - frontend: add the missing Bearer header to 5 tokenless fetch/axios calls that hit the newly protected endpoints (KPIAlignment x2, RolePermissions x2, Dashboard x1). - tests: new test_api_auth_surface_20260914.py (58 cases: no-token 401, token 200, cross-entity 403, bot channel unchanged); adapt test_predict / test_kpi_forecast to send session tokens like production. - scripts/audit_api_auth_surface.py: reusable FastAPI dependency-graph audit (423 endpoints; A bot / B gap / C public), exits 1 when a B-class gap exists.
This commit is contained in:
@@ -482,7 +482,10 @@ async function loadData() {
|
||||
finSummary.value = (r as any)?.summary || {}
|
||||
dataRange.value = (r as any)?.period || ''
|
||||
} else if (userRole.value === 'it') {
|
||||
const channelRes = await axios.get('/api/cma/notifications/channels').then(r => r.data)
|
||||
// 鉴权收口(cma-api-auth-surface-audit-20260914):/notifications/channels 已要求 Bearer
|
||||
const channelRes = await axios.get('/api/cma/notifications/channels', {
|
||||
headers: { 'Authorization': `Bearer ${localStorage.getItem('cma_token')}` },
|
||||
}).then(r => r.data)
|
||||
enabledChannels.value = channelRes.data?.filter((c: any) => c.enabled).length || 0
|
||||
}
|
||||
} catch (e) { console.error('加载数据失败:', e) }
|
||||
|
||||
@@ -158,7 +158,7 @@ async function loadMapList() {
|
||||
try {
|
||||
const token = localStorage.getItem('cma_token')
|
||||
const r = await fetch('/api/cma/maps', {
|
||||
headers: { 'Authorization': `'Bearer ' + token` },
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
}).then(r => r.json())
|
||||
mapList.value = r.data || []
|
||||
} catch {}
|
||||
@@ -271,7 +271,11 @@ const devClass = (d: any) => {
|
||||
|
||||
async function loadConfig() {
|
||||
try {
|
||||
const r = await fetch('/api/cma/alignment/config').then(r => r.json())
|
||||
// 鉴权收口(cma-api-auth-surface-audit-20260914):/alignment/* 已要求 Bearer,补 token
|
||||
const token = localStorage.getItem('cma_token')
|
||||
const r = await fetch('/api/cma/alignment/config', {
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
}).then(r => r.json())
|
||||
alignModes.value = r.modes || []
|
||||
alignConfigured.value = r.configured || false
|
||||
if (r.configured && r.mode) {
|
||||
|
||||
@@ -231,9 +231,12 @@ async function resetPermConfig() {
|
||||
|
||||
async function loadPermData() {
|
||||
try {
|
||||
// 鉴权收口(cma-api-auth-surface-audit-20260914):/permissions/* 已要求 Bearer,补 token
|
||||
const token = localStorage.getItem('cma_token')
|
||||
const authHeaders = { 'Authorization': `Bearer ${token}` }
|
||||
const [modRes, cfgRes] = await Promise.all([
|
||||
fetch('/api/cma/permissions/modules').then(r => r.json()),
|
||||
fetch('/api/cma/permissions/config').then(r => r.json()),
|
||||
fetch('/api/cma/permissions/modules', { headers: authHeaders }).then(r => r.json()),
|
||||
fetch('/api/cma/permissions/config', { headers: authHeaders }).then(r => r.json()),
|
||||
])
|
||||
modules.value = modRes.modules || []
|
||||
actions.value = modRes.actions || []
|
||||
@@ -272,7 +275,11 @@ function feedsLabel(feeds: string[] | null) {
|
||||
|
||||
async function loadAlignmentConfig() {
|
||||
try {
|
||||
const r = await fetch('/api/cma/alignment/config').then(r => r.json())
|
||||
// 鉴权收口(cma-api-auth-surface-audit-20260914):/alignment/* 已要求 Bearer,补 token
|
||||
const token = localStorage.getItem('cma_token')
|
||||
const r = await fetch('/api/cma/alignment/config', {
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
}).then(r => r.json())
|
||||
alignmentModes.value = r.modes || []
|
||||
alignConfigured.value = r.configured || false
|
||||
currentMode.value = null
|
||||
|
||||
Reference in New Issue
Block a user