use anyhow::Result;
use colored::Colorize;
use crate::cli::PolicyAction;
use tsafe_core::audit::AuditEntry;
use crate::helpers::*;
pub(crate) fn cmd_policy(profile: &str, action: PolicyAction) -> Result<()> {
use tsafe_core::vault::parse_rotation_days;
match action {
PolicyAction::Set { key, rotate_every } => {
if parse_rotation_days(&rotate_every).is_none() {
anyhow::bail!(
"invalid rotation interval '{rotate_every}' — use a format like 90d, 30d, 7d"
);
}
let mut vault = open_vault(profile)?;
let current_value = vault.get(&key)?;
let mut tags = vault
.file()
.secrets
.get(&key)
.map(|e| e.tags.clone())
.unwrap_or_default();
tags.insert("rotate_policy".into(), rotate_every.clone());
vault.set(&key, ¤t_value, tags)?;
audit(profile)
.append(&AuditEntry::success(profile, "policy-set", Some(&key)))
.ok();
println!(
"{} Rotation policy set on '{}': every {}",
"✓".green(),
key,
rotate_every
);
Ok(())
}
PolicyAction::Remove { key } => {
let mut vault = open_vault(profile)?;
let current_value = vault.get(&key)?;
let mut tags = vault
.file()
.secrets
.get(&key)
.map(|e| e.tags.clone())
.unwrap_or_default();
tags.remove("rotate_policy");
vault.set(&key, ¤t_value, tags)?;
audit(profile)
.append(&AuditEntry::success(profile, "policy-remove", Some(&key)))
.ok();
println!("{} Rotation policy removed from '{}'", "✓".green(), key);
Ok(())
}
}
}
pub(crate) fn cmd_rotate_due(profile: &str, json_out: bool, fail: bool) -> Result<()> {
use serde_json::json;
use tsafe_core::vault::rotation_due;
let vault = open_vault(profile)?;
let due = rotation_due(vault.file());
if json_out {
let items: Vec<_> = due
.iter()
.map(|(key, days_over, policy)| {
json!({
"key": key,
"days_overdue": days_over,
"policy": policy,
})
})
.collect();
let out = json!({
"profile": profile,
"overdue_count": due.len(),
"items": items,
});
println!("{}", serde_json::to_string_pretty(&out)?);
} else if due.is_empty() {
println!("{} No secrets are overdue for rotation", "✓".green());
println!(
" Tip: attach a rotation policy with {} (checked by {} and {}).",
"tsafe policy set KEY --rotate-every 90d".cyan(),
"tsafe rotate-due".dimmed(),
"tsafe doctor".dimmed()
);
} else {
println!(
"{} {} secret(s) overdue for rotation:\n",
"!".yellow(),
due.len()
);
println!(" {:<30} {:>12} {:>10}", "KEY", "OVERDUE BY", "POLICY");
println!(" {}", "─".repeat(54));
for (key, days_over, policy) in &due {
println!(" {:<30} {:>9} d {:>10}", key.yellow(), days_over, policy);
}
println!(
"\n Update values: {} Re-key vault: {}",
"tsafe set KEY <new-value>".cyan(),
"tsafe rotate".cyan()
);
}
if fail && !due.is_empty() {
anyhow::bail!("{} secret(s) overdue for rotation", due.len());
}
Ok(())
}