fix in smtp and split rs files

This commit is contained in:
Mateusz Gruszczyński committed 2026-07-27 22:56:56 +02:00
1 parent cc3b172e56
commit d6c1c52310
30 files changed
+4327 -4387

No files matched your search

+176
View File
@@ -0,0 +1,176 @@
mod smtp;
mod values;
use std::{net::IpAddr, path::Path};
use smtp::load_smtp;
use values::ConfigValues;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthorizationType {
Local,
Ldap,
Ad,
}
impl AuthorizationType {
fn from_values(values: &ConfigValues) -> Result<Self, Box<dyn std::error::Error>> {
match values.get("AUTHORIZATION_TYPE", "local").trim().to_ascii_lowercase().as_str() {
"local" => Ok(Self::Local),
"ldap" => Ok(Self::Ldap),
"ad" => Ok(Self::Ad),
_ => Err("AUTHORIZATION_TYPE must be one of: local, ldap, ad".into()),
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::Local => "local",
Self::Ldap => "ldap",
Self::Ad => "ad",
}
}
}
#[derive(Debug, Clone)]
pub struct Config {
pub host: IpAddr,
pub port: u16,
pub database_url: String,
pub database_max_connections: u32,
pub static_dir: String,
pub files_dir: String,
pub storage: crate::storage::StorageConfig,
pub upload_max_size_bytes: usize,
pub asset_version: String,
pub asset_cache_max_age_seconds: u64,
pub file_cache_max_age_seconds: u64,
pub smtp: Option<crate::state::SmtpConfig>,
pub registration_enabled: bool,
pub account_confirmation_required: bool,
pub share_confirmation_required: bool,
pub frontend_log_level: String,
pub anonymous_access_token_ttl_days: i64,
pub user_session_ttl_days: i64,
pub unconfirmed_account_ttl_days: i64,
pub authorization_type: AuthorizationType,
pub ldap: Option<crate::auth::ldap::LdapConfig>,
}
impl Config {
pub fn load(path: Option<&Path>) -> Result<Self, Box<dyn std::error::Error>> {
let values = ConfigValues::load(path)?;
let host = values.get("APP_HOST", "127.0.0.1").parse()?;
let port = values.get("APP_PORT", "3000").parse()?;
let database_max_connections = values.get("DATABASE_MAX_CONNECTIONS", "8").parse()?;
let upload_max_size_mb: usize = values.get("UPLOAD_MAX_SIZE_MB", "20").parse()?;
let anonymous_access_token_ttl_days = values.positive_i64("ANONYMOUS_ACCESS_TOKEN_TTL_DAYS", 7)?;
let user_session_ttl_days = values.positive_i64("USER_SESSION_TTL_DAYS", 3)?;
let unconfirmed_account_ttl_days = values.positive_i64("UNCONFIRMED_ACCOUNT_TTL_DAYS", 3)?;
let files_dir = values.get("FILES_DIR", "data/files");
let storage = match values.get("STORAGE_DRIVER", "local").trim().to_ascii_lowercase().as_str() {
"local" => crate::storage::StorageConfig::Local { root: files_dir.clone().into() },
"s3" => crate::storage::StorageConfig::S3 {
endpoint: values.optional("S3_ENDPOINT"),
region: values.get("S3_REGION", "us-east-1"),
bucket: values.required("S3_BUCKET", "STORAGE_DRIVER=s3")?,
access_key: values.required("S3_ACCESS_KEY", "STORAGE_DRIVER=s3")?,
secret_key: values.required("S3_SECRET_KEY", "STORAGE_DRIVER=s3")?,
force_path_style: values.bool("S3_FORCE_PATH_STYLE", false)?,
},
_ => return Err("STORAGE_DRIVER must be local or s3".into()),
};
if upload_max_size_mb == 0 {
return Err("UPLOAD_MAX_SIZE_MB must be greater than 0".into());
}
let authorization_type = AuthorizationType::from_values(&values)?;
let ldap = match authorization_type {
AuthorizationType::Local => None,
AuthorizationType::Ldap | AuthorizationType::Ad => {
let context = format!("AUTHORIZATION_TYPE={}", authorization_type.as_str());
let (default_filter, default_username_attribute) = match authorization_type {
AuthorizationType::Ldap => ("(uid={username})", "uid"),
AuthorizationType::Ad => ("(|(sAMAccountName={username})(userPrincipalName={username}))", "sAMAccountName"),
AuthorizationType::Local => unreachable!(),
};
Some(crate::auth::ldap::LdapConfig {
url: values.required("LDAP_URL", &context)?,
starttls: values.bool("LDAP_STARTTLS", false)?,
bind_dn: values.get("LDAP_BIND_DN", ""),
bind_password: values.get("LDAP_BIND_PASSWORD", ""),
base_dn: values.required("LDAP_BASE_DN", &context)?,
user_filter: values.get("LDAP_USER_FILTER", default_filter),
username_attribute: values.get("LDAP_USERNAME_ATTRIBUTE", default_username_attribute),
email_attribute: values.get("LDAP_EMAIL_ATTRIBUTE", "mail"),
display_name_attribute: values.get("LDAP_DISPLAY_NAME_ATTRIBUTE", "displayName"),
external_id_attribute: values.get("LDAP_EXTERNAL_ID_ATTRIBUTE", match authorization_type {
AuthorizationType::Ldap => "entryUUID",
AuthorizationType::Ad => "objectGUID",
AuthorizationType::Local => unreachable!(),
}),
organization: values.get("LDAP_ORGANIZATION", "organization"),
provider: authorization_type.as_str().to_owned(),
email_required: values.bool("LDAP_EMAIL_REQUIRED", true)?,
link_existing_by_email: values.bool("LDAP_LINK_EXISTING_BY_EMAIL", false)?,
tls_verify: values.bool("LDAP_TLS_VERIFY", true)?,
connect_timeout_seconds: values.positive_u64("LDAP_CONNECT_TIMEOUT_SECONDS", 5)?,
operation_timeout_seconds: values.positive_u64("LDAP_OPERATION_TIMEOUT_SECONDS", 10)?,
})
}
};
let smtp = load_smtp(&values)?;
let config = Self {
host,
port,
database_url: values.get("DATABASE_URL", "sqlite:///data/db/rustpad.db?mode=rwc"),
database_max_connections,
static_dir: values.get("STATIC_DIR", "static"),
files_dir,
storage,
upload_max_size_bytes: upload_max_size_mb.checked_mul(1024 * 1024).ok_or("UPLOAD_MAX_SIZE_MB is too large")?,
asset_version: env!("CARGO_PKG_VERSION").to_owned(),
asset_cache_max_age_seconds: values.nonnegative_u64("ASSET_CACHE_MAX_AGE_SECONDS", 600)?,
file_cache_max_age_seconds: values.nonnegative_u64("FILE_CACHE_MAX_AGE_SECONDS", 600)?,
smtp,
registration_enabled: values.bool("REGISTRATION_ENABLED", false)?,
account_confirmation_required: values.bool("ACCOUNT_CONFIRMATION_REQUIRED", false)?,
share_confirmation_required: values.bool("SHARE_CONFIRMATION_REQUIRED", false)?,
frontend_log_level: values.log_level("FRONTEND_LOG_LEVEL", "warn")?,
anonymous_access_token_ttl_days,
user_session_ttl_days,
unconfirmed_account_ttl_days,
authorization_type,
ldap,
};
config.validate()?;
Ok(config)
}
fn validate(&self) -> Result<(), Box<dyn std::error::Error>> {
if !(self.database_url.starts_with("sqlite:")
|| self.database_url.starts_with("postgres:")
|| self.database_url.starts_with("postgresql:")
|| self.database_url.starts_with("mysql:"))
{
return Err("DATABASE_URL must use sqlite, postgres/postgresql, or mysql".into());
}
if self.database_max_connections == 0 {
return Err("DATABASE_MAX_CONNECTIONS must be greater than 0".into());
}
if self.static_dir.trim().is_empty() || self.files_dir.trim().is_empty() {
return Err("STATIC_DIR and FILES_DIR cannot be empty".into());
}
if let Some(smtp) = &self.smtp {
if !(smtp.public_url.starts_with("http://") || smtp.public_url.starts_with("https://")) {
return Err("PUBLIC_URL must start with http:// or https://".into());
}
}
Ok(())
}
}