use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiConfig {
pub host: String,
pub port: u16,
pub max_concurrent_scans: usize,
pub api_keys: HashMap<String, Permission>,
pub enable_cors: bool,
pub rate_limit_per_minute: u32,
pub max_body_size: usize,
pub request_timeout_seconds: u64,
pub ws_ping_interval_seconds: u64,
pub job_queue_capacity: usize,
pub enable_swagger: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum Permission {
Admin,
User,
ReadOnly,
}
impl Default for ApiConfig {
fn default() -> Self {
let mut api_keys = HashMap::new();
let random_key = generate_secure_api_key();
api_keys.insert(random_key.clone(), Permission::User);
tracing::warn!(
"=============================================================================\n\
SECURITY WARNING: Using auto-generated API key: {}\n\
\n\
This key is randomly generated and will change on restart.\n\
For production use:\n\
1. Create a config file with your own API keys\n\
2. Set strong, unique API keys for each client\n\
3. Never use the default configuration in production\n\
4. Restrict API access to specific IP addresses if possible\n\
=============================================================================",
random_key
);
Self {
host: "127.0.0.1".to_string(),
port: 8080,
max_concurrent_scans: 10,
api_keys,
enable_cors: false, rate_limit_per_minute: 100,
max_body_size: 1024 * 1024, request_timeout_seconds: 300, ws_ping_interval_seconds: 30,
job_queue_capacity: 1000,
enable_swagger: true,
}
}
}
fn generate_secure_api_key() -> String {
use rand::Rng;
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789-_";
const KEY_LENGTH: usize = 32;
let mut rng = rand::thread_rng();
let key: String = (0..KEY_LENGTH)
.map(|_| {
let idx = rng.gen_range(0..CHARSET.len());
CHARSET[idx] as char
})
.collect();
format!("auto-{}", key)
}
impl ApiConfig {
pub fn from_file(path: &str) -> anyhow::Result<Self> {
let content = std::fs::read_to_string(path)?;
let config: ApiConfig = toml::from_str(&content)?;
Ok(config)
}
pub fn create_example(path: &str) -> anyhow::Result<()> {
let config = Self::default();
let toml = toml::to_string_pretty(&config)?;
std::fs::write(path, toml)?;
Ok(())
}
pub fn validate_key(&self, key: &str) -> Option<Permission> {
self.api_keys.get(key).copied()
}
pub fn add_key(&mut self, key: String, permission: Permission) {
self.api_keys.insert(key, permission);
}
pub fn remove_key(&mut self, key: &str) -> Option<Permission> {
self.api_keys.remove(key)
}
}