// 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 { 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, } }