fix(health): 审计问题修复 — 权限守卫 + OAuth中间件 + FHIR声明 + SSE聚合
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

- OAuthClientList/RealtimeMonitor/OfflineEventList/StatisticsDashboard 补权限守卫
- OAuth 中间件注入 TenantContext + FHIR scope→permission 映射
- FHIR CapabilityStatement 移除未实现的 $lastn 操作
- useVitalSSE 修复批量同步事件数据聚合逻辑
This commit is contained in:
iven
2026-05-04 12:02:50 +08:00
parent d436888ca5
commit 1135439403
7 changed files with 80 additions and 15 deletions

View File

@@ -31,7 +31,7 @@ where
"mode": "server",
"resource": [
{ "type": "Patient", "interaction": [{"code": "read"}, {"code": "search-type"}], "operation": [{"name": "everything"}] },
{ "type": "Observation", "interaction": [{"code": "read"}, {"code": "search-type"}], "operation": [{"name": "lastn"}] },
{ "type": "Observation", "interaction": [{"code": "read"}, {"code": "search-type"}] },
{ "type": "Device", "interaction": [{"code": "read"}, {"code": "search-type"}] },
{ "type": "DiagnosticReport", "interaction": [{"code": "read"}, {"code": "search-type"}] },
{ "type": "Encounter", "interaction": [{"code": "read"}, {"code": "search-type"}] },
@@ -40,8 +40,7 @@ where
{ "type": "Task", "interaction": [{"code": "read"}, {"code": "search-type"}] },
],
"operation": [
{ "name": "everything", "definition": "/fhir/R4/Patient/{id}/$everything" },
{ "name": "lastn", "definition": "/fhir/R4/Observation/$lastn" },
{ "name": "everything", "definition": "/fhir/R4/Patient/{id}/$everything" }
]
}]
});

View File

@@ -6,6 +6,7 @@ use axum::{
Json,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
/// Client Credentials JWT Claims
@@ -107,12 +108,48 @@ pub async fn oauth_auth_middleware(request: Request, next: Next) -> Response {
let fhir_ctx = FhirAuthContext {
client_id: claims.sub,
tenant_id: claims.tid,
scopes: claims.scopes,
scopes: claims.scopes.clone(),
allowed_patient_ids: claims.allowed_patient_ids,
};
// 同时注入 TenantContext兼容 require_permission() 和 ctx.tenant_id
// 将 FHIR scope 映射为内部 permission code
let permissions: Vec<String> = claims
.scopes
.iter()
.flat_map(|s| scope_to_permissions(s))
.collect();
let tenant_ctx = erp_core::types::TenantContext {
tenant_id: claims.tid,
user_id: claims.sub,
roles: vec!["api_client".to_string()],
permissions,
department_ids: vec![],
permission_data_scopes: HashMap::new(),
};
let mut request = request;
request.extensions_mut().insert(tenant_ctx);
request.extensions_mut().insert(fhir_ctx);
next.run(request).await
}
/// FHIR scope → 内部 permission code 映射
fn scope_to_permissions(scope: &str) -> Vec<String> {
match scope {
"Patient.read" => vec!["health.patient.list".to_string()],
"Observation.read" => vec![
"health.device-readings.list".to_string(),
"health.health-data.list".to_string(),
],
"Device.read" => vec!["health.devices.list".to_string()],
"Practitioner.read" => vec!["health.doctor.list".to_string()],
"Appointment.read" => vec!["health.appointment.list".to_string()],
"DiagnosticReport.read" => vec!["health.health-data.list".to_string()],
"Encounter.read" => vec!["health.consultation.list".to_string()],
"Task.read" => vec!["health.follow-up.list".to_string()],
_ => vec![],
}
}