Skip to main content

aether_cli/
credentials.rs

1use crate::settings_args::SettingsSourceArgs;
2use aether_auth::{
3    EncryptedFileOAuthCredentialStorage, FakeOAuthCredentialStore, OAuthCredentialStorage, OAuthError, OsKeyringStore,
4};
5use aether_project::{AetherSettings, CredentialsStoreConfig};
6use std::path::Path;
7use std::sync::Arc;
8
9pub fn build_oauth_credential_store(
10    settings_source: &SettingsSourceArgs,
11    cwd: &Path,
12) -> Result<Arc<dyn OAuthCredentialStorage>, OAuthError> {
13    let result = match settings_source.source(cwd) {
14        Some(source) => AetherSettings::load(cwd, [source]),
15        None => AetherSettings::load_default(cwd),
16    };
17
18    let settings_source = match result {
19        Ok(settings) => Ok(settings.credentials_store),
20        Err(e) => Err(OAuthError::CredentialStore(format!("Failed to load settings for credential store config: {e}"))),
21    }?;
22
23    match settings_source {
24        Some(CredentialsStoreConfig::Memory) => Ok(Arc::new(FakeOAuthCredentialStore::new())),
25        Some(CredentialsStoreConfig::EncryptedFile { path, password_env }) => {
26            Ok(Arc::new(EncryptedFileOAuthCredentialStorage::from_settings(path, password_env.as_deref())?))
27        }
28        Some(CredentialsStoreConfig::Keyring) | None => Ok(Arc::new(OsKeyringStore::with_platform_store())),
29    }
30}
31
32#[cfg(test)]
33mod tests {
34    use super::*;
35
36    fn json_source(json: &str) -> SettingsSourceArgs {
37        SettingsSourceArgs { settings_json: Some(json.to_string()), settings_file: None }
38    }
39
40    #[test]
41    fn memory_store_selected_from_settings() {
42        let source = json_source(
43            r#"{
44                "credentialsStore": { "type": "memory" },
45                "agents": [{"name":"a","description":"a","model":"anthropic:claude-sonnet-4-5","userInvocable":true}]
46            }"#,
47        );
48
49        let store = build_oauth_credential_store(&source, Path::new(".")).unwrap();
50        assert!(!store.has_credential("anything"));
51    }
52
53    #[test]
54    fn encrypted_file_from_settings_propagates_missing_passphrase_error() {
55        let source = json_source(
56            r#"{
57                "credentialsStore": { "type": "encryptedFile", "passwordEnv": "AETHER_DEFINITELY_UNSET_VAR_98765" },
58                "agents": [{"name":"a","description":"a","model":"anthropic:claude-sonnet-4-5","userInvocable":true}]
59            }"#,
60        );
61
62        let result = build_oauth_credential_store(&source, Path::new("."));
63        assert!(result.is_err(), "missing passphrase should surface as a clean error");
64    }
65}