From 5f1fa2557bf26e497ec6c7f7c12ddc8df6719406 Mon Sep 17 00:00:00 2001 From: Mathis <154886644+echobt@users.noreply.github.com> Date: Wed, 11 Mar 2026 03:27:47 +0100 Subject: [PATCH 1/2] fix(auth): add secure auth secret storage APIs --- src-tauri/src/app/settings_commands.rs | 11 ++- src-tauri/src/settings/commands.rs | 125 +++++++++++++++++++++++++ src-tauri/src/settings/secure_store.rs | 45 ++++++--- src/utils/authSecretStorage.ts | 113 ++++++++++++++++++++++ 4 files changed, 281 insertions(+), 13 deletions(-) create mode 100644 src/utils/authSecretStorage.ts diff --git a/src-tauri/src/app/settings_commands.rs b/src-tauri/src/app/settings_commands.rs index 53e184e1..64caab7b 100644 --- a/src-tauri/src/app/settings_commands.rs +++ b/src-tauri/src/app/settings_commands.rs @@ -20,6 +20,15 @@ macro_rules! settings_commands { $crate::settings::commands::settings_delete_api_key, // Settings Sync commands $crate::settings_sync::commands::sync_push, + $crate::settings::commands::settings_set_auth_secret, + $crate::settings::commands::settings_get_auth_secret, + $crate::settings::commands::settings_delete_auth_secret, + // Secure API key commands (keyring-based storage) + $crate::settings::commands::settings_set_api_key, + $crate::settings::commands::settings_get_api_key_exists, + $crate::settings::commands::settings_delete_api_key, + // Settings Sync commands + $crate::settings_sync::commands::sync_push, $crate::settings_sync::commands::sync_pull, $crate::settings_sync::commands::sync_status, $crate::settings_sync::commands::sync_resolve_conflicts, @@ -46,4 +55,4 @@ macro_rules! settings_commands { $crate::keybindings::detect_conflicts, ]) }; -} +} \ No newline at end of file diff --git a/src-tauri/src/settings/commands.rs b/src-tauri/src/settings/commands.rs index 8a5bea8f..42358345 100644 --- a/src-tauri/src/settings/commands.rs +++ b/src-tauri/src/settings/commands.rs @@ -11,6 +11,12 @@ use std::collections::HashMap; use std::fs; +use tauri::{AppHandle, Manager}; +use tracing::info; +use secrecy::ExposeSecret; +use std::collections::HashMap; +use std::fs; + use tauri::{AppHandle, Manager}; use tracing::info; @@ -23,6 +29,43 @@ use super::types::{ WorkbenchSettings, ZenModeSettings, }; +const ALLOWED_API_KEY_NAMES: &[&str] = &[ + "anthropic_api_key", + "google_api_key", + "openai_api_key", + "openrouter_api_key", + "proxy_authorization", + "supermaven_api_key", +]; + +const ALLOWED_AUTH_SECRET_NAMES: &[&str] = &[ + "codespaces_auth_state", + "copilot_api_token", + "copilot_oauth_token", +]; + +fn validate_allowed_key(key_name: &str, allowed_keys: &[&str], kind: &str) -> Result<(), String> { + if allowed_keys.contains(&key_name) { + Ok(()) + } else { + Err(format!("Unsupported {} key: {}", kind, key_name)) + } +} + +fn validate_api_key_name(key_name: &str) -> Result<(), String> { + validate_allowed_key(key_name, ALLOWED_API_KEY_NAMES, "API") +} + +fn validate_auth_secret_name(key_name: &str) -> Result<(), String> { + validate_allowed_key(key_name, ALLOWED_AUTH_SECRET_NAMES, "auth secret") +} +use super::types::{ + AISettings, CommandPaletteSettings, CortexSettings, DebugSettings, EditorSettings, + ExplorerSettings, ExtensionSettingsMap, FilesSettings, HttpSettings, LanguageEditorOverride, + ScreencastModeSettings, SearchSettings, SecuritySettings, TerminalSettings, ThemeSettings, + WorkbenchSettings, ZenModeSettings, +}; + /// Load settings from disk #[tauri::command] pub async fn settings_load(app: AppHandle) -> Result { @@ -364,6 +407,13 @@ pub async fn settings_set_extension( /// Store an API key securely in the keyring #[tauri::command] +pub async fn settings_set_api_key( + app: AppHandle, + key_name: String, + api_key: String, +) -> Result<(), String> { + SecureApiKeyStore::set_api_key(&key_name, &api_key)?; + validate_api_key_name(&key_name)?; pub async fn settings_set_api_key( app: AppHandle, key_name: String, @@ -390,6 +440,18 @@ pub async fn settings_get_api_key_exists(key_name: String) -> Result Result { + let deleted = SecureApiKeyStore::delete_api_key(&key_name)?; + validate_api_key_name(&key_name)?; + validate_api_key_name(&key_name)?; +/// Get an API key from the keyring (returns redacted version for UI) +#[tauri::command] +pub async fn settings_get_api_key_exists(key_name: String) -> Result { + SecureApiKeyStore::has_api_key(&key_name) +} + /// Delete an API key from the keyring #[tauri::command] pub async fn settings_delete_api_key(app: AppHandle, key_name: String) -> Result { @@ -409,3 +471,66 @@ pub async fn settings_delete_api_key(app: AppHandle, key_name: String) -> Result Ok(deleted) } + +#[tauri::command] +pub async fn settings_set_auth_secret(key_name: String, value: String) -> Result<(), String> { + validate_auth_secret_name(&key_name)?; + + if value.trim().is_empty() { + return Err("Auth secret cannot be empty".to_string()); + } + + SecureApiKeyStore::set_secret(&key_name, &value) +} + +#[tauri::command] +pub async fn settings_get_auth_secret(key_name: String) -> Result, String> { + validate_auth_secret_name(&key_name)?; + + Ok(SecureApiKeyStore::get_secret(&key_name)? + .map(|secret| secret.expose_secret().to_string())) +} + +#[tauri::command] +pub async fn settings_delete_auth_secret(key_name: String) -> Result { + validate_auth_secret_name(&key_name)?; + SecureApiKeyStore::delete_secret(&key_name) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn api_key_allowlist_accepts_known_provider_keys() { + for key_name in ALLOWED_API_KEY_NAMES { + assert!(validate_api_key_name(key_name).is_ok(), "expected {} to be allowed", key_name); + } + } + + #[test] + fn api_key_allowlist_rejects_auth_secret_keys() { + assert!(validate_api_key_name("copilot_oauth_token").is_err()); + assert!(validate_api_key_name("codespaces_auth_state").is_err()); + } + + #[test] + fn auth_secret_allowlist_accepts_known_auth_keys() { + for key_name in ALLOWED_AUTH_SECRET_NAMES { + assert!( + validate_auth_secret_name(key_name).is_ok(), + "expected {} to be allowed", + key_name + ); + } + } + + #[test] + fn auth_secret_allowlist_rejects_api_key_names() { + assert!(validate_auth_secret_name("openai_api_key").is_err()); + assert!(validate_auth_secret_name("proxy_authorization").is_err()); + } +} + + Ok(deleted) +} \ No newline at end of file diff --git a/src-tauri/src/settings/secure_store.rs b/src-tauri/src/settings/secure_store.rs index 47cd0526..48a73daf 100644 --- a/src-tauri/src/settings/secure_store.rs +++ b/src-tauri/src/settings/secure_store.rs @@ -6,26 +6,47 @@ use secrecy::SecretString; +use super::KEYRING_SERVICE; use super::KEYRING_SERVICE; /// Secure API key storage manager pub struct SecureApiKeyStore; -impl SecureApiKeyStore { - /// Get keyring entry for an API key + /// Get keyring entry for a secure secret. fn get_entry(key_name: &str) -> Result { - keyring::Entry::new(KEYRING_SERVICE, key_name) - .map_err(|e| format!("Failed to access keyring: {e}")) - } + /// Store a secret securely in the keyring. + pub fn set_secret(key_name: &str, secret: &str) -> Result<(), String> { + .set_password(secret) + .map_err(|e| format!("Failed to store secret: {e}")) + /// Retrieve a secret from the keyring. + pub fn get_secret(key_name: &str) -> Result, String> { + Err(e) => Err(format!("Failed to retrieve secret: {e}")), + /// Delete a secret from the keyring. + pub fn delete_secret(key_name: &str) -> Result { + Err(e) => Err(format!("Failed to delete secret: {e}")), + /// Check if a secret exists in the keyring. + pub fn has_secret(key_name: &str) -> Result { + Err(e) => Err(format!("Failed to check secret: {e}")), - /// Store an API key securely in the keyring + /// Store an API key securely in the keyring. pub fn set_api_key(key_name: &str, api_key: &str) -> Result<(), String> { - let entry = Self::get_entry(key_name)?; - entry - .set_password(api_key) - .map_err(|e| format!("Failed to store API key: {e}")) + Self::set_secret(key_name, api_key) + } + + /// Retrieve an API key from the keyring. + pub fn get_api_key(key_name: &str) -> Result, String> { + Self::get_secret(key_name) } + /// Delete an API key from the keyring. + pub fn delete_api_key(key_name: &str) -> Result { + Self::delete_secret(key_name) + } + + /// Check if an API key exists. + pub fn has_api_key(key_name: &str) -> Result { + Self::has_secret(key_name) + } /// Retrieve an API key from the keyring pub fn get_api_key(key_name: &str) -> Result, String> { let entry = Self::get_entry(key_name)?; @@ -59,5 +80,5 @@ impl SecureApiKeyStore { /// Get API key for internal use (returns actual value as SecretString) pub fn get_api_key_internal(key_name: &str) -> Option { - SecureApiKeyStore::get_api_key(key_name).ok().flatten() -} + SecureApiKeyStore::get_secret(key_name).ok().flatten() +} \ No newline at end of file diff --git a/src/utils/authSecretStorage.ts b/src/utils/authSecretStorage.ts new file mode 100644 index 00000000..27f5bb8e --- /dev/null +++ b/src/utils/authSecretStorage.ts @@ -0,0 +1,113 @@ +import { invoke } from "@tauri-apps/api/core"; +import { safeGetItem, safeRemoveItem } from "./safeStorage"; + +interface SecureJsonOptions { + keyName: string; + legacyKey?: string; + loggerScope: string; + validate?: (value: unknown) => value is T; +} + +interface SaveSecureJsonOptions extends SecureJsonOptions { + value: T; +} + +function logError(scope: string, message: string, error: unknown): void { + console.warn(`[${scope}] ${message}`, error); +} + +function parseStoredJson( + raw: string, + { loggerScope, validate }: SecureJsonOptions +): T | null { + try { + const parsed = JSON.parse(raw); + if (validate && !validate(parsed)) { + console.warn(`[${loggerScope}] Ignoring invalid secure auth payload`); + return null; + } + return parsed as T; + } catch (error) { + logError(loggerScope, "Failed to parse secure auth payload", error); + return null; + } +} + +async function setSecureSecret( + keyName: string, + value: string, + loggerScope: string +): Promise { + try { + await invoke("settings_set_auth_secret", { keyName, value }); + return true; + } catch (error) { + logError(loggerScope, `Failed to store secure auth secret for ${keyName}`, error); + return false; + } +} + +export async function loadSecureJson(options: SecureJsonOptions): Promise { + const { keyName, legacyKey, loggerScope } = options; + + try { + const stored = await invoke("settings_get_auth_secret", { keyName }); + if (typeof stored === "string") { + const parsed = parseStoredJson(stored, options); + if (parsed === null) { + await deleteSecureSecret({ keyName, loggerScope }); + } + if (legacyKey) { + safeRemoveItem(legacyKey); + } + return parsed; + } + } catch (error) { + logError(loggerScope, `Failed to load secure auth secret for ${keyName}`, error); + } + + if (!legacyKey) { + return null; + } + + const legacyValue = safeGetItem(legacyKey); + if (!legacyValue) { + return null; + } + + const parsed = parseStoredJson(legacyValue, options); + safeRemoveItem(legacyKey); + + if (parsed !== null) { + await setSecureSecret(keyName, JSON.stringify(parsed), loggerScope); + } + + return parsed; +} + +export async function saveSecureJson(options: SaveSecureJsonOptions): Promise { + const { keyName, legacyKey, loggerScope, value } = options; + + if (legacyKey) { + safeRemoveItem(legacyKey); + } + + const serialized = JSON.stringify(value); + await setSecureSecret(keyName, serialized, loggerScope); +} + +export async function deleteSecureSecret( + options: Pick, "keyName" | "legacyKey" | "loggerScope"> +): Promise { + const { keyName, legacyKey, loggerScope } = options; + + if (legacyKey) { + safeRemoveItem(legacyKey); + } + + try { + await invoke("settings_delete_auth_secret", { keyName }); + } catch (error) { + logError(loggerScope, `Failed to delete secure auth secret for ${keyName}`, error); + } +} From 1dc64a5b2ab6da4a71853d421b0b0a749fba62e3 Mon Sep 17 00:00:00 2001 From: echobt Date: Wed, 11 Mar 2026 03:09:08 +0000 Subject: [PATCH 2/2] fix: repair auth secret settings CI failures --- src-tauri/src/app/settings_commands.rs | 9 +- src-tauri/src/app/workspace_commands.rs | 2 +- src-tauri/src/lib.rs | 2 +- src-tauri/src/settings/commands.rs | 126 +++++++++++++----------- src-tauri/src/settings/secure_store.rs | 81 ++++++++------- src-tauri/src/workspace/mod.rs | 2 +- 6 files changed, 116 insertions(+), 106 deletions(-) diff --git a/src-tauri/src/app/settings_commands.rs b/src-tauri/src/app/settings_commands.rs index 64caab7b..bd667595 100644 --- a/src-tauri/src/app/settings_commands.rs +++ b/src-tauri/src/app/settings_commands.rs @@ -18,15 +18,10 @@ macro_rules! settings_commands { $crate::settings::commands::settings_set_api_key, $crate::settings::commands::settings_get_api_key_exists, $crate::settings::commands::settings_delete_api_key, - // Settings Sync commands - $crate::settings_sync::commands::sync_push, + // Secure auth secret commands (keyring-based storage) $crate::settings::commands::settings_set_auth_secret, $crate::settings::commands::settings_get_auth_secret, $crate::settings::commands::settings_delete_auth_secret, - // Secure API key commands (keyring-based storage) - $crate::settings::commands::settings_set_api_key, - $crate::settings::commands::settings_get_api_key_exists, - $crate::settings::commands::settings_delete_api_key, // Settings Sync commands $crate::settings_sync::commands::sync_push, $crate::settings_sync::commands::sync_pull, @@ -55,4 +50,4 @@ macro_rules! settings_commands { $crate::keybindings::detect_conflicts, ]) }; -} \ No newline at end of file +} diff --git a/src-tauri/src/app/workspace_commands.rs b/src-tauri/src/app/workspace_commands.rs index 7cab4830..c338b447 100644 --- a/src-tauri/src/app/workspace_commands.rs +++ b/src-tauri/src/app/workspace_commands.rs @@ -170,4 +170,4 @@ macro_rules! workspace_commands { $crate::batch::batch_cache_clear, ]) }; -} \ No newline at end of file +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 7fe81137..7755e365 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -48,9 +48,9 @@ mod sandbox; mod search; mod settings; mod settings_sync; -mod startup_timing; #[cfg(feature = "remote-ssh")] mod ssh_terminal; +mod startup_timing; mod system_specs; mod tasks; mod terminal; diff --git a/src-tauri/src/settings/commands.rs b/src-tauri/src/settings/commands.rs index 42358345..d3f3b699 100644 --- a/src-tauri/src/settings/commands.rs +++ b/src-tauri/src/settings/commands.rs @@ -11,12 +11,7 @@ use std::collections::HashMap; use std::fs; -use tauri::{AppHandle, Manager}; -use tracing::info; use secrecy::ExposeSecret; -use std::collections::HashMap; -use std::fs; - use tauri::{AppHandle, Manager}; use tracing::info; @@ -44,6 +39,9 @@ const ALLOWED_AUTH_SECRET_NAMES: &[&str] = &[ "copilot_oauth_token", ]; +const MAX_API_KEY_LENGTH: usize = 512; +const MAX_AUTH_SECRET_LENGTH: usize = 8 * 1024; + fn validate_allowed_key(key_name: &str, allowed_keys: &[&str], kind: &str) -> Result<(), String> { if allowed_keys.contains(&key_name) { Ok(()) @@ -59,12 +57,23 @@ fn validate_api_key_name(key_name: &str) -> Result<(), String> { fn validate_auth_secret_name(key_name: &str) -> Result<(), String> { validate_allowed_key(key_name, ALLOWED_AUTH_SECRET_NAMES, "auth secret") } -use super::types::{ - AISettings, CommandPaletteSettings, CortexSettings, DebugSettings, EditorSettings, - ExplorerSettings, ExtensionSettingsMap, FilesSettings, HttpSettings, LanguageEditorOverride, - ScreencastModeSettings, SearchSettings, SecuritySettings, TerminalSettings, ThemeSettings, - WorkbenchSettings, ZenModeSettings, -}; + +fn validate_secret_value<'a>( + value: &'a str, + kind: &str, + max_length: usize, +) -> Result<&'a str, String> { + let trimmed = value.trim(); + if trimmed.is_empty() { + return Err(format!("{kind} cannot be empty")); + } + + if trimmed.len() > max_length { + return Err(format!("{kind} exceeds maximum length")); + } + + Ok(trimmed) +} /// Load settings from disk #[tauri::command] @@ -405,6 +414,14 @@ pub async fn settings_set_extension( // === Secure API key commands === +fn set_api_key_presence(settings: &mut CortexSettings, key_name: &str, exists: bool) { + match key_name { + "supermaven_api_key" => settings.ai.has_supermaven_api_key = exists, + "proxy_authorization" => settings.http.has_proxy_authorization = exists, + _ => {} + } +} + /// Store an API key securely in the keyring #[tauri::command] pub async fn settings_set_api_key( @@ -412,23 +429,14 @@ pub async fn settings_set_api_key( key_name: String, api_key: String, ) -> Result<(), String> { - SecureApiKeyStore::set_api_key(&key_name, &api_key)?; validate_api_key_name(&key_name)?; -pub async fn settings_set_api_key( - app: AppHandle, - key_name: String, - api_key: String, -) -> Result<(), String> { - SecureApiKeyStore::set_api_key(&key_name, &api_key)?; + let api_key = validate_secret_value(&api_key, "API key", MAX_API_KEY_LENGTH)?; + SecureApiKeyStore::set_api_key(&key_name, api_key)?; // Update settings to reflect that key exists let settings_state = app.state::(); if let Ok(mut settings) = settings_state.0.lock() { - match key_name.as_str() { - "supermaven_api_key" => settings.ai.has_supermaven_api_key = true, - "proxy_authorization" => settings.http.has_proxy_authorization = true, - _ => {} - } + set_api_key_presence(&mut settings, &key_name, true); } Ok(()) @@ -437,60 +445,43 @@ pub async fn settings_set_api_key( /// Get an API key from the keyring (returns redacted version for UI) #[tauri::command] pub async fn settings_get_api_key_exists(key_name: String) -> Result { - SecureApiKeyStore::has_api_key(&key_name) -} - -/// Delete an API key from the keyring -#[tauri::command] -pub async fn settings_delete_api_key(app: AppHandle, key_name: String) -> Result { - let deleted = SecureApiKeyStore::delete_api_key(&key_name)?; - validate_api_key_name(&key_name)?; validate_api_key_name(&key_name)?; -/// Get an API key from the keyring (returns redacted version for UI) -#[tauri::command] -pub async fn settings_get_api_key_exists(key_name: String) -> Result { SecureApiKeyStore::has_api_key(&key_name) } /// Delete an API key from the keyring #[tauri::command] pub async fn settings_delete_api_key(app: AppHandle, key_name: String) -> Result { + validate_api_key_name(&key_name)?; let deleted = SecureApiKeyStore::delete_api_key(&key_name)?; // Update settings to reflect that key is gone if deleted { let settings_state = app.state::(); if let Ok(mut settings) = settings_state.0.lock() { - match key_name.as_str() { - "supermaven_api_key" => settings.ai.has_supermaven_api_key = false, - "proxy_authorization" => settings.http.has_proxy_authorization = false, - _ => {} - } - }; + set_api_key_presence(&mut settings, &key_name, false); + } } Ok(deleted) } +/// Store an auth secret securely in the keyring #[tauri::command] pub async fn settings_set_auth_secret(key_name: String, value: String) -> Result<(), String> { validate_auth_secret_name(&key_name)?; - - if value.trim().is_empty() { - return Err("Auth secret cannot be empty".to_string()); - } - - SecureApiKeyStore::set_secret(&key_name, &value) + let value = validate_secret_value(&value, "Auth secret", MAX_AUTH_SECRET_LENGTH)?; + SecureApiKeyStore::set_secret(&key_name, value) } +/// Retrieve an auth secret from the keyring #[tauri::command] pub async fn settings_get_auth_secret(key_name: String) -> Result, String> { validate_auth_secret_name(&key_name)?; - - Ok(SecureApiKeyStore::get_secret(&key_name)? - .map(|secret| secret.expose_secret().to_string())) + Ok(SecureApiKeyStore::get_secret(&key_name)?.map(|secret| secret.expose_secret().to_string())) } +/// Delete an auth secret from the keyring #[tauri::command] pub async fn settings_delete_auth_secret(key_name: String) -> Result { validate_auth_secret_name(&key_name)?; @@ -504,7 +495,10 @@ mod tests { #[test] fn api_key_allowlist_accepts_known_provider_keys() { for key_name in ALLOWED_API_KEY_NAMES { - assert!(validate_api_key_name(key_name).is_ok(), "expected {} to be allowed", key_name); + assert!( + validate_api_key_name(key_name).is_ok(), + "expected {key_name} to be allowed" + ); } } @@ -519,8 +513,7 @@ mod tests { for key_name in ALLOWED_AUTH_SECRET_NAMES { assert!( validate_auth_secret_name(key_name).is_ok(), - "expected {} to be allowed", - key_name + "expected {key_name} to be allowed" ); } } @@ -530,7 +523,30 @@ mod tests { assert!(validate_auth_secret_name("openai_api_key").is_err()); assert!(validate_auth_secret_name("proxy_authorization").is_err()); } -} - Ok(deleted) -} \ No newline at end of file + #[test] + fn validate_secret_value_trims_valid_input() { + assert!(matches!( + validate_secret_value(" secret ", "Auth secret", MAX_AUTH_SECRET_LENGTH), + Ok("secret") + )); + } + + #[test] + fn validate_secret_value_rejects_empty_input() { + assert!(matches!( + validate_secret_value(" ", "Auth secret", MAX_AUTH_SECRET_LENGTH), + Err(ref error) if error == "Auth secret cannot be empty" + )); + } + + #[test] + fn validate_secret_value_rejects_oversized_input() { + let oversized = "a".repeat(MAX_AUTH_SECRET_LENGTH + 1); + + assert!(matches!( + validate_secret_value(&oversized, "Auth secret", MAX_AUTH_SECRET_LENGTH), + Err(ref error) if error == "Auth secret exceeds maximum length" + )); + } +} diff --git a/src-tauri/src/settings/secure_store.rs b/src-tauri/src/settings/secure_store.rs index 48a73daf..e09de1fc 100644 --- a/src-tauri/src/settings/secure_store.rs +++ b/src-tauri/src/settings/secure_store.rs @@ -6,79 +6,78 @@ use secrecy::SecretString; -use super::KEYRING_SERVICE; use super::KEYRING_SERVICE; /// Secure API key storage manager pub struct SecureApiKeyStore; - /// Get keyring entry for a secure secret. +impl SecureApiKeyStore { + /// Get keyring entry for a secure secret fn get_entry(key_name: &str) -> Result { - /// Store a secret securely in the keyring. + keyring::Entry::new(KEYRING_SERVICE, key_name) + .map_err(|e| format!("Failed to access keyring: {e}")) + } + + /// Store a secret securely in the keyring pub fn set_secret(key_name: &str, secret: &str) -> Result<(), String> { + let entry = Self::get_entry(key_name)?; + entry .set_password(secret) .map_err(|e| format!("Failed to store secret: {e}")) - /// Retrieve a secret from the keyring. + } + + /// Retrieve a secret from the keyring pub fn get_secret(key_name: &str) -> Result, String> { + let entry = Self::get_entry(key_name)?; + match entry.get_password() { + Ok(secret) => Ok(Some(SecretString::from(secret))), + Err(keyring::Error::NoEntry) => Ok(None), Err(e) => Err(format!("Failed to retrieve secret: {e}")), - /// Delete a secret from the keyring. + } + } + + /// Delete a secret from the keyring pub fn delete_secret(key_name: &str) -> Result { + let entry = Self::get_entry(key_name)?; + match entry.delete_credential() { + Ok(()) => Ok(true), + Err(keyring::Error::NoEntry) => Ok(false), Err(e) => Err(format!("Failed to delete secret: {e}")), - /// Check if a secret exists in the keyring. + } + } + + /// Check if a secret exists pub fn has_secret(key_name: &str) -> Result { + let entry = Self::get_entry(key_name)?; + match entry.get_password() { + Ok(_) => Ok(true), + Err(keyring::Error::NoEntry) => Ok(false), Err(e) => Err(format!("Failed to check secret: {e}")), + } + } - /// Store an API key securely in the keyring. + /// Store an API key securely in the keyring pub fn set_api_key(key_name: &str, api_key: &str) -> Result<(), String> { Self::set_secret(key_name, api_key) } - /// Retrieve an API key from the keyring. - pub fn get_api_key(key_name: &str) -> Result, String> { - Self::get_secret(key_name) - } - - /// Delete an API key from the keyring. - pub fn delete_api_key(key_name: &str) -> Result { - Self::delete_secret(key_name) - } - - /// Check if an API key exists. - pub fn has_api_key(key_name: &str) -> Result { - Self::has_secret(key_name) - } /// Retrieve an API key from the keyring pub fn get_api_key(key_name: &str) -> Result, String> { - let entry = Self::get_entry(key_name)?; - match entry.get_password() { - Ok(key) => Ok(Some(SecretString::from(key))), - Err(keyring::Error::NoEntry) => Ok(None), - Err(e) => Err(format!("Failed to retrieve API key: {e}")), - } + Self::get_secret(key_name) } /// Delete an API key from the keyring pub fn delete_api_key(key_name: &str) -> Result { - let entry = Self::get_entry(key_name)?; - match entry.delete_credential() { - Ok(()) => Ok(true), - Err(keyring::Error::NoEntry) => Ok(false), - Err(e) => Err(format!("Failed to delete API key: {e}")), - } + Self::delete_secret(key_name) } /// Check if an API key exists pub fn has_api_key(key_name: &str) -> Result { - let entry = Self::get_entry(key_name)?; - match entry.get_password() { - Ok(_) => Ok(true), - Err(keyring::Error::NoEntry) => Ok(false), - Err(e) => Err(format!("Failed to check API key: {e}")), - } + Self::has_secret(key_name) } } /// Get API key for internal use (returns actual value as SecretString) pub fn get_api_key_internal(key_name: &str) -> Option { - SecureApiKeyStore::get_secret(key_name).ok().flatten() -} \ No newline at end of file + SecureApiKeyStore::get_api_key(key_name).ok().flatten() +} diff --git a/src-tauri/src/workspace/mod.rs b/src-tauri/src/workspace/mod.rs index f3cd1648..3527cef0 100644 --- a/src-tauri/src/workspace/mod.rs +++ b/src-tauri/src/workspace/mod.rs @@ -10,4 +10,4 @@ pub mod multi_root; pub mod types; pub use commands::*; -pub use core::*; \ No newline at end of file +pub use core::*;