Skip to main content

vtcode_auth/credentials/
mode.rs

1//! Storage backend selection for credentials.
2
3/// Preferred storage backend for credentials.
4///
5/// - `Keyring`: Use OS-specific secure storage (macOS Keychain, Windows Credential Manager,
6///   Linux Secret Service). This is the default as it's the most secure option.
7/// - `File`: Use AES-256-GCM encrypted file (requires the `file-storage` feature or
8///   custom implementation)
9/// - `Auto`: Try keyring first, fall back to file if unavailable
10#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
11#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
12#[serde(rename_all = "lowercase")]
13pub enum AuthCredentialsStoreMode {
14    /// Use OS-specific keyring service.
15    /// This is the most secure option as credentials are managed by the OS
16    /// and are not accessible to other users or applications.
17    Keyring,
18    /// Persist credentials in an encrypted file.
19    /// The file is encrypted with AES-256-GCM using a machine-derived key.
20    File,
21    /// Use keyring when available; otherwise, fall back to file.
22    Auto,
23}
24
25impl Default for AuthCredentialsStoreMode {
26    /// Platform-aware default:
27    ///
28    /// - **macOS**: `File` — AES-256-GCM encrypted file with machine-derived key.
29    ///   Avoids macOS Keychain authorization popups that trigger on every new
30    ///   binary (including each release update). Users can opt into `Keyring`
31    ///   via `credential_storage_mode` in `vtcode.toml`.
32    ///
33    /// - **Linux / Windows / others**: `Auto` — try OS keyring (Secret Service
34    ///   / Windows Credential Manager, no popups), fall back to encrypted file.
35    fn default() -> Self {
36        #[cfg(target_os = "macos")]
37        {
38            Self::File
39        }
40        #[cfg(not(target_os = "macos"))]
41        {
42            Self::Auto
43        }
44    }
45}
46
47impl AuthCredentialsStoreMode {
48    /// Resolve `Auto` to the best available concrete backend.
49    /// `Keyring` and `File` pass through unchanged.
50    pub(crate) fn effective_mode(self) -> Self {
51        match self {
52            Self::Auto => {
53                if super::keyring::is_functional() {
54                    Self::Keyring
55                } else {
56                    tracing::debug!("Keyring not available, falling back to file storage");
57                    Self::File
58                }
59            }
60            mode => mode,
61        }
62    }
63}