功能修复: 1. 患者创建空名称验证:后端添加 name.trim().is_empty() 检查 2. 仪表盘统计容错:单个查询失败返回零值而非 500 3. FHIR 路由修复:从 /fhir 移到 /api/v1/fhir 保持一致 4. 冻结模块后端中间件:新增 frozen_module_middleware 拦截冻结路径 5. 积分端点权限码:health.health-data.list → health.points.list 6. 角色权限迁移:护士补充 devices.list,运营补充 points.list/manage 7. 测试结果文档:R01-R05 角色测试 + T00/T10 结果归档 Clippy 全 workspace 清零(14→0 errors): - erp-core: 修复 empty doc line、collapsible if、redundant closure 等 9 处 - erp-health: 修复 too_many_arguments、unused var、unnecessary parens 等 58 处 - erp-ai: 修复 dead_code、unused import 等 11 处 - erp-plugin: 修复 too_many_arguments、wildcard pattern 等 11 处 - erp-server-migration: 修复 enum_variant_names 5 处 - erp-auth/config/workflow/message: 各 1-3 处 工程改进: - lint-staged 配置迁移到 .lintstagedrc.js(函数式避免文件列表传给 clippy) - cargo fmt 统一格式化
138 lines
3.9 KiB
Rust
138 lines
3.9 KiB
Rust
use axum::{
|
|
Json,
|
|
extract::{Request, State},
|
|
http::StatusCode,
|
|
middleware::Next,
|
|
response::{IntoResponse, Response},
|
|
};
|
|
use sea_orm::ColumnTrait;
|
|
use sea_orm::EntityTrait;
|
|
use sea_orm::QueryFilter;
|
|
use sha2::{Digest, Sha256};
|
|
use uuid::Uuid;
|
|
|
|
use crate::state::HealthState;
|
|
|
|
/// 网关认证上下文 — 中间件注入到请求扩展中
|
|
#[derive(Debug, Clone)]
|
|
pub struct GatewayAuthContext {
|
|
pub gateway_db_id: Uuid, // ble_gateways 表主键
|
|
pub tenant_id: Uuid,
|
|
pub gateway_id: String, // 物理设备标识
|
|
}
|
|
|
|
/// BLE 网关 API Key 认证中间件
|
|
/// 请求头: `Authorization: Gateway <api_key>` 或 `X-Gateway-Key: <api_key>`
|
|
pub async fn gateway_auth_middleware(
|
|
State(state): State<HealthState>,
|
|
mut request: Request,
|
|
next: Next,
|
|
) -> Response {
|
|
let api_key = extract_gateway_key(&request);
|
|
|
|
let api_key = match api_key {
|
|
Some(key) => key,
|
|
None => {
|
|
return (
|
|
StatusCode::UNAUTHORIZED,
|
|
Json(serde_json::json!({
|
|
"success": false,
|
|
"message": "Missing gateway API key. Use Authorization: Gateway <key> or X-Gateway-Key header"
|
|
})),
|
|
)
|
|
.into_response();
|
|
}
|
|
};
|
|
|
|
// 用前 8 位快速定位,再用完整 hash 验证
|
|
let prefix = &api_key[..8.min(api_key.len())];
|
|
let key_hash = sha256_hex(api_key.as_bytes());
|
|
|
|
let gateway = crate::entity::ble_gateway::Entity::find()
|
|
.filter(crate::entity::ble_gateway::Column::ApiKeyPrefix.eq(prefix))
|
|
.filter(crate::entity::ble_gateway::Column::ApiKeyHash.eq(&key_hash))
|
|
.filter(crate::entity::ble_gateway::Column::DeletedAt.is_null())
|
|
.filter(crate::entity::ble_gateway::Column::Status.eq("active"))
|
|
.one(&state.db)
|
|
.await;
|
|
|
|
match gateway {
|
|
Ok(Some(g)) => {
|
|
let ctx = GatewayAuthContext {
|
|
gateway_db_id: g.id,
|
|
tenant_id: g.tenant_id,
|
|
gateway_id: g.gateway_id.clone(),
|
|
};
|
|
request.extensions_mut().insert(ctx);
|
|
next.run(request).await
|
|
}
|
|
Ok(None) => (
|
|
StatusCode::UNAUTHORIZED,
|
|
Json(serde_json::json!({
|
|
"success": false,
|
|
"message": "Invalid or inactive gateway API key"
|
|
})),
|
|
)
|
|
.into_response(),
|
|
Err(e) => {
|
|
tracing::error!(error = %e, "Gateway auth database error");
|
|
(
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Json(serde_json::json!({
|
|
"success": false,
|
|
"message": "Authentication service error"
|
|
})),
|
|
)
|
|
.into_response()
|
|
}
|
|
}
|
|
}
|
|
|
|
fn extract_gateway_key(request: &Request) -> Option<String> {
|
|
// Authorization: Gateway <key>
|
|
if let Some(auth) = request
|
|
.headers()
|
|
.get("Authorization")
|
|
.and_then(|v| v.to_str().ok())
|
|
&& let Some(key) = auth.strip_prefix("Gateway ")
|
|
{
|
|
let key = key.trim();
|
|
if !key.is_empty() {
|
|
return Some(key.to_string());
|
|
}
|
|
}
|
|
|
|
// X-Gateway-Key: <key>
|
|
if let Some(key) = request
|
|
.headers()
|
|
.get("X-Gateway-Key")
|
|
.and_then(|v| v.to_str().ok())
|
|
{
|
|
let key = key.trim();
|
|
if !key.is_empty() {
|
|
return Some(key.to_string());
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
/// SHA-256 hex digest
|
|
pub fn sha256_hex(data: &[u8]) -> String {
|
|
let mut hasher = Sha256::new();
|
|
hasher.update(data);
|
|
let result = hasher.finalize();
|
|
hex::encode(result)
|
|
}
|
|
|
|
/// 生成随机 API Key (32 字节 hex = 64 字符)
|
|
pub fn generate_api_key() -> (String, String, String) {
|
|
use rand_core::{OsRng, RngCore};
|
|
let mut bytes = [0u8; 32];
|
|
OsRng.fill_bytes(&mut bytes);
|
|
let api_key = hex::encode(bytes);
|
|
let prefix = api_key[..8].to_string();
|
|
let hash = sha256_hex(api_key.as_bytes());
|
|
(api_key, prefix, hash)
|
|
}
|