From 1bc00ce07eadfaa366d6c7e7cb44c38e4b84c1f2 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 13 Jun 2024 15:53:22 +0100 Subject: [PATCH] feat!: [#591] extract new types for SMTP server configuration From: ```toml [mail] email_verification_enabled = false from = "example@email.com" password = "" port = 25 reply_to = "noreply@email.com" server = "" username = "" ``` To: ```toml [mail] email_verification_enabled = false from = "example@email.com" reply_to = "noreply@email.com" [mail.smtp] port = 25 server = "" [mail.smtp.credentials] password = "" username = "" ``` --- .../default/config/index.container.mysql.toml | 3 +- .../config/index.container.sqlite3.toml | 2 +- .../index.private.e2e.container.sqlite3.toml | 2 +- .../index.public.e2e.container.mysql.toml | 2 +- .../index.public.e2e.container.sqlite3.toml | 2 +- src/bootstrap/logging.rs | 2 +- src/config/mod.rs | 33 ++++++-- src/config/v1/mail.rs | 84 ++++++++++++++----- src/config/v1/mod.rs | 2 +- src/lib.rs | 10 ++- src/mailer.rs | 15 ++-- .../api/client/v1/contexts/settings/mod.rs | 45 ++++++++-- tests/common/contexts/settings/mod.rs | 46 +++++++--- tests/e2e/environment.rs | 5 +- tests/environments/isolated.rs | 3 +- 15 files changed, 188 insertions(+), 68 deletions(-) diff --git a/share/default/config/index.container.mysql.toml b/share/default/config/index.container.mysql.toml index b6d3585d..9a53efb6 100644 --- a/share/default/config/index.container.mysql.toml +++ b/share/default/config/index.container.mysql.toml @@ -4,6 +4,7 @@ log_level = "info" [database] connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index" -[mail] +[mail.smtp] port = 1025 server = "mailcatcher" + diff --git a/share/default/config/index.container.sqlite3.toml b/share/default/config/index.container.sqlite3.toml index 319ebd16..5abf23f4 100644 --- a/share/default/config/index.container.sqlite3.toml +++ b/share/default/config/index.container.sqlite3.toml @@ -4,6 +4,6 @@ log_level = "info" [database] connect_url = "sqlite:///var/lib/torrust/index/database/sqlite3.db?mode=rwc" -[mail] +[mail.smtp] port = 1025 server = "mailcatcher" diff --git a/share/default/config/index.private.e2e.container.sqlite3.toml b/share/default/config/index.private.e2e.container.sqlite3.toml index f06dece3..8747c579 100644 --- a/share/default/config/index.private.e2e.container.sqlite3.toml +++ b/share/default/config/index.private.e2e.container.sqlite3.toml @@ -9,6 +9,6 @@ url = "http://tracker:7070" [database] connect_url = "sqlite:///var/lib/torrust/index/database/e2e_testing_sqlite3.db?mode=rwc" -[mail] +[mail.smtp] port = 1025 server = "mailcatcher" diff --git a/share/default/config/index.public.e2e.container.mysql.toml b/share/default/config/index.public.e2e.container.mysql.toml index 88ccd69b..0382c12c 100644 --- a/share/default/config/index.public.e2e.container.mysql.toml +++ b/share/default/config/index.public.e2e.container.mysql.toml @@ -8,6 +8,6 @@ url = "udp://tracker:6969" [database] connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index_e2e_testing" -[mail] +[mail.smtp] port = 1025 server = "mailcatcher" diff --git a/share/default/config/index.public.e2e.container.sqlite3.toml b/share/default/config/index.public.e2e.container.sqlite3.toml index 1ef9fb14..e04d0ef6 100644 --- a/share/default/config/index.public.e2e.container.sqlite3.toml +++ b/share/default/config/index.public.e2e.container.sqlite3.toml @@ -8,6 +8,6 @@ url = "udp://tracker:6969" [database] connect_url = "sqlite:///var/lib/torrust/index/database/e2e_testing_sqlite3.db?mode=rwc" -[mail] +[mail.smtp] port = 1025 server = "mailcatcher" diff --git a/src/bootstrap/logging.rs b/src/bootstrap/logging.rs index e4697bd7..d15c676b 100644 --- a/src/bootstrap/logging.rs +++ b/src/bootstrap/logging.rs @@ -11,7 +11,7 @@ use std::sync::Once; use tracing::info; use tracing::level_filters::LevelFilter; -use crate::config::v1::logging::LogLevel; +use crate::config::LogLevel; static INIT: Once = Once::new(); diff --git a/src/config/mod.rs b/src/config/mod.rs index 1bc8b407..2e2b75ce 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -19,18 +19,33 @@ use url::Url; use crate::web::api::server::DynError; pub type Settings = v1::Settings; + pub type Api = v1::api::Api; + pub type Auth = v1::auth::Auth; +pub type EmailOnSignup = v1::auth::EmailOnSignup; +pub type SecretKey = v1::auth::SecretKey; +pub type PasswordConstraints = v1::auth::PasswordConstraints; + pub type Database = v1::database::Database; + pub type ImageCache = v1::image_cache::ImageCache; + pub type Mail = v1::mail::Mail; +pub type Smtp = v1::mail::Smtp; +pub type Credentials = v1::mail::Credentials; + pub type Network = v1::net::Network; + pub type TrackerStatisticsImporter = v1::tracker_statistics_importer::TrackerStatisticsImporter; + pub type Tracker = v1::tracker::Tracker; +pub type ApiToken = v1::tracker::ApiToken; + pub type Logging = v1::logging::Logging; +pub type LogLevel = v1::logging::LogLevel; + pub type Website = v1::website::Website; -pub type EmailOnSignup = v1::auth::EmailOnSignup; -pub type PasswordConstraints = v1::auth::PasswordConstraints; /// Prefix for env vars that overwrite configuration options. const CONFIG_OVERRIDE_PREFIX: &str = "TORRUST_INDEX_CONFIG_OVERRIDE_"; @@ -321,9 +336,7 @@ mod tests { use url::Url; - use crate::config::v1::auth::SecretKey; - use crate::config::v1::tracker::ApiToken; - use crate::config::{Configuration, ConfigurationPublic, Info, Settings}; + use crate::config::{ApiToken, Configuration, ConfigurationPublic, Info, SecretKey, Settings}; #[cfg(test)] fn default_config_toml() -> String { @@ -358,10 +371,14 @@ mod tests { email_verification_enabled = false from = "example@email.com" reply_to = "noreply@email.com" - username = "" - password = "" - server = "" + + [mail.smtp] port = 25 + server = "" + + [mail.smtp.credentials] + password = "" + username = "" [image_cache] max_request_timeout_ms = 1000 diff --git a/src/config/v1/mail.rs b/src/config/v1/mail.rs index 885a4383..7675ceac 100644 --- a/src/config/v1/mail.rs +++ b/src/config/v1/mail.rs @@ -13,18 +13,9 @@ pub struct Mail { /// The email address to reply to. #[serde(default = "Mail::default_reply_to")] pub reply_to: Mailbox, - /// The username to use for SMTP authentication. - #[serde(default = "Mail::default_username")] - pub username: String, - /// The password to use for SMTP authentication. - #[serde(default = "Mail::default_password")] - pub password: String, - /// The SMTP server to use. - #[serde(default = "Mail::default_server")] - pub server: String, - /// The SMTP port to use. - #[serde(default = "Mail::default_port")] - pub port: u16, + /// The SMTP server configuration. + #[serde(default = "Mail::default_smtp")] + pub smtp: Smtp, } impl Default for Mail { @@ -33,10 +24,7 @@ impl Default for Mail { email_verification_enabled: Self::default_email_verification_enabled(), from: Self::default_from(), reply_to: Self::default_reply_to(), - username: Self::default_username(), - password: Self::default_password(), - server: Self::default_server(), - port: Self::default_port(), + smtp: Self::default_smtp(), } } } @@ -54,14 +42,36 @@ impl Mail { "noreply@email.com".parse().expect("valid mailbox") } - fn default_username() -> String { - String::default() + fn default_smtp() -> Smtp { + Smtp::default() } +} - fn default_password() -> String { - String::default() +/// SMTP configuration. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Smtp { + /// The SMTP port to use. + #[serde(default = "Smtp::default_port")] + pub port: u16, + /// The SMTP server to use. + #[serde(default = "Smtp::default_server")] + pub server: String, + /// The SMTP server credentials. + #[serde(default = "Smtp::default_credentials")] + pub credentials: Credentials, +} + +impl Default for Smtp { + fn default() -> Self { + Self { + server: Self::default_server(), + port: Self::default_port(), + credentials: Self::default_credentials(), + } } +} +impl Smtp { fn default_server() -> String { String::default() } @@ -69,4 +79,38 @@ impl Mail { fn default_port() -> u16 { 25 } + + fn default_credentials() -> Credentials { + Credentials::default() + } +} + +/// SMTP configuration. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Credentials { + /// The password to use for SMTP authentication. + #[serde(default = "Credentials::default_password")] + pub password: String, + /// The username to use for SMTP authentication. + #[serde(default = "Credentials::default_username")] + pub username: String, +} + +impl Default for Credentials { + fn default() -> Self { + Self { + username: Self::default_username(), + password: Self::default_password(), + } + } +} + +impl Credentials { + fn default_username() -> String { + String::default() + } + + fn default_password() -> String { + String::default() + } } diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index ada1648c..1820c923 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -64,7 +64,7 @@ impl Settings { if let Some(_password) = self.database.connect_url.password() { let _ = self.database.connect_url.set_password(Some("***")); } - "***".clone_into(&mut self.mail.password); + "***".clone_into(&mut self.mail.smtp.credentials.password); self.auth.secret_key = SecretKey::new("***"); } } diff --git a/src/lib.rs b/src/lib.rs index 0d7cd435..c06d407f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -194,10 +194,14 @@ //! email_verification_enabled = false //! from = "example@email.com" //! reply_to = "noreply@email.com" -//! username = "" -//! password = "" -//! server = "" +//! +//! [mail.smtp] //! port = 25 +//! server = "" +//! +//! [mail.smtp.credentials] +//! password = "" +//! username = "" //! //! [image_cache] //! max_request_timeout_ms = 1000 diff --git a/src/mailer.rs b/src/mailer.rs index 36134b14..0eb5f35b 100644 --- a/src/mailer.rs +++ b/src/mailer.rs @@ -70,19 +70,22 @@ impl Service { async fn get_mailer(cfg: &Configuration) -> Mailer { let settings = cfg.settings.read().await; - if !settings.mail.username.is_empty() && !settings.mail.password.is_empty() { + if !settings.mail.smtp.credentials.username.is_empty() && !settings.mail.smtp.credentials.password.is_empty() { // SMTP authentication - let creds = Credentials::new(settings.mail.username.clone(), settings.mail.password.clone()); + let creds = Credentials::new( + settings.mail.smtp.credentials.username.clone(), + settings.mail.smtp.credentials.password.clone(), + ); - AsyncSmtpTransport::::builder_dangerous(&settings.mail.server) - .port(settings.mail.port) + AsyncSmtpTransport::::builder_dangerous(&settings.mail.smtp.server) + .port(settings.mail.smtp.port) .credentials(creds) .authentication(vec![Mechanism::Login, Mechanism::Xoauth2, Mechanism::Plain]) .build() } else { // SMTP without authentication - AsyncSmtpTransport::::builder_dangerous(&settings.mail.server) - .port(settings.mail.port) + AsyncSmtpTransport::::builder_dangerous(&settings.mail.smtp.server) + .port(settings.mail.smtp.port) .build() } } diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index 3a8ea60f..e9ba4974 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -5,9 +5,10 @@ use url::Url; use crate::config::v1::tracker::ApiToken; use crate::config::{ - Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Mail as DomainMail, - Network as DomainNetwork, PasswordConstraints as DomainPasswordConstraints, Settings as DomainSettings, - Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, + Api as DomainApi, Auth as DomainAuth, Credentials as DomainCredentials, Database as DomainDatabase, + ImageCache as DomainImageCache, Mail as DomainMail, Network as DomainNetwork, + PasswordConstraints as DomainPasswordConstraints, Settings as DomainSettings, Smtp as DomainSmtp, Tracker as DomainTracker, + TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, }; #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -66,10 +67,20 @@ pub struct Mail { pub email_verification_enabled: bool, pub from: String, pub reply_to: String, - pub username: String, - pub password: String, + pub smtp: Smtp, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Smtp { pub server: String, pub port: u16, + pub credentials: Credentials, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Credentials { + pub username: String, + pub password: String, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -169,10 +180,26 @@ impl From for Mail { email_verification_enabled: mail.email_verification_enabled, from: mail.from.to_string(), reply_to: mail.reply_to.to_string(), - username: mail.username, - password: mail.password, - server: mail.server, - port: mail.port, + smtp: Smtp::from(mail.smtp), + } + } +} + +impl From for Smtp { + fn from(smtp: DomainSmtp) -> Self { + Self { + server: smtp.server, + port: smtp.port, + credentials: Credentials::from(smtp.credentials), + } + } +} + +impl From for Credentials { + fn from(credentials: DomainCredentials) -> Self { + Self { + username: credentials.username, + password: credentials.password, } } } diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 74235bd2..29763e13 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -1,11 +1,11 @@ pub mod responses; use serde::{Deserialize, Serialize}; -use torrust_index::config::v1::tracker::ApiToken; use torrust_index::config::{ - Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Logging as DomainLogging, - Mail as DomainMail, Network as DomainNetwork, PasswordConstraints as DomainPasswordConstraints, Settings as DomainSettings, - Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, + Api as DomainApi, ApiToken, Auth as DomainAuth, Credentials as DomainCredentials, Database as DomainDatabase, + ImageCache as DomainImageCache, Logging as DomainLogging, Mail as DomainMail, Network as DomainNetwork, + PasswordConstraints as DomainPasswordConstraints, Settings as DomainSettings, Smtp as DomainSmtp, Tracker as DomainTracker, + TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, }; use url::Url; @@ -71,10 +71,20 @@ pub struct Mail { pub email_verification_enabled: bool, pub from: String, pub reply_to: String, - pub username: String, - pub password: String, + pub smtp: Smtp, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Smtp { pub server: String, pub port: u16, + pub credentials: Credentials, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Credentials { + pub username: String, + pub password: String, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -183,10 +193,26 @@ impl From for Mail { email_verification_enabled: mail.email_verification_enabled, from: mail.from.to_string(), reply_to: mail.reply_to.to_string(), - username: mail.username, - password: mail.password, - server: mail.server, - port: mail.port, + smtp: mail.smtp.into(), + } + } +} + +impl From for Smtp { + fn from(smtp: DomainSmtp) -> Self { + Self { + server: smtp.server, + port: smtp.port, + credentials: smtp.credentials.into(), + } + } +} + +impl From for Credentials { + fn from(credentials: DomainCredentials) -> Self { + Self { + username: credentials.username, + password: credentials.password, } } } diff --git a/tests/e2e/environment.rs b/tests/e2e/environment.rs index 224ca55a..65b7a116 100644 --- a/tests/e2e/environment.rs +++ b/tests/e2e/environment.rs @@ -1,8 +1,7 @@ use std::env; use std::str::FromStr; -use torrust_index::config::v1::tracker::ApiToken; -use torrust_index::config::TrackerMode; +use torrust_index::config::{ApiToken, TrackerMode}; use torrust_index::web::api::Version; use url::Url; @@ -116,7 +115,7 @@ impl TestEnv { settings.tracker.token = ApiToken::new("***"); - "***".clone_into(&mut settings.mail.password); + "***".clone_into(&mut settings.mail.smtp.credentials.password); "***".clone_into(&mut settings.auth.secret_key); diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index 91fb1345..34635f99 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -1,7 +1,6 @@ use tempfile::TempDir; use torrust_index::config; -use torrust_index::config::v1::logging::LogLevel; -use torrust_index::config::FREE_PORT; +use torrust_index::config::{LogLevel, FREE_PORT}; use torrust_index::web::api::Version; use url::Url;