From 80d3c61cc2b7098a3dd7750a4ab68590d7510dcf Mon Sep 17 00:00:00 2001 From: BlockListed <44610569+BlockListed@users.noreply.github.com> Date: Sat, 9 Sep 2023 08:50:45 +0200 Subject: [PATCH] add configuration support for multiple domains --- src/config.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 11 deletions(-) diff --git a/src/config.rs b/src/config.rs index 489a229d..e7cca8ac 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ -use std::env::consts::EXE_SUFFIX; +use std::{env::consts::EXE_SUFFIX, collections::HashMap}; use std::process::exit; use std::sync::RwLock; +use std::sync::OnceLock; use job_scheduler_ng::Schedule; use once_cell::sync::Lazy; @@ -47,6 +48,10 @@ macro_rules! make_config { _usr: ConfigBuilder, _overrides: Vec, + + domain_hostmap: OnceLock, + domain_origins: OnceLock, + domain_paths: OnceLock, } #[derive(Clone, Default, Deserialize, Serialize)] @@ -135,13 +140,20 @@ macro_rules! make_config { fn build(&self) -> ConfigItems { let mut config = ConfigItems::default(); - let _domain_set = self.domain.is_some(); + let _domain_set = self.domain_change_back.is_some(); $($( config.$name = make_config!{ @build self.$name.clone(), &config, $none_action, $($default)? }; )+)+ config.domain_set = _domain_set; - config.domain = config.domain.trim_end_matches('/').to_string(); + config.domain_change_back = config.domain_change_back.split(',').map(|d| d.trim_end_matches('/')).fold(String::new(), |acc, d| { + acc.push_str(d); + acc.push(','); + acc + }); + + // Remove trailing comma + config.domain_change_back.pop(); config.signups_domains_whitelist = config.signups_domains_whitelist.trim().to_lowercase(); config.org_creation_users = config.org_creation_users.trim().to_lowercase(); @@ -335,6 +347,8 @@ macro_rules! make_config { } +type HostHashMap = HashMap; + //STRUCTURE: // /// Short description (without this they won't appear on the list) // group { @@ -414,15 +428,15 @@ make_config! { /// General settings settings { - /// Domain URL |> This needs to be set to the URL used to access the server, including 'http[s]://' - /// and port, if it's different than the default. Some server functions don't work correctly without this value - domain: String, true, def, "http://localhost".to_string(); + /// Comma seperated list of Domain URLs |> This needs to be set to the URL used to access the server, including + /// 'http[s]://' and port, if it's different than the default. Some server functions don't work correctly without this value + // TODO: Change back, this is only done to break existing references + domain_change_back: String, true, def, "http://localhost".to_string(); /// Domain Set |> Indicates if the domain is set by the admin. Otherwise the default will be used. domain_set: bool, false, def, false; - /// Domain origin |> Domain URL origin (in https://example.com:8443/path, https://example.com:8443 is the origin) - domain_origin: String, false, auto, |c| extract_url_origin(&c.domain); /// Domain path |> Domain URL path (in https://example.com:8443/path, /path is the path) - domain_path: String, false, auto, |c| extract_url_path(&c.domain); + /// MUST be the same for all domains. + domain_path: String, false, auto, |c| extract_url_path(&c.domain_change_back.split(',').nth(0).expect("Missing domain")); /// Enable web vault web_vault_enabled: bool, false, def, true; @@ -667,7 +681,7 @@ make_config! { /// Embed images as email attachments. smtp_embed_images: bool, true, def, true; /// _smtp_img_src - _smtp_img_src: String, false, gen, |c| generate_smtp_img_src(c.smtp_embed_images, &c.domain); + _smtp_img_src: String, false, gen, |c| generate_smtp_img_src(c.smtp_embed_images, &c.domain_change_back); /// Enable SMTP debugging (Know the risks!) |> DANGEROUS: Enabling this will output very detailed SMTP messages. This could contain sensitive information like passwords and usernames! Only enable this during troubleshooting! smtp_debug: bool, false, def, false; /// Accept Invalid Certs (Know the risks!) |> DANGEROUS: Allow invalid certificates. This option introduces significant vulnerabilities to man-in-the-middle attacks! @@ -1010,10 +1024,33 @@ fn extract_url_path(url: &str) -> String { } } -fn generate_smtp_img_src(embed_images: bool, domain: &str) -> String { +/// Extracts host part from a URL. +pub fn extract_url_host(url: &str) -> String { + match Url::parse(url) { + Ok(u) => { + let Some(mut host) = u.host_str().map(|s| s.to_string()) else { + println!("Domain does not contain host!"); + return String::new(); + }; + + if let Some(port) = u.port().map(|p| p.to_string()) { + host.push_str(&port); + } + + host + } + Err(_) => { + // we already print it in the method above, no need to do it again + String::new() + } + } +} + +fn generate_smtp_img_src(embed_images: bool, domains: &str) -> String { if embed_images { "cid:".to_string() } else { + let domain = domains.split(',').nth(0).expect("Domain missing"); format!("{domain}/vw_static/") } } @@ -1082,6 +1119,9 @@ impl Config { _env, _usr, _overrides, + domain_origins: OnceLock::new(), + domain_paths: OnceLock::new(), + domain_hostmap: OnceLock::new(), }), }) } @@ -1249,6 +1289,36 @@ impl Config { } } } + + pub fn domain_origin(&self, host: &str) -> Option { + // This is done to prevent deadlock, when read-locking an rwlock twice + let domains = self.domain_change_back(); + + self.inner.read().unwrap().domain_origins.get_or_init(|| { + domains.split(',') + .map(|d| { + (extract_url_host(d), extract_url_origin(d)) + }) + .collect() + }).get(host).map(|h| h.clone()) + } + + pub fn host_to_domain(&self, host: &str) -> Option { + // This is done to prevent deadlock, when read-locking an rwlock twice + let domains = self.domain_change_back(); + + self.inner.read().unwrap().domain_hostmap.get_or_init(|| { + domains.split(',') + .map(|d| { + (extract_url_host(d), extract_url_path(d)) + }) + .collect() + }).get(host).map(|h| h.clone()) + } + + pub fn main_domain(&self) -> String { + self.domain_change_back().split(',').nth(0).expect("Missing domain").to_string() + } } use handlebars::{