fix(secrets-mcp 0.5.20): code review plan — export secret types, env map, rollback, API key, MCP tools, web session & validation

- Export/import: optional secret_types map; AddResult includes entry_id
- env_map: dot→__ segment encoding; collision errors
- rollback: FOR UPDATE + txn-consistent snapshot; restore name from history
- regenerate_api_key: rows_affected guard
- MCP: find count propagates errors; add uses entry_id for relations; rollback no encryption key
- Web: load_session_user_strict + JSON handlers key_version; PATCH length limits
- Tests: ExportEntry serde, env segment
This commit is contained in:
voson
2026-04-11 17:10:16 +08:00
parent 2c7dbf890b
commit d772066210
13 changed files with 266 additions and 141 deletions

View File

@@ -25,8 +25,8 @@ use secrets_core::service::{
use crate::AppState;
use super::{
ENTRIES_PAGE_LIMIT, UiLang, current_user_id, paginate, render_template, request_ui_lang,
require_valid_user, tr,
ENTRIES_PAGE_LIMIT, UiLang, paginate, render_template, request_ui_lang, require_valid_user,
require_valid_user_json, tr,
};
// ── Template types ────────────────────────────────────────────────────────────
@@ -616,10 +616,8 @@ pub(super) async fn api_entry_patch(
Json(body): Json<EntryPatchBody>,
) -> Result<Json<serde_json::Value>, EntryApiError> {
let lang = request_ui_lang(&headers);
let user_id = current_user_id(&session).await.ok_or((
StatusCode::UNAUTHORIZED,
Json(json!({ "error": tr(lang, "未登录", "尚未登入", "Not logged in") })),
))?;
let user = require_valid_user_json(&state.pool, &session, lang).await?;
let user_id = user.id;
let folder = body.folder.trim();
let entry_type = body.entry_type.trim();
@@ -635,6 +633,39 @@ pub(super) async fn api_entry_patch(
));
}
if folder.chars().count() > crate::validation::MAX_FOLDER_LENGTH {
return Err((
StatusCode::BAD_REQUEST,
Json(
json!({ "error": tr(lang, "folder 长度不能超过 128 个字符", "folder 長度不能超過 128 個字元", "folder must be at most 128 characters") }),
),
));
}
if entry_type.chars().count() > crate::validation::MAX_ENTRY_TYPE_LENGTH {
return Err((
StatusCode::BAD_REQUEST,
Json(
json!({ "error": tr(lang, "type 长度不能超过 64 个字符", "type 長度不能超過 64 個字元", "type must be at most 64 characters") }),
),
));
}
if name.chars().count() > crate::validation::MAX_NAME_LENGTH {
return Err((
StatusCode::BAD_REQUEST,
Json(
json!({ "error": tr(lang, "name 长度不能超过 256 个字符", "name 長度不能超過 256 個字元", "name must be at most 256 characters") }),
),
));
}
if notes.chars().count() > crate::validation::MAX_NOTES_LENGTH {
return Err((
StatusCode::BAD_REQUEST,
Json(
json!({ "error": tr(lang, "notes 长度不能超过 10000 个字符", "notes 長度不能超過 10000 個字元", "notes must be at most 10000 characters") }),
),
));
}
let tags: Vec<String> = body
.tags
.into_iter()
@@ -683,10 +714,8 @@ pub(super) async fn api_entry_options(
Query(q): Query<EntryOptionQuery>,
) -> Result<Json<serde_json::Value>, EntryApiError> {
let lang = request_ui_lang(&headers);
let user_id = current_user_id(&session).await.ok_or((
StatusCode::UNAUTHORIZED,
Json(json!({ "error": tr(lang, "未登录", "尚未登入", "Not logged in") })),
))?;
let user = require_valid_user_json(&state.pool, &session, lang).await?;
let user_id = user.id;
let query =
q.q.as_deref()
@@ -738,10 +767,8 @@ pub(super) async fn api_entry_delete(
Path(entry_id): Path<Uuid>,
) -> Result<Json<serde_json::Value>, EntryApiError> {
let lang = request_ui_lang(&headers);
let user_id = current_user_id(&session).await.ok_or((
StatusCode::UNAUTHORIZED,
Json(json!({ "error": tr(lang, "未登录", "尚未登入", "Not logged in") })),
))?;
let user = require_valid_user_json(&state.pool, &session, lang).await?;
let user_id = user.id;
delete_by_id(&state.pool, entry_id, user_id)
.await
@@ -760,10 +787,8 @@ pub(super) async fn api_trash_restore(
Path(entry_id): Path<Uuid>,
) -> Result<Json<serde_json::Value>, EntryApiError> {
let lang = request_ui_lang(&headers);
let user_id = current_user_id(&session).await.ok_or((
StatusCode::UNAUTHORIZED,
Json(json!({ "error": tr(lang, "未登录", "尚未登入", "Not logged in") })),
))?;
let user = require_valid_user_json(&state.pool, &session, lang).await?;
let user_id = user.id;
restore_deleted_by_id(&state.pool, entry_id, user_id)
.await
@@ -782,10 +807,8 @@ pub(super) async fn api_trash_purge(
Path(entry_id): Path<Uuid>,
) -> Result<Json<serde_json::Value>, EntryApiError> {
let lang = request_ui_lang(&headers);
let user_id = current_user_id(&session).await.ok_or((
StatusCode::UNAUTHORIZED,
Json(json!({ "error": tr(lang, "未登录", "尚未登入", "Not logged in") })),
))?;
let user = require_valid_user_json(&state.pool, &session, lang).await?;
let user_id = user.id;
purge_deleted_by_id(&state.pool, entry_id, user_id)
.await
@@ -818,10 +841,8 @@ pub(super) async fn api_secret_check_name(
Query(params): Query<SecretCheckNameQuery>,
) -> Result<Json<SecretCheckNameResponse>, EntryApiError> {
let lang = request_ui_lang(&headers);
let user_id = current_user_id(&session).await.ok_or((
StatusCode::UNAUTHORIZED,
Json(json!({ "error": tr(lang, "未登录", "尚未登入", "Not logged in") })),
))?;
let user = require_valid_user_json(&state.pool, &session, lang).await?;
let user_id = user.id;
let name = params.name.trim();
if name.is_empty() {
@@ -914,10 +935,8 @@ pub(super) async fn api_secret_patch(
}
let lang = request_ui_lang(&headers);
let user_id = current_user_id(&session).await.ok_or((
StatusCode::UNAUTHORIZED,
Json(json!({ "error": tr(lang, "未登录", "尚未登入", "Not logged in") })),
))?;
let user = require_valid_user_json(&state.pool, &session, lang).await?;
let user_id = user.id;
let name = body.name.as_ref().map(|s| s.trim());
let secret_type = body.secret_type.as_ref().map(|s| s.trim());
@@ -1123,10 +1142,8 @@ pub(super) async fn api_entry_secret_unlink(
}
let lang = request_ui_lang(&headers);
let user_id = current_user_id(&session).await.ok_or((
StatusCode::UNAUTHORIZED,
Json(json!({ "error": tr(lang, "未登录", "尚未登入", "Not logged in") })),
))?;
let user = require_valid_user_json(&state.pool, &session, lang).await?;
let user_id = user.id;
let mut tx = state
.pool
@@ -1216,10 +1233,8 @@ pub(super) async fn api_entry_secrets_decrypt(
Path(entry_id): Path<Uuid>,
) -> Result<Json<serde_json::Value>, EntryApiError> {
let lang = request_ui_lang(&headers);
let user_id = current_user_id(&session).await.ok_or((
StatusCode::UNAUTHORIZED,
Json(json!({ "error": tr(lang, "未登录", "尚未登入", "Not logged in") })),
))?;
let user = require_valid_user_json(&state.pool, &session, lang).await?;
let user_id = user.id;
let master_key = require_encryption_key(&headers, lang)?;