envvault/cli/commands/
auth.rs1use crate::cli::output;
12use crate::cli::Cli;
13#[cfg(not(feature = "keyring-store"))]
14use crate::errors::EnvVaultError;
15use crate::errors::Result;
16
17pub fn execute_keyring(cli: &Cli, delete: bool) -> Result<()> {
19 #[cfg(feature = "keyring-store")]
20 {
21 let path = crate::cli::vault_path(cli)?;
22 let vault_id = path.to_string_lossy().to_string();
23
24 if delete {
25 crate::keyring::delete_password(&vault_id)?;
26 output::success("Password removed from OS keyring.");
27 } else {
28 let keyfile = crate::cli::load_keyfile(cli)?;
31 let password = crate::cli::prompt_password_for_vault(None)?;
32 let _store =
33 crate::vault::VaultStore::open(&path, password.as_bytes(), keyfile.as_deref())?;
34
35 crate::keyring::store_password(&vault_id, &password)?;
36 output::success("Password saved to OS keyring. Future opens will be automatic.");
37 }
38
39 Ok(())
40 }
41
42 #[cfg(not(feature = "keyring-store"))]
43 {
44 let _ = (cli, delete);
45 Err(EnvVaultError::KeyringError(
46 "keyring support not compiled — rebuild with `cargo build --features keyring-store`"
47 .into(),
48 ))
49 }
50}
51
52pub fn execute_keyfile_generate(cli: &Cli, keyfile_path: Option<&str>) -> Result<()> {
54 let cwd = std::env::current_dir()?;
55
56 let path = match keyfile_path {
57 Some(p) => std::path::PathBuf::from(p),
58 None => cwd.join(&cli.vault_dir).join("keyfile"),
59 };
60
61 crate::crypto::keyfile::generate_keyfile(&path)?;
62
63 let path_display = path.display();
64 output::success(&format!("Keyfile generated at {path_display}"));
65 output::warning("Keep this file secret! Anyone with it can help unlock your vault.");
66 output::tip("Add the keyfile path to .gitignore to prevent accidental commits.");
67
68 let relative = path.strip_prefix(&cwd).map_or_else(
70 |_| path.to_string_lossy().to_string(),
71 |p| p.to_string_lossy().to_string(),
72 );
73
74 crate::cli::gitignore::patch_gitignore(&cwd, &relative);
75
76 Ok(())
77}
78
79#[cfg(test)]
80mod tests {
81 use tempfile::TempDir;
82
83 #[test]
84 fn keyring_disabled_returns_error() {
85 #[cfg(not(feature = "keyring-store"))]
88 {
89 use clap::Parser;
90 let cli = crate::cli::Cli::parse_from(["envvault", "auth", "keyring"]);
91 let result = super::execute_keyring(&cli, false);
92 assert!(result.is_err());
93 let msg = result.unwrap_err().to_string();
94 assert!(
95 msg.contains("keyring support not compiled"),
96 "unexpected error: {msg}"
97 );
98 }
99 }
100
101 #[test]
102 fn keyfile_generate_creates_file() {
103 use clap::Parser;
104
105 let dir = TempDir::new().unwrap();
106 let kf_path = dir.path().join("my.keyfile");
107
108 let cli = crate::cli::Cli::parse_from([
109 "envvault",
110 "--vault-dir",
111 dir.path().to_str().unwrap(),
112 "auth",
113 "keyfile-generate",
114 kf_path.to_str().unwrap(),
115 ]);
116
117 super::execute_keyfile_generate(&cli, Some(kf_path.to_str().unwrap())).unwrap();
118
119 assert!(kf_path.exists(), "keyfile should be created");
120 let data = std::fs::read(&kf_path).unwrap();
121 assert_eq!(data.len(), 32, "keyfile should be 32 bytes");
122 }
123
124 #[test]
125 fn keyfile_generate_patches_gitignore() {
126 let dir = TempDir::new().unwrap();
129 let dir_path = dir.path().canonicalize().unwrap();
130 let kf_path = dir_path.join("vault.keyfile");
131
132 crate::crypto::keyfile::generate_keyfile(&kf_path).unwrap();
134
135 let relative = kf_path.strip_prefix(&dir_path).map_or_else(
137 |_| kf_path.to_string_lossy().to_string(),
138 |p| p.to_string_lossy().to_string(),
139 );
140 crate::cli::gitignore::patch_gitignore(&dir_path, &relative);
141
142 let gitignore = std::fs::read_to_string(dir_path.join(".gitignore")).unwrap_or_default();
143 assert!(
144 gitignore.contains("keyfile"),
145 "gitignore should contain keyfile entry: {gitignore}"
146 );
147 }
148}