1pub mod context;
2
3use std::env;
4use std::fs;
5use std::io::IsTerminal;
6
7use crate::error::{AuthyError, Result};
8use crate::session;
9use crate::vault::{self, VaultKey};
10use context::AuthContext;
11
12const AUTHY_PASSPHRASE_ENV: &str = "AUTHY_PASSPHRASE";
13const AUTHY_KEYFILE_ENV: &str = "AUTHY_KEYFILE";
14const AUTHY_TOKEN_ENV: &str = "AUTHY_TOKEN";
15const AUTHY_NON_INTERACTIVE_ENV: &str = "AUTHY_NON_INTERACTIVE";
16
17pub fn is_non_interactive() -> bool {
20 if env::var(AUTHY_NON_INTERACTIVE_ENV)
21 .map(|v| v == "1")
22 .unwrap_or(false)
23 {
24 return true;
25 }
26 !std::io::stdin().is_terminal()
27}
28
29pub fn resolve_auth(require_write: bool) -> Result<(VaultKey, AuthContext)> {
35 if let Ok(token) = env::var(AUTHY_TOKEN_ENV) {
37 if require_write {
38 return Err(AuthyError::TokenReadOnly);
39 }
40
41 let keyfile_path = env::var(AUTHY_KEYFILE_ENV)
43 .map_err(|_| AuthyError::AuthFailed(
44 "AUTHY_TOKEN requires AUTHY_KEYFILE to be set".into(),
45 ))?;
46
47 let (identity, pubkey) = read_keyfile(&keyfile_path)?;
48 let vault_key = VaultKey::Keyfile {
49 identity: identity.clone(),
50 pubkey,
51 };
52
53 let vault = vault::load_vault(&vault_key)?;
55 let hmac_key = vault::crypto::derive_key(identity.as_bytes(), b"session-hmac", 32);
56 let session_record = session::validate_token(&token, &vault.sessions, &hmac_key)?;
57
58 let auth_ctx = AuthContext::from_token(
59 session_record.id.clone(),
60 session_record.scope.clone(),
61 session_record.run_only,
62 );
63
64 return Ok((vault_key, auth_ctx));
65 }
66
67 if let Ok(keyfile_path) = env::var(AUTHY_KEYFILE_ENV) {
69 let (identity, pubkey) = read_keyfile(&keyfile_path)?;
70 let vault_key = VaultKey::Keyfile { identity, pubkey };
71 let auth_ctx = AuthContext::master_keyfile();
72 return Ok((vault_key, auth_ctx));
73 }
74
75 if let Ok(passphrase) = env::var(AUTHY_PASSPHRASE_ENV) {
77 let vault_key = VaultKey::Passphrase(passphrase);
78 let auth_ctx = AuthContext::master_passphrase();
79 return Ok((vault_key, auth_ctx));
80 }
81
82 if is_non_interactive() {
84 return Err(AuthyError::AuthFailed(
85 "No credentials provided. Set AUTHY_KEYFILE, AUTHY_PASSPHRASE, or AUTHY_TOKEN environment variable.".into(),
86 ));
87 }
88
89 interactive_passphrase_prompt()
90}
91
92#[cfg(feature = "cli")]
93fn interactive_passphrase_prompt() -> Result<(VaultKey, AuthContext)> {
94 let passphrase = dialoguer::Password::new()
95 .with_prompt("Enter vault passphrase")
96 .interact()
97 .map_err(|e| AuthyError::AuthFailed(format!("Failed to read passphrase: {e}")))?;
98 Ok((VaultKey::Passphrase(passphrase), AuthContext::master_passphrase()))
99}
100
101#[cfg(not(feature = "cli"))]
102fn interactive_passphrase_prompt() -> Result<(VaultKey, AuthContext)> {
103 Err(AuthyError::AuthFailed(
104 "No credentials provided. Set AUTHY_KEYFILE or AUTHY_PASSPHRASE.".into(),
105 ))
106}
107
108pub fn resolve_auth_for_init(
110 passphrase: Option<String>,
111 generate_keyfile: Option<String>,
112) -> Result<VaultKey> {
113 if let Some(keyfile_path) = generate_keyfile {
114 let (secret_key, public_key) = vault::crypto::generate_keypair();
115 fs::write(&keyfile_path, &secret_key)?;
117 #[cfg(unix)]
119 {
120 use std::os::unix::fs::PermissionsExt;
121 fs::set_permissions(&keyfile_path, fs::Permissions::from_mode(0o600))?;
122 }
123 let pubkey_path = format!("{}.pub", keyfile_path);
125 fs::write(&pubkey_path, &public_key)?;
126
127 eprintln!("Generated keyfile: {}", keyfile_path);
128 eprintln!("Public key: {}", pubkey_path);
129
130 return Ok(VaultKey::Keyfile {
131 identity: secret_key,
132 pubkey: public_key,
133 });
134 }
135
136 if let Some(pass) = passphrase {
137 return Ok(VaultKey::Passphrase(pass));
138 }
139
140 if let Ok(pass) = env::var(AUTHY_PASSPHRASE_ENV) {
142 return Ok(VaultKey::Passphrase(pass));
143 }
144
145 interactive_init_passphrase()
146}
147
148#[cfg(feature = "cli")]
149fn interactive_init_passphrase() -> Result<VaultKey> {
150 let pass = dialoguer::Password::new()
151 .with_prompt("Create vault passphrase")
152 .with_confirmation("Confirm passphrase", "Passphrases don't match")
153 .interact()
154 .map_err(|e| AuthyError::AuthFailed(format!("Failed to read passphrase: {e}")))?;
155 Ok(VaultKey::Passphrase(pass))
156}
157
158#[cfg(not(feature = "cli"))]
159fn interactive_init_passphrase() -> Result<VaultKey> {
160 Err(AuthyError::AuthFailed(
161 "No credentials provided. Pass a passphrase or set AUTHY_PASSPHRASE.".into(),
162 ))
163}
164
165pub fn read_keyfile(path: &str) -> Result<(String, String)> {
167 let content = fs::read_to_string(path)
168 .map_err(|e| AuthyError::InvalidKeyfile(format!("Cannot read {}: {}", path, e)))?;
169
170 let identity: age::x25519::Identity = content
171 .trim()
172 .parse()
173 .map_err(|e: &str| AuthyError::InvalidKeyfile(e.to_string()))?;
174
175 let pubkey = identity.to_public().to_string();
176 Ok((content.trim().to_string(), pubkey))
177}