use rand::RngCore;
fn generate_hex_key() -> String {
let mut bytes = [0u8; 64];
rand::thread_rng().fill_bytes(&mut bytes);
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
pub fn run() {
let secret = generate_hex_key();
println!("JWT_SECRET={secret}");
println!();
println!("Add this to your .env file.");
}
pub fn rotate(key_name: &str, write: bool) -> anyhow::Result<()> {
use console::style;
let new_key = generate_hex_key();
let env_path = std::path::Path::new(".env");
let old_masked = if env_path.exists() {
let content = std::fs::read_to_string(env_path)?;
content
.lines()
.find(|l| l.starts_with(&format!("{key_name}=")))
.map(|line| {
let val = line.split_once('=').map(|x| x.1).unwrap_or("");
if val.len() > 8 {
format!("{}…{}", &val[..4], &val[val.len() - 4..])
} else {
"****".to_string()
}
})
.unwrap_or_else(|| "(not set)".to_string())
} else {
"(no .env file)".to_string()
};
println!("{} Rotating {key_name}", style("key:rotate").green().bold());
println!(" Old: {old_masked}");
println!(" New: {}…{}", &new_key[..8], &new_key[new_key.len() - 8..]);
println!();
println!(
" {} Existing JWTs and encrypted values signed with the old key will be invalidated.",
style("⚠").yellow()
);
if write {
let new_line = format!("{key_name}={new_key}");
if env_path.exists() {
let content = std::fs::read_to_string(env_path)?;
let updated = if content
.lines()
.any(|l| l.starts_with(&format!("{key_name}=")))
{
content
.lines()
.map(|l| {
if l.starts_with(&format!("{key_name}=")) {
new_line.clone()
} else {
l.to_string()
}
})
.collect::<Vec<_>>()
.join("\n")
+ "\n"
} else {
format!("{content}\n{new_line}\n")
};
std::fs::write(env_path, updated)?;
println!(" {} Updated .env with new {key_name}", style("✓").green());
} else {
std::fs::write(env_path, format!("{new_line}\n"))?;
println!(" {} Created .env with {key_name}", style("✓").green());
}
} else {
println!();
println!(" Run with --write to update .env in place:");
println!(" rok key:rotate --write");
println!();
println!(" Or manually update your .env:");
println!(" {key_name}={new_key}");
}
Ok(())
}