Skip to main content

vtcode_auth/credentials/
mod.rs

1//! Credential storage — keyring and encrypted-file backends.
2//!
3//! # Module Structure
4//!
5//! | Submodule | Responsibility |
6//! |---|---|
7//! | [`mode`] | Backend selection enum (`Keyring` / `File` / `Auto`) |
8//! | [`keyring`] | OS keyring creation, liveness, disable detection |
9//! | [`encryption`] | AES-256-GCM encrypt/decrypt (pure, no IO) |
10//! | [`storage`] | `CredentialStorage` — orchestrates backends |
11//! | [`legacy`] | Legacy `auth.json` migration |
12
13mod encryption;
14pub(crate) mod keyring;
15mod legacy;
16mod mode;
17mod storage;
18
19pub use mode::AuthCredentialsStoreMode;
20pub use storage::CredentialStorage;
21
22use std::collections::BTreeMap;
23
24use anyhow::Result;
25
26/// Custom API Key storage for provider-specific keys.
27///
28/// Provides secure storage and retrieval of API keys for custom providers
29/// using the OS keyring or encrypted file storage.
30pub struct CustomApiKeyStorage {
31    provider: String,
32    storage: CredentialStorage,
33}
34
35impl CustomApiKeyStorage {
36    /// Create a new custom API key storage for a specific provider.
37    pub fn new(provider: &str) -> Self {
38        let normalized_provider = provider.to_lowercase();
39        Self {
40            provider: normalized_provider.clone(),
41            storage: CredentialStorage::new("vtcode", format!("api_key_{normalized_provider}")),
42        }
43    }
44
45    /// Store an API key securely.
46    pub fn store(&self, api_key: &str, mode: AuthCredentialsStoreMode) -> Result<()> {
47        self.storage.store_with_mode(api_key, mode)?;
48        let _ = legacy::clear_for_provider(&self.provider);
49        Ok(())
50    }
51
52    /// Retrieve a stored API key.
53    pub fn load(&self, mode: AuthCredentialsStoreMode) -> Result<Option<String>> {
54        if let Some(key) = self.storage.load_with_mode(mode)? {
55            return Ok(Some(key));
56        }
57
58        self.load_legacy_auth_json(mode)
59    }
60
61    /// Clear (delete) a stored API key.
62    pub fn clear(&self, mode: AuthCredentialsStoreMode) -> Result<()> {
63        self.storage.clear_with_mode(mode)?;
64        let _ = legacy::clear_for_provider(&self.provider);
65        Ok(())
66    }
67
68    fn load_legacy_auth_json(&self, mode: AuthCredentialsStoreMode) -> Result<Option<String>> {
69        let Some(legacy_entry) = legacy::load_for_provider(&self.provider)? else {
70            return Ok(None);
71        };
72
73        if let Err(err) = self.storage.store_with_mode(&legacy_entry.api_key, mode) {
74            tracing::warn!(
75                "Failed to migrate legacy plaintext auth.json entry for provider '{}' into secure storage: {}",
76                self.provider,
77                err
78            );
79            return Ok(Some(legacy_entry.api_key));
80        }
81
82        let path = crate::storage_paths::legacy_auth_storage_path().ok();
83        if let Some(p) = path {
84            let _ = legacy::delete_file(&p);
85        }
86
87        tracing::warn!(
88            "Migrated legacy plaintext auth.json entry for provider '{}' into secure storage",
89            self.provider
90        );
91        Ok(Some(legacy_entry.api_key))
92    }
93}
94
95/// Migrate plain-text API keys from a config map into secure storage.
96///
97/// Returns a map of provider → success/failure.
98pub fn migrate_custom_api_keys(
99    custom_api_keys: &BTreeMap<String, String>,
100    mode: AuthCredentialsStoreMode,
101) -> Result<BTreeMap<String, bool>> {
102    let mut results = BTreeMap::new();
103
104    for (provider, api_key) in custom_api_keys {
105        let storage = CustomApiKeyStorage::new(provider);
106        match storage.store(api_key, mode) {
107            Ok(()) => {
108                tracing::info!("Migrated API key for provider '{provider}' to secure storage");
109                results.insert(provider.clone(), true);
110            }
111            Err(e) => {
112                tracing::warn!("Failed to migrate API key for provider '{provider}': {e}");
113                results.insert(provider.clone(), false);
114            }
115        }
116    }
117
118    Ok(results)
119}
120
121/// Load all custom API keys from secure storage.
122///
123/// Returns a map of provider → API key for those that have stored keys.
124pub fn load_custom_api_keys(providers: &[String], mode: AuthCredentialsStoreMode) -> Result<BTreeMap<String, String>> {
125    let mut api_keys = BTreeMap::new();
126
127    for provider in providers {
128        let storage = CustomApiKeyStorage::new(provider);
129        if let Some(key) = storage.load(mode)? {
130            api_keys.insert(provider.clone(), key);
131        }
132    }
133
134    Ok(api_keys)
135}
136
137/// Clear all custom API keys from secure storage.
138pub fn clear_custom_api_keys(providers: &[String], mode: AuthCredentialsStoreMode) -> Result<()> {
139    for provider in providers {
140        let storage = CustomApiKeyStorage::new(provider);
141        if let Err(e) = storage.clear(mode) {
142            tracing::warn!("Failed to clear API key for provider '{provider}': {e}");
143        }
144    }
145    Ok(())
146}