use anyhow::{Context, Result};
use colored::Colorize;
use tsafe_core::{audit::AuditEntry, events::emit_event, keyring_store};
use crate::helpers::{audit, open_vault, prompt_password, prompt_password_confirmed};
pub(crate) fn cmd_rotate_key(profile: &str) -> Result<()> {
let mut vault = open_vault(profile)?;
let new_pw = if let Ok(pw) = std::env::var("TSAFE_NEW_MASTER_PASSWORD") {
if pw.is_empty() {
anyhow::bail!(
"TSAFE_NEW_MASTER_PASSWORD is set but empty — unset it or provide a non-empty password"
);
}
tracing::debug!("rotate-key: using TSAFE_NEW_MASTER_PASSWORD (non-interactive)");
pw
} else {
prompt_password_confirmed()?
};
vault
.rotate(new_pw.as_bytes())
.context("vault re-encryption failed")?;
tracing::info!(profile, "vault re-encrypted successfully");
match keyring_store::retrieve_password(profile) {
Ok(Some(_)) => {
match keyring_store::store_password(profile, &new_pw) {
Ok(()) => {
tracing::debug!(profile, "biometric credential updated to new password");
}
Err(e) => {
tracing::warn!(
profile,
error = %e,
"failed to update biometric credential after re-key"
);
eprintln!(
"{} Vault re-encrypted but the OS credential store update failed: {e}",
"warn:".yellow().bold()
);
eprintln!(
" The stored credential is now stale — biometric / quick-unlock will fail."
);
eprintln!(
" Run `tsafe --profile {profile} biometric re-enroll` to restore password-free unlock."
);
}
}
}
Ok(None) => {
tracing::debug!(profile, "no biometric credential stored; skipping update");
}
Err(e) => {
tracing::debug!(profile, error = %e, "keyring unavailable; skipping biometric update");
}
}
audit(profile)
.append(&AuditEntry::success(profile, "rotate-key", None))
.ok();
emit_event(profile, "rotate-key", None);
println!(
"{} Vault re-encrypted successfully. If biometric is enabled, the stored credential has been updated.",
"✓".green()
);
Ok(())
}
#[allow(dead_code)]
fn _read_current_password(profile: &str) -> Result<String> {
prompt_password(&format!("Current password for profile '{profile}': "))
}