admin.rs
101 lignes · 3615 octets
//! Admin account configuration //! //! Used for the default administrator account created at first run when no admin //! exists. See `services::default_admin_service::ensure_default_admin` (called //! from `main`). Set `CREATE_DEFAULT_ADMIN=false` to disable after adding other admins. use std::env; use validator::ValidationError; /// Configuration for the default administrator account #[derive(Debug, Clone)] pub struct AdminConfig { /// Login name for the default admin (env: `ADMIN_USERNAME`, default: `"admin"`). pub username: String, /// Email address for the default admin (env: `ADMIN_EMAIL`, default: `"admin@localhost"`). pub email: String, /// Optional password override (env: `ADMIN_PASSWORD`); generated if absent. pub password: Option<String>, } impl AdminConfig { /// Load admin configuration from environment variables. pub fn from_env() -> Self { let username = env::var("ADMIN_USERNAME").unwrap_or_else(|_| "admin".to_string()); let email = env::var("ADMIN_EMAIL").unwrap_or_else(|_| "admin@localhost".to_string()); let password = env::var("ADMIN_PASSWORD").ok(); Self { username, email, password, } } /// Validate the admin configuration, returning an error if any field is invalid. pub fn validate(&self) -> Result<(), ValidationError> { // Delegate charset + length to Username::parse_allow_reserved so // ADMIN_USERNAME=admin (reserved by public parse) keeps working. crate::types::Username::parse_allow_reserved(&self.username) .map_err(|_| ValidationError::new("username_format"))?; if !self.email.contains('@') { return Err(ValidationError::new("email_format")); } let parts: Vec<&str> = self.email.split('@').collect(); if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() { return Err(ValidationError::new("email_format")); } if let Some(ref password) = self.password { if password == "REMPLACER_PAR_UN_MOT_DE_PASSE_FORT" || password == "password" { return Err(ValidationError::new("password_placeholder")); } if password.len() < 8 { return Err(ValidationError::new("password_length")); } } Ok(()) } } #[cfg(test)] mod tests { use super::AdminConfig; fn config_with_password(password: Option<String>) -> AdminConfig { AdminConfig { username: "admin".into(), email: "admin@localhost".into(), password, } } #[test] fn rejects_literal_password() { let err = config_with_password(Some("password".into())) .validate() .expect_err("literal password must be rejected"); assert_eq!(err.code.as_ref(), "password_placeholder"); } #[test] fn rejects_cli_placeholder_password() { let err = config_with_password(Some("REMPLACER_PAR_UN_MOT_DE_PASSE_FORT".into())) .validate() .expect_err("CLI placeholder password must be rejected"); assert_eq!(err.code.as_ref(), "password_placeholder"); } #[test] fn accepts_strong_password() { config_with_password(Some("Str0ngEnough".into())) .validate() .expect("non-placeholder password of sufficient length must pass"); } #[test] fn accepts_missing_password() { config_with_password(None) .validate() .expect("absent password is generated later"); } }
GitRust