auths_cli/core/provider.rs
1use anyhow::Context;
2use auths_core::error::AgentError;
3use auths_core::signing::PassphraseProvider;
4use zeroize::Zeroizing;
5
6/// A PassphraseProvider implementation that prompts the user on the command line.
7#[derive(Debug, Clone, Default)]
8pub struct CliPassphraseProvider;
9
10impl CliPassphraseProvider {
11 /// Creates a new instance.
12 pub fn new() -> Self {
13 Self
14 }
15}
16
17impl PassphraseProvider for CliPassphraseProvider {
18 /// Securely obtains a passphrase by prompting the user on the terminal.
19 fn get_passphrase(&self, prompt_message: &str) -> Result<Zeroizing<String>, AgentError> {
20 // Print the contextual prompt provided by the caller to stderr
21 eprintln!("{}", prompt_message);
22
23 // Use rpassword to prompt securely
24 let password = rpassword::prompt_password("Enter passphrase: ")
25 .context("Failed to read passphrase from terminal") // Add context using anyhow
26 .map_err(|e| {
27 // Map the anyhow::Error (wrapping std::io::Error) to AgentError
28 // Consider adding a specific AgentError variant like UserInputCancelled or IOFailed
29 eprintln!("Error reading passphrase: {:?}", e); // Log the specific error
30 // For now, map generic IO errors. A more specific mapping could be done.
31 if let Some(io_err) = e.downcast_ref::<std::io::Error>() {
32 // Example: Check for specific kinds like Interrupted if needed
33 // if io_err.kind() == std::io::ErrorKind::Interrupted {
34 // return AgentError::UserInputCancelled;
35 // }
36 AgentError::IO(std::io::Error::new(io_err.kind(), format!("{}", e))) // Create a new IO error to own the message
37 } else {
38 AgentError::SecurityError(format!("Failed to get passphrase: {}", e)) // Fallback
39 }
40 })?;
41
42 Ok(Zeroizing::new(password))
43 }
44}
45
46/// A PassphraseProvider that returns a pre-collected passphrase.
47///
48/// Use this when the passphrase must be collected before starting a
49/// background task (e.g., a terminal spinner) that would interfere
50/// with stdin.
51pub struct PrefilledPassphraseProvider {
52 passphrase: Zeroizing<String>,
53}
54
55impl PrefilledPassphraseProvider {
56 pub fn new(passphrase: Zeroizing<String>) -> Self {
57 Self { passphrase }
58 }
59}
60
61impl PassphraseProvider for PrefilledPassphraseProvider {
62 fn get_passphrase(&self, _prompt_message: &str) -> Result<Zeroizing<String>, AgentError> {
63 Ok(self.passphrase.clone())
64 }
65}