use std::net::IpAddr; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(default)] pub struct AdminConfig { pub enabled: Option, pub bind_addr: Option, pub port: Option, pub seed_username: Option, pub seed_password: Option, pub session_cookie_name: Option, pub session_table_name: Option, pub cors_allow_origins: Vec, pub cors_allow_credentials: Option, } impl AdminConfig { pub fn enabled(&self) -> bool { self.enabled.unwrap_or(true) } pub fn bind_addr(&self) -> &str { self.bind_addr.as_deref().unwrap_or("0.0.0.0") } pub fn port(&self) -> u16 { self.port.unwrap_or(3004) } pub fn seed_username(&self) -> Option<&str> { self.seed_username .as_deref() .map(str::trim) .filter(|value| !value.is_empty()) } pub fn seed_password(&self) -> Option<&str> { self.seed_password .as_deref() .filter(|value| !value.is_empty()) } pub fn session_cookie_name(&self) -> &str { self.session_cookie_name .as_deref() .unwrap_or("gitdata_admin_session") } pub fn session_table_name(&self) -> &str { self.session_table_name .as_deref() .unwrap_or("admin_session") } pub fn cors_allow_credentials(&self) -> bool { self.cors_allow_credentials.unwrap_or(false) } pub fn is_valid(&self) -> Result<(), String> { if !self.enabled() { return Ok(()); } let bind = self.bind_addr(); if bind.parse::().is_err() { return Err(format!("admin.bind_addr is not a valid IP: {bind:?}")); } if self.port == Some(0) { return Err("admin.port must be greater than 0".to_string()); } if self.session_cookie_name().trim().is_empty() { return Err("admin.session_cookie_name must not be empty".to_string()); } let table = self.session_table_name(); if table.trim().is_empty() { return Err("admin.session_table_name must not be empty".to_string()); } if table.contains(['*', '?', '[', ']', '\\']) { return Err(format!( "admin.session_table_name must not contain glob characters: {table:?}" )); } match (self.seed_username(), self.seed_password()) { (Some(username), Some(password)) => { if username.len() < 3 { return Err("admin.seed_username must be at least 3 characters".to_string()); } if password.len() < 8 { return Err("admin.seed_password must be at least 8 characters".to_string()); } } (Some(_), None) => { return Err("admin.seed_username requires admin.seed_password".to_string()); } (None, Some(_)) => { return Err("admin.seed_password requires admin.seed_username".to_string()); } (None, None) => {} } for origin in &self.cors_allow_origins { if !origin.starts_with("http://") && !origin.starts_with("https://") { return Err(format!( "admin.cors_allow_origins entry must be an origin URL: {origin:?}" )); } } Ok(()) } }