use anyhow::Result;
use console::{style, Emoji};
use std::io::{self, Write};
pub fn generate_secret(kind: &str, length: Option<usize>) -> Result<()> {
println!();
let secret = match kind {
"secret" | "Secret" | "SESSION_SECRET" => generate_random_secret(length.unwrap_or(32)),
"token" | "Token" | "ACCESS_TOKEN" => generate_token(length.unwrap_or(32)),
"jwt" | "JWT_SECRET" => generate_jwt_secret(),
"apikey" | "api-key" | "API_KEY" => generate_api_key(),
"password" | "PASSWORD" => generate_password(length.unwrap_or(16)),
"database" | "DB_PASSWORD" => generate_db_password(),
_ => {
println!(" {} Unknown secret type '{}'. Using 'secret'.", style("⚠").yellow(), kind);
println!(" {} Available types: secret, token, jwt, apikey, password, database", style("ℹ").dim());
println!();
generate_random_secret(length.unwrap_or(32))
}
};
println!("{} {}", Emoji("🔐", ""), style("Generated Secret").bold());
println!("{}", style("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━").dim());
println!();
println!(" {} {}", style("Type:").dim(), style(kind).cyan());
println!();
let width = secret.len().min(60).max(20);
let top_bottom = "┌".to_string() + &"─".repeat(width + 2) + "┐";
let middle = format!("│ {:width$} │", secret, width = width);
println!(" {}", top_bottom);
println!(" {}", middle);
println!(" {}", top_bottom.replace("┌", "└").replace("┐", "┘"));
println!();
println!(" {} {}", style("→").cyan(), style("Tip: Copy with mouse (click and drag to select)").dim());
println!();
print!(" {} Save to .env? (y/N): ", style("?").cyan());
io::stdout().flush()?;
let mut input = String::new();
if io::stdin().read_line(&mut input).is_ok() {
if input.trim().to_lowercase() == "y" {
save_to_env(&secret, kind)?;
}
}
Ok(())
}
fn generate_random_secret(length: usize) -> String {
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}|;:,.<>?";
let mut rng = Xorshift64::new();
(0..length)
.map(|_| {
let idx = rng.next_u64() as usize % CHARSET.len();
CHARSET[idx] as char
})
.collect()
}
fn generate_token(length: usize) -> String {
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let mut rng = Xorshift64::new();
(0..length)
.map(|_| {
let idx = rng.next_u64() as usize % CHARSET.len();
CHARSET[idx] as char
})
.collect()
}
fn generate_jwt_secret() -> String {
let mut rng = Xorshift64::new();
let bytes: Vec<u8> = (0..32).map(|_| rng.next_u64() as u8).collect();
base64_encode(&bytes)
}
fn generate_api_key() -> String {
let prefix = "keg_";
let mut rng = Xorshift64::new();
let key: String = (0..24)
.map(|_| {
let idx = rng.next_u64() as usize % 36;
if idx < 10 {
(b'0' + idx as u8) as char
} else {
(b'a' + (idx - 10) as u8) as char
}
})
.collect();
format!("{}{}", prefix, key)
}
fn generate_password(length: usize) -> String {
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*";
let mut rng = Xorshift64::new();
(0..length)
.map(|_| {
let idx = rng.next_u64() as usize % CHARSET.len();
CHARSET[idx] as char
})
.collect()
}
fn generate_db_password() -> String {
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@$%^&*()-_=+[]{}|;:,.<>?";
let mut rng = Xorshift64::new();
(0..20)
.map(|_| {
let idx = rng.next_u64() as usize % CHARSET.len();
CHARSET[idx] as char
})
.collect()
}
fn save_to_env(secret: &str, kind: &str) -> Result<()> {
let env_key = match kind {
"secret" | "SESSION_SECRET" => "SESSION_SECRET",
"token" | "ACCESS_TOKEN" => "ACCESS_TOKEN",
"jwt" | "JWT_SECRET" => "JWT_SECRET",
"apikey" | "api-key" | "API_KEY" => "API_KEY",
"password" | "PASSWORD" => "APP_PASSWORD",
"database" | "DB_PASSWORD" => "DB_PASSWORD",
_ => "SECRET_KEY",
};
let env_file = std::path::Path::new(".env");
let existing = if env_file.exists() {
std::fs::read_to_string(env_file)?
} else {
String::new()
};
if existing.lines().any(|line| line.starts_with(&format!("{}=", env_key))) {
println!(" {} {} already exists in .env, skipping", style("⚠").yellow(), env_key);
return Ok(());
}
let mut content = existing.trim().to_string();
if !content.is_empty() && !content.ends_with('\n') {
content.push('\n');
}
content.push_str(&format!("{}={}\n", env_key, secret));
std::fs::write(env_file, &content)?;
println!(" {} {} saved to .env", style("✓").green(), env_key);
Ok(())
}
struct Xorshift64 {
state: u64,
}
impl Xorshift64 {
fn new() -> Self {
let seed = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(123456789);
Self { state: seed }
}
fn next_u64(&mut self) -> u64 {
let mut x = self.state;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.state = x;
x
}
}
fn base64_encode(bytes: &[u8]) -> String {
const BASE64: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
bytes
.chunks(3)
.flat_map(|chunk| {
let b = match chunk.len() {
1 => [chunk[0], 0, 0],
2 => [chunk[0], chunk[1], 0],
_ => [chunk[0], chunk[1], chunk[2]],
};
[
BASE64[(b[0] >> 2) as usize],
BASE64[((b[0] & 0x03) << 4 | b[1] >> 4) as usize],
if chunk.len() > 1 { BASE64[((b[1] & 0x0f) << 2 | b[2] >> 6) as usize] } else { b'=' },
if chunk.len() > 2 { BASE64[(b[2] & 0x3f) as usize] } else { b'=' },
]
})
.map(|c| c as char)
.collect()
}