Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
- 新增 66 个 @reserved 标注 (已有 22 个) - 覆盖: agent/butler/classroom/hand/mcp/pipeline/skill/trigger/viking/zclaw 等模块 - MCP 命令增加 @connected 注释说明前端接入路径 - @reserved 总数: 89 (含 identity_init)
106 lines
3.0 KiB
Rust
106 lines
3.0 KiB
Rust
// Secure storage module for ZCLAW desktop app
|
|
// Uses the OS keyring/keychain for secure credential storage
|
|
// - Windows: DPAPI
|
|
// - macOS: Keychain
|
|
// - Linux: Secret Service API (gnome-keyring, kwallet, etc.)
|
|
|
|
use keyring::Entry;
|
|
|
|
const SERVICE_NAME: &str = "zclaw";
|
|
|
|
/// Store a value securely in the OS keyring
|
|
// @connected
|
|
#[tauri::command]
|
|
pub fn secure_store_set(key: String, value: String) -> Result<(), String> {
|
|
let entry = Entry::new(SERVICE_NAME, &key).map_err(|e| {
|
|
format!(
|
|
"Failed to create keyring entry for '{}': {}",
|
|
key,
|
|
e.to_string()
|
|
)
|
|
})?;
|
|
|
|
entry.set_password(&value).map_err(|e| {
|
|
format!(
|
|
"Failed to store value for key '{}': {}",
|
|
key,
|
|
e.to_string()
|
|
)
|
|
})?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Retrieve a value from the OS keyring
|
|
// @reserved: secure storage access
|
|
// @connected
|
|
#[tauri::command]
|
|
pub fn secure_store_get(key: String) -> Result<String, String> {
|
|
let entry = Entry::new(SERVICE_NAME, &key).map_err(|e| {
|
|
format!(
|
|
"Failed to create keyring entry for '{}': {}",
|
|
key,
|
|
e.to_string()
|
|
)
|
|
})?;
|
|
|
|
entry.get_password().map_err(|e| {
|
|
// Return empty string for "not found" errors to distinguish from actual errors
|
|
let error_str = e.to_string();
|
|
if error_str.contains("No matching entry") || error_str.contains("not found") {
|
|
String::new()
|
|
} else {
|
|
format!("Failed to retrieve value for key '{}': {}", key, error_str)
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Delete a value from the OS keyring
|
|
// @connected
|
|
#[tauri::command]
|
|
pub fn secure_store_delete(key: String) -> Result<(), String> {
|
|
let entry = Entry::new(SERVICE_NAME, &key).map_err(|e| {
|
|
format!(
|
|
"Failed to create keyring entry for '{}': {}",
|
|
key,
|
|
e.to_string()
|
|
)
|
|
})?;
|
|
|
|
match entry.delete_credential() {
|
|
Ok(()) => Ok(()),
|
|
Err(e) => {
|
|
let error_str = e.to_string();
|
|
// Don't fail if the entry doesn't exist
|
|
if error_str.contains("No matching entry") || error_str.contains("not found") {
|
|
Ok(())
|
|
} else {
|
|
Err(format!("Failed to delete value for key '{}': {}", key, error_str))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Check if secure storage is available on this platform
|
|
// @reserved: secure storage access
|
|
// @connected
|
|
#[tauri::command]
|
|
pub fn secure_store_is_available() -> bool {
|
|
// Try to create a test entry to verify keyring is working
|
|
let test_key = "__zclaw_availability_test__";
|
|
match Entry::new(SERVICE_NAME, test_key) {
|
|
Ok(entry) => {
|
|
// Try to set and delete a test value
|
|
match entry.set_password("test") {
|
|
Ok(_) => {
|
|
// Clean up the test entry
|
|
let _ = entry.delete_credential();
|
|
true
|
|
}
|
|
Err(_) => false,
|
|
}
|
|
}
|
|
Err(_) => false,
|
|
}
|
|
}
|