use crate::cli::output;
use crate::cli::Cli;
#[cfg(not(feature = "keyring-store"))]
use crate::errors::EnvVaultError;
use crate::errors::Result;
pub fn execute_keyring(cli: &Cli, delete: bool) -> Result<()> {
#[cfg(feature = "keyring-store")]
{
let path = crate::cli::vault_path(cli)?;
let vault_id = path.to_string_lossy().to_string();
if delete {
crate::keyring::delete_password(&vault_id)?;
output::success("Password removed from OS keyring.");
} else {
let keyfile = crate::cli::load_keyfile(cli)?;
let password = crate::cli::prompt_password_for_vault(None)?;
let _store =
crate::vault::VaultStore::open(&path, password.as_bytes(), keyfile.as_deref())?;
crate::keyring::store_password(&vault_id, &password)?;
output::success("Password saved to OS keyring. Future opens will be automatic.");
}
Ok(())
}
#[cfg(not(feature = "keyring-store"))]
{
let _ = (cli, delete);
Err(EnvVaultError::KeyringError(
"keyring support not compiled — rebuild with `cargo build --features keyring-store`"
.into(),
))
}
}
pub fn execute_keyfile_generate(cli: &Cli, keyfile_path: Option<&str>) -> Result<()> {
let cwd = std::env::current_dir()?;
let path = match keyfile_path {
Some(p) => std::path::PathBuf::from(p),
None => cwd.join(&cli.vault_dir).join("keyfile"),
};
crate::crypto::keyfile::generate_keyfile(&path)?;
let path_display = path.display();
output::success(&format!("Keyfile generated at {path_display}"));
output::warning("Keep this file secret! Anyone with it can help unlock your vault.");
output::tip("Add the keyfile path to .gitignore to prevent accidental commits.");
let relative = path.strip_prefix(&cwd).map_or_else(
|_| path.to_string_lossy().to_string(),
|p| p.to_string_lossy().to_string(),
);
crate::cli::gitignore::patch_gitignore(&cwd, &relative);
Ok(())
}
#[cfg(test)]
mod tests {
use tempfile::TempDir;
#[test]
fn keyring_disabled_returns_error() {
#[cfg(not(feature = "keyring-store"))]
{
use clap::Parser;
let cli = crate::cli::Cli::parse_from(["envvault", "auth", "keyring"]);
let result = super::execute_keyring(&cli, false);
assert!(result.is_err());
let msg = result.unwrap_err().to_string();
assert!(
msg.contains("keyring support not compiled"),
"unexpected error: {msg}"
);
}
}
#[test]
fn keyfile_generate_creates_file() {
use clap::Parser;
let dir = TempDir::new().unwrap();
let kf_path = dir.path().join("my.keyfile");
let cli = crate::cli::Cli::parse_from([
"envvault",
"--vault-dir",
dir.path().to_str().unwrap(),
"auth",
"keyfile-generate",
kf_path.to_str().unwrap(),
]);
super::execute_keyfile_generate(&cli, Some(kf_path.to_str().unwrap())).unwrap();
assert!(kf_path.exists(), "keyfile should be created");
let data = std::fs::read(&kf_path).unwrap();
assert_eq!(data.len(), 32, "keyfile should be 32 bytes");
}
#[test]
fn keyfile_generate_patches_gitignore() {
let dir = TempDir::new().unwrap();
let dir_path = dir.path().canonicalize().unwrap();
let kf_path = dir_path.join("vault.keyfile");
crate::crypto::keyfile::generate_keyfile(&kf_path).unwrap();
let relative = kf_path.strip_prefix(&dir_path).map_or_else(
|_| kf_path.to_string_lossy().to_string(),
|p| p.to_string_lossy().to_string(),
);
crate::cli::gitignore::patch_gitignore(&dir_path, &relative);
let gitignore = std::fs::read_to_string(dir_path.join(".gitignore")).unwrap_or_default();
assert!(
gitignore.contains("keyfile"),
"gitignore should contain keyfile entry: {gitignore}"
);
}
}