Skip to main content

jwt_hack/
config.rs

1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::path::{Path, PathBuf};
5
6/// Common secrets for dictionary attacks
7pub const COMMON_SECRETS: &[&str] = &[
8    "",
9    "secret",
10    "password",
11    "1234",
12    "123456",
13    "admin",
14    "test",
15    "key",
16    "jwt",
17    "token",
18    "your-256-bit-secret",
19    "your-secret",
20    "mysecret",
21    "default",
22    "changeme",
23    "qwerty",
24    "abc123",
25    "letmein",
26    "welcome",
27    "monkey",
28];
29
30/// Configuration structure for jwt-hack
31#[derive(Debug, Clone, Serialize, Deserialize, Default)]
32pub struct Config {
33    /// Default secret key for HMAC algorithms
34    pub default_secret: Option<String>,
35    /// Default algorithm to use
36    pub default_algorithm: Option<String>,
37    /// Default wordlist path for cracking
38    pub default_wordlist: Option<PathBuf>,
39    /// Default private key path
40    pub default_private_key: Option<PathBuf>,
41}
42
43impl Config {
44    /// Load configuration from a specific file path
45    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
46        let content = fs::read_to_string(path.as_ref())
47            .with_context(|| format!("Failed to read config file: {}", path.as_ref().display()))?;
48
49        toml::from_str(&content)
50            .with_context(|| format!("Failed to parse config file: {}", path.as_ref().display()))
51    }
52
53    /// Get the default config directory path using XDG specification
54    pub fn default_config_dir() -> Option<PathBuf> {
55        // Check XDG_CONFIG_HOME environment variable first
56        if let Ok(xdg_config_home) = std::env::var("XDG_CONFIG_HOME") {
57            let path = PathBuf::from(xdg_config_home).join("jwt-hack");
58            return Some(path);
59        }
60
61        // Fall back to platform-specific config directory
62        dirs::config_dir().map(|config_dir| config_dir.join("jwt-hack"))
63    }
64
65    /// Get the default config file path
66    pub fn default_config_file() -> Option<PathBuf> {
67        Self::default_config_dir().map(|dir| dir.join("config.toml"))
68    }
69
70    /// Load configuration with fallback logic
71    /// 1. Use provided config file path if given
72    /// 2. Try default config file location
73    /// 3. Return default config if no file exists
74    pub fn load(config_path: Option<&Path>) -> Result<Self> {
75        if let Some(path) = config_path {
76            // Use explicitly provided config file
77            return Self::from_file(path);
78        }
79
80        // Try default config file location
81        if let Some(default_path) = Self::default_config_file() {
82            if default_path.exists() {
83                return Self::from_file(default_path);
84            }
85        }
86
87        // Return default config if no file exists
88        Ok(Self::default())
89    }
90
91    /// Create default config directory if it doesn't exist
92    pub fn ensure_config_dir() -> Result<Option<PathBuf>> {
93        if let Some(config_dir) = Self::default_config_dir() {
94            if !config_dir.exists() {
95                fs::create_dir_all(&config_dir).with_context(|| {
96                    format!(
97                        "Failed to create config directory: {}",
98                        config_dir.display()
99                    )
100                })?;
101            }
102            Ok(Some(config_dir))
103        } else {
104            Ok(None)
105        }
106    }
107
108    /// Save configuration to a file
109    pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
110        let content = toml::to_string_pretty(self).context("Failed to serialize config to TOML")?;
111
112        // Ensure parent directory exists
113        if let Some(parent) = path.as_ref().parent() {
114            fs::create_dir_all(parent)
115                .with_context(|| format!("Failed to create directory: {}", parent.display()))?;
116        }
117
118        fs::write(path.as_ref(), content)
119            .with_context(|| format!("Failed to write config file: {}", path.as_ref().display()))?;
120
121        Ok(())
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use tempfile::TempDir;
129
130    #[test]
131    fn test_default_config() {
132        let config = Config::default();
133        assert!(config.default_secret.is_none());
134        assert!(config.default_algorithm.is_none());
135        assert!(config.default_wordlist.is_none());
136        assert!(config.default_private_key.is_none());
137    }
138
139    #[test]
140    fn test_config_serialization() {
141        let config = Config {
142            default_secret: Some("test_secret".to_string()),
143            default_algorithm: Some("HS256".to_string()),
144            default_wordlist: Some(PathBuf::from("/path/to/wordlist.txt")),
145            default_private_key: Some(PathBuf::from("/path/to/key.pem")),
146        };
147
148        let toml_str = toml::to_string(&config).unwrap();
149        let deserialized: Config = toml::from_str(&toml_str).unwrap();
150
151        assert_eq!(config.default_secret, deserialized.default_secret);
152        assert_eq!(config.default_algorithm, deserialized.default_algorithm);
153        assert_eq!(config.default_wordlist, deserialized.default_wordlist);
154        assert_eq!(config.default_private_key, deserialized.default_private_key);
155    }
156
157    #[test]
158    fn test_config_from_file() {
159        let temp_dir = TempDir::new().unwrap();
160        let config_file = temp_dir.path().join("test_config.toml");
161
162        let config_content = r#"
163default_secret = "my_secret"
164default_algorithm = "HS512"
165default_wordlist = "/path/to/wordlist.txt"
166default_private_key = "/path/to/private.pem"
167"#;
168
169        fs::write(&config_file, config_content).unwrap();
170
171        let config = Config::from_file(&config_file).unwrap();
172        assert_eq!(config.default_secret, Some("my_secret".to_string()));
173        assert_eq!(config.default_algorithm, Some("HS512".to_string()));
174        assert_eq!(
175            config.default_wordlist,
176            Some(PathBuf::from("/path/to/wordlist.txt"))
177        );
178        assert_eq!(
179            config.default_private_key,
180            Some(PathBuf::from("/path/to/private.pem"))
181        );
182    }
183
184    #[test]
185    fn test_config_load_with_fallback() {
186        // Test loading with non-existent file should return default config
187        let config = Config::load(None).unwrap();
188        assert!(config.default_secret.is_none());
189    }
190
191    #[test]
192    fn test_save_to_file() {
193        let temp_dir = TempDir::new().unwrap();
194        let config_file = temp_dir.path().join("save_test.toml");
195
196        let config = Config {
197            default_secret: Some("saved_secret".to_string()),
198            default_algorithm: Some("HS256".to_string()),
199            default_wordlist: None,
200            default_private_key: None,
201        };
202
203        config.save_to_file(&config_file).unwrap();
204
205        let loaded_config = Config::from_file(&config_file).unwrap();
206        assert_eq!(config.default_secret, loaded_config.default_secret);
207        assert_eq!(config.default_algorithm, loaded_config.default_algorithm);
208    }
209
210    #[test]
211    fn test_default_config_dir() {
212        // This test just ensures the function doesn't panic
213        // The actual path depends on the environment
214        let _config_dir = Config::default_config_dir();
215    }
216
217    #[test]
218    fn test_ensure_config_dir() {
219        // This test just ensures the function doesn't panic
220        let _result = Config::ensure_config_dir();
221    }
222}