use anyhow::Result; use serde_json::Value; use sqlx::PgPool; use std::collections::HashMap; use uuid::Uuid; use crate::crypto; use crate::service::search::{fetch_secrets_for_entries, resolve_entry, resolve_entry_by_id}; /// Decrypt a single named field from an entry. /// `folder` is optional; if omitted and multiple entries share the name, an error is returned. pub async fn get_secret_field( pool: &PgPool, name: &str, folder: Option<&str>, field_name: &str, master_key: &[u8; 32], user_id: Option, ) -> Result { let entry = resolve_entry(pool, name, folder, user_id).await?; let entry_ids = vec![entry.id]; let secrets_map = fetch_secrets_for_entries(pool, &entry_ids).await?; let fields = secrets_map.get(&entry.id).map(Vec::as_slice).unwrap_or(&[]); let field = fields .iter() .find(|f| f.name == field_name) .ok_or_else(|| anyhow::anyhow!("Secret field '{}' not found", field_name))?; crypto::decrypt_json(master_key, &field.encrypted) } /// Decrypt all secret fields from an entry. Returns a map field_name → decrypted Value. /// `folder` is optional; if omitted and multiple entries share the name, an error is returned. pub async fn get_all_secrets( pool: &PgPool, name: &str, folder: Option<&str>, master_key: &[u8; 32], user_id: Option, ) -> Result> { let entry = resolve_entry(pool, name, folder, user_id).await?; let entry_ids = vec![entry.id]; let secrets_map = fetch_secrets_for_entries(pool, &entry_ids).await?; let fields = secrets_map.get(&entry.id).map(Vec::as_slice).unwrap_or(&[]); let mut map = HashMap::new(); for f in fields { let decrypted = crypto::decrypt_json(master_key, &f.encrypted)?; map.insert(f.name.clone(), decrypted); } Ok(map) } /// Decrypt a single named field from an entry, located by its UUID. pub async fn get_secret_field_by_id( pool: &PgPool, entry_id: Uuid, field_name: &str, master_key: &[u8; 32], user_id: Option, ) -> Result { resolve_entry_by_id(pool, entry_id, user_id) .await .map_err(|_| anyhow::anyhow!("Entry with id '{}' not found", entry_id))?; let entry_ids = vec![entry_id]; let secrets_map = fetch_secrets_for_entries(pool, &entry_ids).await?; let fields = secrets_map.get(&entry_id).map(Vec::as_slice).unwrap_or(&[]); let field = fields .iter() .find(|f| f.name == field_name) .ok_or_else(|| anyhow::anyhow!("Secret field '{}' not found", field_name))?; crypto::decrypt_json(master_key, &field.encrypted) } /// Decrypt all secret fields from an entry, located by its UUID. /// Returns a map field_name → decrypted Value. pub async fn get_all_secrets_by_id( pool: &PgPool, entry_id: Uuid, master_key: &[u8; 32], user_id: Option, ) -> Result> { // Validate entry exists (and that it belongs to the requesting user) resolve_entry_by_id(pool, entry_id, user_id) .await .map_err(|_| anyhow::anyhow!("Entry with id '{}' not found", entry_id))?; let entry_ids = vec![entry_id]; let secrets_map = fetch_secrets_for_entries(pool, &entry_ids).await?; let fields = secrets_map.get(&entry_id).map(Vec::as_slice).unwrap_or(&[]); let mut map = HashMap::new(); for f in fields { let decrypted = crypto::decrypt_json(master_key, &f.encrypted)?; map.insert(f.name.clone(), decrypted); } Ok(map) }