release(secrets-mcp): 0.5.10 — Web 模块化、性能与错误处理
All checks were successful
Secrets MCP — Build & Release / 检查 / 构建 / 发版 (push) Successful in 6m3s
Secrets MCP — Build & Release / 部署 secrets-mcp (push) Successful in 1m36s

- 拆分 web.rs 为 web/ 子模块;统一 client_ip 提取
- core: user_scope SQL 复用、env_map N+1 消除、FETCH_ALL 上限调整
- entries 列表页并行查询;PgPool 去 Arc;结构化 NotFound 等错误
- CI: SSH 私钥安全写入;crypto/hex 与依赖清理;MCP 输入长度校验
- AGENTS: API Key 明文存储设计说明
This commit is contained in:
voson
2026-04-06 18:01:44 +08:00
parent b0fcb83592
commit 59084a409d
28 changed files with 2300 additions and 2254 deletions

View File

@@ -5,7 +5,6 @@ use std::collections::HashMap;
use uuid::Uuid;
use crate::crypto;
use crate::models::Entry;
use crate::service::search::{fetch_entries, fetch_secrets_for_entries};
/// Build an env variable map from entry secrets (for dry-run preview or injection).
@@ -22,56 +21,43 @@ pub async fn build_env_map(
user_id: Option<Uuid>,
) -> Result<HashMap<String, String>> {
let entries = fetch_entries(pool, folder, entry_type, name, tags, None, user_id).await?;
if entries.is_empty() {
return Ok(HashMap::new());
}
let entry_ids: Vec<Uuid> = entries.iter().map(|e| e.id).collect();
let secrets_map = fetch_secrets_for_entries(pool, &entry_ids).await?;
let mut combined: HashMap<String, String> = HashMap::new();
for entry in &entries {
let entry_map =
build_entry_env_map(pool, entry, only_fields, prefix, master_key, user_id).await?;
combined.extend(entry_map);
let all_fields = secrets_map.get(&entry.id).map(Vec::as_slice).unwrap_or(&[]);
let effective_prefix = env_prefix(entry, prefix);
let fields: Vec<_> = if only_fields.is_empty() {
all_fields.iter().collect()
} else {
all_fields
.iter()
.filter(|f| only_fields.contains(&f.name))
.collect()
};
for f in fields {
let decrypted = crypto::decrypt_json(master_key, &f.encrypted)?;
let key = format!(
"{}_{}",
effective_prefix,
f.name.to_uppercase().replace(['-', '.'], "_")
);
combined.insert(key, json_to_env_string(&decrypted));
}
}
Ok(combined)
}
async fn build_entry_env_map(
pool: &PgPool,
entry: &Entry,
only_fields: &[String],
prefix: &str,
master_key: &[u8; 32],
_user_id: Option<Uuid>,
) -> Result<HashMap<String, String>> {
let entry_ids = vec![entry.id];
let secrets_map = fetch_secrets_for_entries(pool, &entry_ids).await?;
let all_fields = secrets_map.get(&entry.id).map(Vec::as_slice).unwrap_or(&[]);
let fields: Vec<_> = if only_fields.is_empty() {
all_fields.iter().collect()
} else {
all_fields
.iter()
.filter(|f| only_fields.contains(&f.name))
.collect()
};
let effective_prefix = env_prefix(entry, prefix);
let mut map = HashMap::new();
for f in fields {
let decrypted = crypto::decrypt_json(master_key, &f.encrypted)?;
let key = format!(
"{}_{}",
effective_prefix,
f.name.to_uppercase().replace(['-', '.'], "_")
);
map.insert(key, json_to_env_string(&decrypted));
}
Ok(map)
}
fn env_prefix(entry: &Entry, prefix: &str) -> String {
fn env_prefix(entry: &crate::models::Entry, prefix: &str) -> String {
let name_part = entry.name.to_uppercase().replace(['-', '.', ' '], "_");
if prefix.is_empty() {
name_part