use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::{Path, PathBuf};
pub const COMMON_SECRETS: &[&str] = &[
"",
"secret",
"password",
"1234",
"123456",
"admin",
"test",
"key",
"jwt",
"token",
"your-256-bit-secret",
"your-secret",
"mysecret",
"default",
"changeme",
"qwerty",
"abc123",
"letmein",
"welcome",
"monkey",
];
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Config {
pub default_secret: Option<String>,
pub default_algorithm: Option<String>,
pub default_wordlist: Option<PathBuf>,
pub default_private_key: Option<PathBuf>,
}
impl Config {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let content = fs::read_to_string(path.as_ref())
.with_context(|| format!("Failed to read config file: {}", path.as_ref().display()))?;
toml::from_str(&content)
.with_context(|| format!("Failed to parse config file: {}", path.as_ref().display()))
}
pub fn default_config_dir() -> Option<PathBuf> {
if let Ok(xdg_config_home) = std::env::var("XDG_CONFIG_HOME") {
let path = PathBuf::from(xdg_config_home).join("jwt-hack");
return Some(path);
}
dirs::config_dir().map(|config_dir| config_dir.join("jwt-hack"))
}
pub fn default_config_file() -> Option<PathBuf> {
Self::default_config_dir().map(|dir| dir.join("config.toml"))
}
pub fn load(config_path: Option<&Path>) -> Result<Self> {
if let Some(path) = config_path {
return Self::from_file(path);
}
if let Some(default_path) = Self::default_config_file() {
if default_path.exists() {
return Self::from_file(default_path);
}
}
Ok(Self::default())
}
pub fn ensure_config_dir() -> Result<Option<PathBuf>> {
if let Some(config_dir) = Self::default_config_dir() {
if !config_dir.exists() {
fs::create_dir_all(&config_dir).with_context(|| {
format!(
"Failed to create config directory: {}",
config_dir.display()
)
})?;
}
Ok(Some(config_dir))
} else {
Ok(None)
}
}
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let content = toml::to_string_pretty(self).context("Failed to serialize config to TOML")?;
if let Some(parent) = path.as_ref().parent() {
fs::create_dir_all(parent)
.with_context(|| format!("Failed to create directory: {}", parent.display()))?;
}
fs::write(path.as_ref(), content)
.with_context(|| format!("Failed to write config file: {}", path.as_ref().display()))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_default_config() {
let config = Config::default();
assert!(config.default_secret.is_none());
assert!(config.default_algorithm.is_none());
assert!(config.default_wordlist.is_none());
assert!(config.default_private_key.is_none());
}
#[test]
fn test_config_serialization() {
let config = Config {
default_secret: Some("test_secret".to_string()),
default_algorithm: Some("HS256".to_string()),
default_wordlist: Some(PathBuf::from("/path/to/wordlist.txt")),
default_private_key: Some(PathBuf::from("/path/to/key.pem")),
};
let toml_str = toml::to_string(&config).unwrap();
let deserialized: Config = toml::from_str(&toml_str).unwrap();
assert_eq!(config.default_secret, deserialized.default_secret);
assert_eq!(config.default_algorithm, deserialized.default_algorithm);
assert_eq!(config.default_wordlist, deserialized.default_wordlist);
assert_eq!(config.default_private_key, deserialized.default_private_key);
}
#[test]
fn test_config_from_file() {
let temp_dir = TempDir::new().unwrap();
let config_file = temp_dir.path().join("test_config.toml");
let config_content = r#"
default_secret = "my_secret"
default_algorithm = "HS512"
default_wordlist = "/path/to/wordlist.txt"
default_private_key = "/path/to/private.pem"
"#;
fs::write(&config_file, config_content).unwrap();
let config = Config::from_file(&config_file).unwrap();
assert_eq!(config.default_secret, Some("my_secret".to_string()));
assert_eq!(config.default_algorithm, Some("HS512".to_string()));
assert_eq!(
config.default_wordlist,
Some(PathBuf::from("/path/to/wordlist.txt"))
);
assert_eq!(
config.default_private_key,
Some(PathBuf::from("/path/to/private.pem"))
);
}
#[test]
fn test_config_load_with_fallback() {
let config = Config::load(None).unwrap();
assert!(config.default_secret.is_none());
}
#[test]
fn test_save_to_file() {
let temp_dir = TempDir::new().unwrap();
let config_file = temp_dir.path().join("save_test.toml");
let config = Config {
default_secret: Some("saved_secret".to_string()),
default_algorithm: Some("HS256".to_string()),
default_wordlist: None,
default_private_key: None,
};
config.save_to_file(&config_file).unwrap();
let loaded_config = Config::from_file(&config_file).unwrap();
assert_eq!(config.default_secret, loaded_config.default_secret);
assert_eq!(config.default_algorithm, loaded_config.default_algorithm);
}
#[test]
fn test_default_config_dir() {
let _config_dir = Config::default_config_dir();
}
#[test]
fn test_ensure_config_dir() {
let _result = Config::ensure_config_dir();
}
}