use base64::Engine; use serde::Deserialize; #[derive(Clone, Deserialize)] #[serde(default)] pub struct PowConfig { pub difficulty: u32, pub max_difficulty: u32, pub expires_secs: u64, pub mac_key: Option, } impl std::fmt::Debug for PowConfig { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { formatter .debug_struct("PowConfig") .field("difficulty", &self.difficulty) .field("max_difficulty", &self.max_difficulty) .field("expires_secs", &self.expires_secs) .field("mac_key", &self.mac_key.as_ref().map(|_| "")) .finish() } } impl Default for PowConfig { fn default() -> Self { Self { difficulty: 16, max_difficulty: 24, expires_secs: 60, mac_key: None, } } } impl PowConfig { pub fn validate(&self) -> Result<(), String> { if self.difficulty == 0 { return Err("pow.difficulty must be greater than 0".to_string()); } if self.max_difficulty > 256 { return Err("pow.max_difficulty must be <= 256".to_string()); } if self.difficulty > self.max_difficulty { return Err("pow.difficulty must be <= pow.max_difficulty".to_string()); } if self.expires_secs == 0 { return Err("pow.expires_secs must be greater than 0".to_string()); } match &self.mac_key { Some(key) => { if key.trim().starts_with("REPLACE_WITH_") { return Err( "pow.mac_key is still set to the placeholder from config.yaml.example" .to_string(), ); } let length = base64::engine::general_purpose::STANDARD .decode(key) .map(|bytes| bytes.len()) .unwrap_or(0); if length < 32 { return Err("pow.mac_key must be base64 of at least 32 bytes".to_string()); } } None => return Err("pow.mac_key is required".to_string()), } Ok(()) } } impl crate::loader::Section for PowConfig { const KEY: &'static str = "pow"; }