Files
hms/crates/erp-health/src/gateway_auth.rs
iven 6d5a711d2c
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
fix: 修复测试发现的 7 个问题 + 全 workspace clippy 清零
功能修复:
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 统一格式化
2026-05-07 23:43:14 +08:00

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)
}