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