main
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<String>,
}
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(|_| "<redacted>"))
.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";
}