auths_cli/factories/
mod.rs1pub mod storage;
2
3use std::io::IsTerminal;
4use std::sync::Arc;
5
6use anyhow::Result;
7
8use auths_sdk::core_config::{EnvironmentConfig, load_config};
9use auths_sdk::keychain::{get_passphrase_cache, parse_duration_str};
10use auths_sdk::paths::auths_home;
11use auths_sdk::ports::agent::AgentSigningPort;
12use auths_sdk::signing::{KeychainPassphraseProvider, PassphraseProvider};
13use auths_telemetry::TelemetryShutdown;
14use auths_telemetry::config::{build_sinks_from_config, load_audit_config};
15use auths_telemetry::sinks::composite::CompositeSink;
16
17use crate::adapters::config_store::FileConfigStore;
18use crate::cli::AuthsCli;
19use crate::config::{CliConfig, OutputFormat};
20use crate::core::provider::{CliPassphraseProvider, PrefilledPassphraseProvider};
21
22pub fn build_config(cli: &AuthsCli) -> Result<CliConfig> {
39 let output_format = if cli.json {
40 OutputFormat::Json
41 } else {
42 OutputFormat::Text
43 };
44
45 let env_config = EnvironmentConfig::from_env();
46
47 let passphrase_provider: Arc<dyn PassphraseProvider + Send + Sync> =
48 if let Some(passphrase) = env_config.keychain.passphrase.clone() {
49 Arc::new(PrefilledPassphraseProvider::new(zeroize::Zeroizing::new(
50 passphrase,
51 )))
52 } else {
53 let config = load_config(&FileConfigStore);
54 let cache = get_passphrase_cache(config.passphrase.biometric);
55 let ttl_secs = config
56 .passphrase
57 .duration
58 .as_deref()
59 .and_then(parse_duration_str);
60 let inner = Arc::new(CliPassphraseProvider::new());
61 Arc::new(KeychainPassphraseProvider::new(
62 inner,
63 cache,
64 "default".to_string(),
65 config.passphrase.cache,
66 ttl_secs,
67 ))
68 };
69
70 let is_interactive = std::io::stdout().is_terminal();
71
72 let repo_path = resolve_repo_root(cli.repo.clone());
77
78 Ok(CliConfig {
79 repo_path,
80 output_format,
81 is_interactive,
82 passphrase_provider,
83 env_config,
84 })
85}
86
87fn resolve_repo_root(repo_flag: Option<std::path::PathBuf>) -> Option<std::path::PathBuf> {
101 #[allow(clippy::disallowed_methods)] let auths_repo = std::env::var("AUTHS_REPO").ok().filter(|s| !s.is_empty());
103 #[allow(clippy::disallowed_methods)] let auths_home = std::env::var("AUTHS_HOME").ok().filter(|s| !s.is_empty());
105
106 let (root, via_home_alias) = pick_repo_root(repo_flag, auths_repo, auths_home);
107 if via_home_alias {
108 eprintln!(
109 "warning: AUTHS_HOME is deprecated; prefer AUTHS_REPO to override the storage root"
110 );
111 }
112 root
113}
114
115fn pick_repo_root(
131 repo_flag: Option<std::path::PathBuf>,
132 auths_repo: Option<String>,
133 auths_home: Option<String>,
134) -> (Option<std::path::PathBuf>, bool) {
135 if let Some(flag) = repo_flag {
136 return (Some(flag), false);
137 }
138 if let Some(repo) = auths_repo {
139 return (Some(std::path::PathBuf::from(repo)), false);
140 }
141 match auths_home {
142 Some(home) => (Some(std::path::PathBuf::from(home)), true),
143 None => (None, false),
144 }
145}
146
147pub fn init_audit_sinks() -> Option<TelemetryShutdown> {
157 let audit_path = match auths_home() {
158 Ok(h) => h.join("audit.toml"),
159 Err(_) => return None,
160 };
161 let config = load_audit_config(&audit_path);
162 #[allow(clippy::disallowed_methods)] let sinks = build_sinks_from_config(&config, |name| std::env::var(name).ok());
164 if sinks.is_empty() {
165 return None;
166 }
167 let composite = Arc::new(CompositeSink::new(sinks));
168 Some(auths_telemetry::init_telemetry_with_sink(composite))
169}
170
171pub fn build_agent_provider() -> Arc<dyn AgentSigningPort + Send + Sync> {
181 #[cfg(unix)]
182 {
183 Arc::new(crate::adapters::agent::CliAgentAdapter)
184 }
185 #[cfg(not(unix))]
186 {
187 Arc::new(auths_sdk::ports::agent::NoopAgentProvider)
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::pick_repo_root;
194 use std::path::PathBuf;
195
196 #[test]
197 fn repo_flag_wins_over_both_env_vars() {
198 let (root, via_home) = pick_repo_root(
199 Some(PathBuf::from("/flag")),
200 Some("/repo".into()),
201 Some("/home".into()),
202 );
203 assert_eq!(root, Some(PathBuf::from("/flag")));
204 assert!(!via_home);
205 }
206
207 #[test]
208 fn auths_repo_wins_over_auths_home() {
209 let (root, via_home) = pick_repo_root(None, Some("/repo".into()), Some("/home".into()));
210 assert_eq!(root, Some(PathBuf::from("/repo")));
211 assert!(!via_home);
212 }
213
214 #[test]
215 fn auths_home_populates_repo_path_with_warning() {
216 let (root, via_home) = pick_repo_root(None, None, Some("/home".into()));
220 assert_eq!(root, Some(PathBuf::from("/home")));
221 assert!(via_home, "AUTHS_HOME must trigger the deprecation warning");
222 }
223
224 #[test]
225 fn no_override_leaves_default_root() {
226 let (root, via_home) = pick_repo_root(None, None, None);
227 assert_eq!(root, None);
228 assert!(!via_home);
229 }
230}