From eb987e9c14a492a5af31c4917ec9b7bc02cd52b4 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 12 Jun 2024 16:50:00 +0100 Subject: [PATCH 1/9] feat!: [#591] config v2, tracker mode from tracker Normalize TrackerMode enum to use the same values as in the Tracker. ```rust pub enum TrackerMode { /// Will track every new info hash and serve every peer. #[serde(rename = "public")] Public, /// Will only track whitelisted info hashes. #[serde(rename = "listed")] Listed, /// Will only serve authenticated peers #[serde(rename = "private")] Private, /// Will only track whitelisted info hashes and serve authenticated peers #[serde(rename = "private_listed")] PrivateListed, } ``` That will enable to use the TrackerMode defined in https://crates.io/crates/torrust-tracker-primitives for TrackerMode in the future when a new version of that crate is released. --- .../index.private.e2e.container.sqlite3.toml | 2 +- src/config/mod.rs | 80 +++++++++---------- src/lib.rs | 4 +- .../api/server/v1/contexts/settings/mod.rs | 6 +- tests/common/contexts/settings/mod.rs | 2 +- 5 files changed, 44 insertions(+), 50 deletions(-) diff --git a/share/default/config/index.private.e2e.container.sqlite3.toml b/share/default/config/index.private.e2e.container.sqlite3.toml index fe781f5b..cc934a98 100644 --- a/share/default/config/index.private.e2e.container.sqlite3.toml +++ b/share/default/config/index.private.e2e.container.sqlite3.toml @@ -2,7 +2,7 @@ log_level = "info" [tracker] api_url = "http://tracker:1212" -mode = "Private" +mode = "private" url = "http://tracker:7070" [database] diff --git a/src/config/mod.rs b/src/config/mod.rs index 8ff9c08a..ccdff762 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,9 +2,9 @@ pub mod v1; pub mod validator; -use std::env; use std::str::FromStr; use std::sync::Arc; +use std::{env, fmt}; use camino::Utf8PathBuf; use figment::providers::{Env, Format, Serialized, Toml}; @@ -119,45 +119,29 @@ impl From for Error { } } -/* todo: +// todo: use https://crates.io/crates/torrust-tracker-primitives for TrackerMode. -Use https://crates.io/crates/torrust-tracker-primitives for TrackerMode. - -Enum variants: - - In Index In Tracker -- `Public` -> `Public` -- `Private` -> `Private` -- `Whitelisted` -> `Listed` -- `PrivateWhitelisted` -> `PrivateListed` - -Enum serialized values: - - In Index In Tracker -- `Public` -> `public` -- `Private` -> `private` -- `Whitelisted` -> `listed` -- `PrivateWhitelisted` -> `private_listed` - -It's a breaking change for the toml config file en the API. - -*/ - -/// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives) -/// crate for more information. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +/// The mode the tracker will run in. +/// +/// Refer to [Torrust Tracker Configuration](https://docs.rs/torrust-tracker-configuration) +/// to know how to configure the tracker to run in each mode. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] pub enum TrackerMode { /// Will track every new info hash and serve every peer. + #[serde(rename = "public")] Public, - /// Will only serve authenticated peers. - Private, - /// Will only track whitelisted info hashes. - Whitelisted, + #[serde(rename = "listed")] + Listed, + + /// Will only serve authenticated peers + #[serde(rename = "private")] + Private, - /// Will only track whitelisted info hashes and serve authenticated peers. - PrivateWhitelisted, + /// Will only track whitelisted info hashes and serve authenticated peers + #[serde(rename = "private_listed")] + PrivateListed, } impl Default for TrackerMode { @@ -166,18 +150,28 @@ impl Default for TrackerMode { } } +impl fmt::Display for TrackerMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let display_str = match self { + TrackerMode::Public => "public", + TrackerMode::Listed => "listed", + TrackerMode::Private => "private", + TrackerMode::PrivateListed => "private_listed", + }; + write!(f, "{display_str}") + } +} + impl FromStr for TrackerMode { type Err = String; fn from_str(s: &str) -> Result { - match s { - "Public" => Ok(TrackerMode::Public), - "Private" => Ok(TrackerMode::Private), - "Whitelisted" => Ok(TrackerMode::Whitelisted), - "PrivateWhitelisted" => Ok(TrackerMode::PrivateWhitelisted), - _ => Err(format!( - "{s} is not a valid tracker mode. Valid values: 'Public', 'Private', 'Whitelisted', 'PrivateWhitelisted' " - )), + match s.to_lowercase().as_str() { + "public" => Ok(TrackerMode::Public), + "listed" => Ok(TrackerMode::Listed), + "private" => Ok(TrackerMode::Private), + "private_listed" => Ok(TrackerMode::PrivateListed), + _ => Err(format!("Unknown tracker mode: {s}")), } } } @@ -185,7 +179,7 @@ impl FromStr for TrackerMode { impl TrackerMode { #[must_use] pub fn is_open(&self) -> bool { - matches!(self, TrackerMode::Public | TrackerMode::Whitelisted) + matches!(self, TrackerMode::Public | TrackerMode::Listed) } #[must_use] @@ -336,7 +330,7 @@ mod tests { [tracker] url = "udp://localhost:6969" - mode = "Public" + mode = "public" api_url = "http://localhost:1212/" token = "MyAccessToken" token_valid_seconds = 7257600 diff --git a/src/lib.rs b/src/lib.rs index c3148e86..0a26c192 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,7 +58,7 @@ //! ```toml //! [tracker] //! url = "udp://localhost:6969" -//! mode = "Public" +//! mode = "public" //! api_url = "http://localhost:1212/" //! token = "MyAccessToken" //! token_valid_seconds = 7257600 @@ -171,7 +171,7 @@ //! //! [tracker] //! url = "udp://localhost:6969" -//! mode = "Public" +//! mode = "public" //! api_url = "http://localhost:1212/" //! token = "MyAccessToken" //! token_valid_seconds = 7257600 diff --git a/src/web/api/server/v1/contexts/settings/mod.rs b/src/web/api/server/v1/contexts/settings/mod.rs index fe9aeab3..089137e6 100644 --- a/src/web/api/server/v1/contexts/settings/mod.rs +++ b/src/web/api/server/v1/contexts/settings/mod.rs @@ -35,7 +35,7 @@ //! }, //! "tracker": { //! "url": "udp://localhost:6969", -//! "mode": "Public", +//! "mode": "public", //! "api_url": "http://localhost:1212/", //! "token": "MyAccessToken", //! "token_valid_seconds": 7257600 @@ -102,7 +102,7 @@ //! --header "Content-Type: application/json" \ //! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ //! --request POST \ -//! --data '{"website":{"name":"Torrust"},"tracker":{"url":"udp://localhost:6969","mode":"Public","api_url":"http://localhost:1212/","token":"MyAccessToken","token_valid_seconds":7257600},"net":{"port":3001,"base_url":null},"auth":{"email_on_signup":"Optional","min_password_length":6,"max_password_length":64,"secret_key":"MaxVerstappenWC2021"},"database":{"connect_url":"sqlite://./storage/database/data.db?mode=rwc"},"mail":{"email_verification_enabled":false,"from":"example@email.com","reply_to":"noreply@email.com","username":"","password":"","server":"","port":25},"image_cache":{"max_request_timeout_ms":1000,"capacity":128000000,"entry_size_limit":4000000,"user_quota_period_seconds":3600,"user_quota_bytes":64000000},"api":{"default_torrent_page_size":10,"max_torrent_page_size":30},"tracker_statistics_importer":{"torrent_info_update_interval":3600}}' \ +//! --data '{"website":{"name":"Torrust"},"tracker":{"url":"udp://localhost:6969","mode":"public","api_url":"http://localhost:1212/","token":"MyAccessToken","token_valid_seconds":7257600},"net":{"port":3001,"base_url":null},"auth":{"email_on_signup":"Optional","min_password_length":6,"max_password_length":64,"secret_key":"MaxVerstappenWC2021"},"database":{"connect_url":"sqlite://./storage/database/data.db?mode=rwc"},"mail":{"email_verification_enabled":false,"from":"example@email.com","reply_to":"noreply@email.com","username":"","password":"","server":"","port":25},"image_cache":{"max_request_timeout_ms":1000,"capacity":128000000,"entry_size_limit":4000000,"user_quota_period_seconds":3600,"user_quota_bytes":64000000},"api":{"default_torrent_page_size":10,"max_torrent_page_size":30},"tracker_statistics_importer":{"torrent_info_update_interval":3600}}' \ //! "http://127.0.0.1:3001/v1/settings" //! ``` //! @@ -158,7 +158,7 @@ //! "data": { //! "website_name": "Torrust", //! "tracker_url": "udp://localhost:6969", -//! "tracker_mode": "Public", +//! "tracker_mode": "public", //! "email_on_signup": "Optional" //! } //! } diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index b72ca7d5..a3e1b080 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -113,7 +113,7 @@ impl From for Tracker { fn from(tracker: DomainTracker) -> Self { Self { url: tracker.url, - mode: format!("{:?}", tracker.mode), + mode: tracker.mode.to_string(), api_url: tracker.api_url, token: tracker.token, token_valid_seconds: tracker.token_valid_seconds, From bb75303e8a4d61541e45a52cf5e7a7116ee5919f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 12 Jun 2024 17:07:36 +0100 Subject: [PATCH 2/9] feat!: [#591] lowercase for auth::enail_on_signup emnum variants --- src/config/mod.rs | 2 +- src/config/v1/auth.rs | 26 +++++++++++++++++++ src/lib.rs | 2 +- .../api/server/v1/contexts/settings/mod.rs | 6 ++--- src/web/api/server/v1/contexts/user/mod.rs | 2 +- tests/common/contexts/settings/mod.rs | 2 +- 6 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index ccdff762..9c75a557 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -339,7 +339,7 @@ mod tests { port = 3001 [auth] - email_on_signup = "Optional" + email_on_signup = "optional" min_password_length = 6 max_password_length = 64 secret_key = "MaxVerstappenWC2021" diff --git a/src/config/v1/auth.rs b/src/config/v1/auth.rs index 6b7f0786..29bfaa59 100644 --- a/src/config/v1/auth.rs +++ b/src/config/v1/auth.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::str::FromStr; use serde::{Deserialize, Serialize}; @@ -50,6 +51,7 @@ impl Auth { /// Whether the email is required on signup or not. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] pub enum EmailOnSignup { /// The email is required on signup. Required, @@ -65,6 +67,30 @@ impl Default for EmailOnSignup { } } +impl fmt::Display for EmailOnSignup { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let display_str = match self { + EmailOnSignup::Required => "required", + EmailOnSignup::Optional => "optional", + EmailOnSignup::None => "none", + }; + write!(f, "{display_str}") + } +} + +impl FromStr for EmailOnSignup { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "required" => Ok(EmailOnSignup::Required), + "optional" => Ok(EmailOnSignup::Optional), + "none" => Ok(EmailOnSignup::None), + _ => Err(format!("Unknown config 'email_on_signup' option (required, optional, none): {s}")), + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct SecretKey(String); diff --git a/src/lib.rs b/src/lib.rs index 0a26c192..fa7fc351 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -180,7 +180,7 @@ //! port = 3001 //! //! [auth] -//! email_on_signup = "Optional" +//! email_on_signup = "optional" //! min_password_length = 6 //! max_password_length = 64 //! secret_key = "MaxVerstappenWC2021" diff --git a/src/web/api/server/v1/contexts/settings/mod.rs b/src/web/api/server/v1/contexts/settings/mod.rs index 089137e6..b30cd96b 100644 --- a/src/web/api/server/v1/contexts/settings/mod.rs +++ b/src/web/api/server/v1/contexts/settings/mod.rs @@ -45,7 +45,7 @@ //! "base_url": null //! }, //! "auth": { -//! "email_on_signup": "Optional", +//! "email_on_signup": "optional", //! "min_password_length": 6, //! "max_password_length": 64, //! "secret_key": "MaxVerstappenWC2021" @@ -102,7 +102,7 @@ //! --header "Content-Type: application/json" \ //! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ //! --request POST \ -//! --data '{"website":{"name":"Torrust"},"tracker":{"url":"udp://localhost:6969","mode":"public","api_url":"http://localhost:1212/","token":"MyAccessToken","token_valid_seconds":7257600},"net":{"port":3001,"base_url":null},"auth":{"email_on_signup":"Optional","min_password_length":6,"max_password_length":64,"secret_key":"MaxVerstappenWC2021"},"database":{"connect_url":"sqlite://./storage/database/data.db?mode=rwc"},"mail":{"email_verification_enabled":false,"from":"example@email.com","reply_to":"noreply@email.com","username":"","password":"","server":"","port":25},"image_cache":{"max_request_timeout_ms":1000,"capacity":128000000,"entry_size_limit":4000000,"user_quota_period_seconds":3600,"user_quota_bytes":64000000},"api":{"default_torrent_page_size":10,"max_torrent_page_size":30},"tracker_statistics_importer":{"torrent_info_update_interval":3600}}' \ +//! --data '{"website":{"name":"Torrust"},"tracker":{"url":"udp://localhost:6969","mode":"public","api_url":"http://localhost:1212/","token":"MyAccessToken","token_valid_seconds":7257600},"net":{"port":3001,"base_url":null},"auth":{"email_on_signup":"optional","min_password_length":6,"max_password_length":64,"secret_key":"MaxVerstappenWC2021"},"database":{"connect_url":"sqlite://./storage/database/data.db?mode=rwc"},"mail":{"email_verification_enabled":false,"from":"example@email.com","reply_to":"noreply@email.com","username":"","password":"","server":"","port":25},"image_cache":{"max_request_timeout_ms":1000,"capacity":128000000,"entry_size_limit":4000000,"user_quota_period_seconds":3600,"user_quota_bytes":64000000},"api":{"default_torrent_page_size":10,"max_torrent_page_size":30},"tracker_statistics_importer":{"torrent_info_update_interval":3600}}' \ //! "http://127.0.0.1:3001/v1/settings" //! ``` //! @@ -159,7 +159,7 @@ //! "website_name": "Torrust", //! "tracker_url": "udp://localhost:6969", //! "tracker_mode": "public", -//! "email_on_signup": "Optional" +//! "email_on_signup": "optional" //! } //! } //! ``` diff --git a/src/web/api/server/v1/contexts/user/mod.rs b/src/web/api/server/v1/contexts/user/mod.rs index a13c2bb8..2e3ce8d0 100644 --- a/src/web/api/server/v1/contexts/user/mod.rs +++ b/src/web/api/server/v1/contexts/user/mod.rs @@ -45,7 +45,7 @@ //! //! ```toml //! [auth] -//! email_on_signup = "Optional" +//! email_on_signup = "optional" //! min_password_length = 6 //! max_password_length = 64 //! ``` diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index a3e1b080..8ca66a66 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -133,7 +133,7 @@ impl From for Network { impl From for Auth { fn from(auth: DomainAuth) -> Self { Self { - email_on_signup: format!("{:?}", auth.email_on_signup), + email_on_signup: auth.email_on_signup.to_string(), min_password_length: auth.min_password_length, max_password_length: auth.max_password_length, secret_key: auth.secret_key.to_string(), From 82fc32cd9f3147d4f987a1e49b983296e857fea0 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 12 Jun 2024 17:09:32 +0100 Subject: [PATCH 3/9] feat!: [#591] rename enum variant EmailOnSignup::None to Ignored --- src/config/v1/auth.rs | 10 ++++++---- src/services/user.rs | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/config/v1/auth.rs b/src/config/v1/auth.rs index 29bfaa59..0a685757 100644 --- a/src/config/v1/auth.rs +++ b/src/config/v1/auth.rs @@ -58,7 +58,7 @@ pub enum EmailOnSignup { /// The email is optional on signup. Optional, /// The email is not allowed on signup. It will only be ignored if provided. - None, // code-review: rename to `Ignored`? + Ignored, } impl Default for EmailOnSignup { @@ -72,7 +72,7 @@ impl fmt::Display for EmailOnSignup { let display_str = match self { EmailOnSignup::Required => "required", EmailOnSignup::Optional => "optional", - EmailOnSignup::None => "none", + EmailOnSignup::Ignored => "ignored", }; write!(f, "{display_str}") } @@ -85,8 +85,10 @@ impl FromStr for EmailOnSignup { match s.to_lowercase().as_str() { "required" => Ok(EmailOnSignup::Required), "optional" => Ok(EmailOnSignup::Optional), - "none" => Ok(EmailOnSignup::None), - _ => Err(format!("Unknown config 'email_on_signup' option (required, optional, none): {s}")), + "none" => Ok(EmailOnSignup::Ignored), + _ => Err(format!( + "Unknown config 'email_on_signup' option (required, optional, ignored): {s}" + )), } } } diff --git a/src/services/user.rs b/src/services/user.rs index e808548e..4d521029 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -86,7 +86,7 @@ impl RegistrationService { } registration_form.email.clone() } - EmailOnSignup::None => None, + EmailOnSignup::Ignored => None, EmailOnSignup::Optional => registration_form.email.clone(), }; From 50ebb9a754948506637c3120931ddc86a8fa2770 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 12 Jun 2024 17:34:22 +0100 Subject: [PATCH 4/9] feat!: [#591] move log_level into a new logging section in config Old: ```toml log_level = "info" [website] name = "Torrust" ``` New: ```toml [logging] log_level = "info" [website] name = "Torrust" ``` And the value is not Optional anymore. It was a Optional but when it was None it defaulted to LogLevel::Info. In practice that means is mandatory but with the `Info` default value. --- .../default/config/index.container.mysql.toml | 1 + .../config/index.container.sqlite3.toml | 1 + .../config/index.development.sqlite3.toml | 1 + .../index.private.e2e.container.sqlite3.toml | 1 + .../index.public.e2e.container.mysql.toml | 1 + .../index.public.e2e.container.sqlite3.toml | 1 + src/app.rs | 2 +- src/bootstrap/logging.rs | 20 +---- src/config/mod.rs | 6 +- src/config/v1/logging.rs | 76 +++++++++++++++++++ src/config/v1/mod.rs | 24 +----- .../tracker_statistics_importer/app.rs | 2 +- tests/common/contexts/settings/mod.rs | 19 ++++- tests/environments/isolated.rs | 9 +-- 14 files changed, 117 insertions(+), 47 deletions(-) create mode 100644 src/config/v1/logging.rs diff --git a/share/default/config/index.container.mysql.toml b/share/default/config/index.container.mysql.toml index 5564fdaf..b6d3585d 100644 --- a/share/default/config/index.container.mysql.toml +++ b/share/default/config/index.container.mysql.toml @@ -1,3 +1,4 @@ +[logging] log_level = "info" [database] diff --git a/share/default/config/index.container.sqlite3.toml b/share/default/config/index.container.sqlite3.toml index 534578ed..319ebd16 100644 --- a/share/default/config/index.container.sqlite3.toml +++ b/share/default/config/index.container.sqlite3.toml @@ -1,3 +1,4 @@ +[logging] log_level = "info" [database] diff --git a/share/default/config/index.development.sqlite3.toml b/share/default/config/index.development.sqlite3.toml index 8e188d5e..c2b8b582 100644 --- a/share/default/config/index.development.sqlite3.toml +++ b/share/default/config/index.development.sqlite3.toml @@ -1,3 +1,4 @@ +[logging] log_level = "info" # Uncomment if you want to enable TSL for development diff --git a/share/default/config/index.private.e2e.container.sqlite3.toml b/share/default/config/index.private.e2e.container.sqlite3.toml index cc934a98..f06dece3 100644 --- a/share/default/config/index.private.e2e.container.sqlite3.toml +++ b/share/default/config/index.private.e2e.container.sqlite3.toml @@ -1,3 +1,4 @@ +[logging] log_level = "info" [tracker] diff --git a/share/default/config/index.public.e2e.container.mysql.toml b/share/default/config/index.public.e2e.container.mysql.toml index 16eb9f04..88ccd69b 100644 --- a/share/default/config/index.public.e2e.container.mysql.toml +++ b/share/default/config/index.public.e2e.container.mysql.toml @@ -1,3 +1,4 @@ +[logging] log_level = "info" [tracker] diff --git a/share/default/config/index.public.e2e.container.sqlite3.toml b/share/default/config/index.public.e2e.container.sqlite3.toml index c9e764bf..1ef9fb14 100644 --- a/share/default/config/index.public.e2e.container.sqlite3.toml +++ b/share/default/config/index.public.e2e.container.sqlite3.toml @@ -1,3 +1,4 @@ +[logging] log_level = "info" [tracker] diff --git a/src/app.rs b/src/app.rs index 93934f6e..44cc33fc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -38,7 +38,7 @@ pub struct Running { /// It panics if there is an error connecting to the database. #[allow(clippy::too_many_lines)] pub async fn run(configuration: Configuration, api_version: &Version) -> Running { - let log_level = configuration.settings.read().await.log_level.clone(); + let log_level = configuration.settings.read().await.logging.log_level.clone(); logging::setup(&log_level); diff --git a/src/bootstrap/logging.rs b/src/bootstrap/logging.rs index 4c02d6ee..e4697bd7 100644 --- a/src/bootstrap/logging.rs +++ b/src/bootstrap/logging.rs @@ -11,12 +11,12 @@ use std::sync::Once; use tracing::info; use tracing::level_filters::LevelFilter; -use crate::config::v1::LogLevel; +use crate::config::v1::logging::LogLevel; static INIT: Once = Once::new(); -pub fn setup(log_level: &Option) { - let tracing_level = config_level_or_default(log_level); +pub fn setup(log_level: &LogLevel) { + let tracing_level: LevelFilter = log_level.clone().into(); if tracing_level == LevelFilter::OFF { return; @@ -27,20 +27,6 @@ pub fn setup(log_level: &Option) { }); } -fn config_level_or_default(log_level: &Option) -> LevelFilter { - match log_level { - None => LevelFilter::INFO, - Some(level) => match level { - LogLevel::Off => LevelFilter::OFF, - LogLevel::Error => LevelFilter::ERROR, - LogLevel::Warn => LevelFilter::WARN, - LogLevel::Info => LevelFilter::INFO, - LogLevel::Debug => LevelFilter::DEBUG, - LogLevel::Trace => LevelFilter::TRACE, - }, - } -} - fn tracing_stdout_init(filter: LevelFilter, style: &TraceStyle) { let builder = tracing_subscriber::fmt().with_max_level(filter); diff --git a/src/config/mod.rs b/src/config/mod.rs index 9c75a557..275732da 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -27,6 +27,7 @@ pub type Mail = v1::mail::Mail; pub type Network = v1::net::Network; pub type TrackerStatisticsImporter = v1::tracker_statistics_importer::TrackerStatisticsImporter; pub type Tracker = v1::tracker::Tracker; +pub type Logging = v1::logging::Logging; pub type Website = v1::website::Website; pub type EmailOnSignup = v1::auth::EmailOnSignup; @@ -325,7 +326,10 @@ mod tests { #[cfg(test)] fn default_config_toml() -> String { - let config = r#"[website] + let config = r#"[logging] + log_level = "info" + + [website] name = "Torrust" [tracker] diff --git a/src/config/v1/logging.rs b/src/config/v1/logging.rs new file mode 100644 index 00000000..93387286 --- /dev/null +++ b/src/config/v1/logging.rs @@ -0,0 +1,76 @@ +use std::fmt; + +use serde::{Deserialize, Serialize}; +use tracing::level_filters::LevelFilter; + +/// Core configuration for the API +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Logging { + /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, `Debug`, `Trace`. + #[serde(default = "Logging::default_log_level")] + pub log_level: LogLevel, +} + +impl Default for Logging { + fn default() -> Self { + Self { + log_level: Logging::default_log_level(), + } + } +} + +impl Logging { + fn default_log_level() -> LogLevel { + LogLevel::Info + } +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone)] +#[serde(rename_all = "lowercase")] +pub enum LogLevel { + /// A level lower than all log levels. + Off, + /// Corresponds to the `Error` log level. + Error, + /// Corresponds to the `Warn` log level. + Warn, + /// Corresponds to the `Info` log level. + Info, + /// Corresponds to the `Debug` log level. + Debug, + /// Corresponds to the `Trace` log level. + Trace, +} + +impl Default for LogLevel { + fn default() -> Self { + Self::Info + } +} + +impl fmt::Display for LogLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let display_str = match self { + LogLevel::Off => "off", + LogLevel::Error => "error", + LogLevel::Warn => "warn", + LogLevel::Info => "info", + LogLevel::Debug => "debug", + LogLevel::Trace => "trace", + }; + write!(f, "{display_str}") + } +} + +impl From for LevelFilter { + fn from(log_level: LogLevel) -> Self { + match log_level { + LogLevel::Off => LevelFilter::OFF, + LogLevel::Error => LevelFilter::ERROR, + LogLevel::Warn => LevelFilter::WARN, + LogLevel::Info => LevelFilter::INFO, + LogLevel::Debug => LevelFilter::DEBUG, + LogLevel::Trace => LevelFilter::TRACE, + } + } +} diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index d788494b..ada1648c 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -2,12 +2,14 @@ pub mod api; pub mod auth; pub mod database; pub mod image_cache; +pub mod logging; pub mod mail; pub mod net; pub mod tracker; pub mod tracker_statistics_importer; pub mod website; +use logging::Logging; use serde::{Deserialize, Serialize}; use self::api::Api; @@ -24,10 +26,9 @@ use super::validator::{ValidationError, Validator}; /// The whole configuration for the index. #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] pub struct Settings { - /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, - /// `Debug` and `Trace`. Default is `Info`. + /// The logging configuration. #[serde(default)] - pub log_level: Option, + pub logging: Logging, /// The website customizable values. #[serde(default)] pub website: Website, @@ -73,20 +74,3 @@ impl Validator for Settings { self.tracker.validate() } } - -#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone)] -#[serde(rename_all = "lowercase")] -pub enum LogLevel { - /// A level lower than all log levels. - Off, - /// Corresponds to the `Error` log level. - Error, - /// Corresponds to the `Warn` log level. - Warn, - /// Corresponds to the `Info` log level. - Info, - /// Corresponds to the `Debug` log level. - Debug, - /// Corresponds to the `Trace` log level. - Trace, -} diff --git a/src/console/commands/tracker_statistics_importer/app.rs b/src/console/commands/tracker_statistics_importer/app.rs index edb43304..f9d9d4f9 100644 --- a/src/console/commands/tracker_statistics_importer/app.rs +++ b/src/console/commands/tracker_statistics_importer/app.rs @@ -90,7 +90,7 @@ pub async fn import() { let configuration = initialize_configuration(); - let log_level = configuration.settings.read().await.log_level.clone(); + let log_level = configuration.settings.read().await.logging.log_level.clone(); logging::setup(&log_level); diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 8ca66a66..6e138f8d 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -3,14 +3,15 @@ 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, Mail as DomainMail, - Network as DomainNetwork, Settings as DomainSettings, Tracker as DomainTracker, + Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Logging as DomainLogging, + Mail as DomainMail, Network as DomainNetwork, Settings as DomainSettings, Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, }; use url::Url; #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Settings { + pub logging: Logging, pub website: Website, pub tracker: Tracker, pub net: Network, @@ -22,6 +23,11 @@ pub struct Settings { pub tracker_statistics_importer: TrackerStatisticsImporter, } +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Logging { + pub log_level: String, +} + #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Website { pub name: String, @@ -90,6 +96,7 @@ pub struct TrackerStatisticsImporter { impl From for Settings { fn from(settings: DomainSettings) -> Self { Settings { + logging: Logging::from(settings.logging), website: Website::from(settings.website), tracker: Tracker::from(settings.tracker), net: Network::from(settings.net), @@ -103,6 +110,14 @@ impl From for Settings { } } +impl From for Logging { + fn from(logging: DomainLogging) -> Self { + Self { + log_level: logging.log_level.to_string(), + } + } +} + impl From for Website { fn from(website: DomainWebsite) -> Self { Self { name: website.name } diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index d74ee48e..91fb1345 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -1,6 +1,6 @@ use tempfile::TempDir; use torrust_index::config; -use torrust_index::config::v1::LogLevel; +use torrust_index::config::v1::logging::LogLevel; use torrust_index::config::FREE_PORT; use torrust_index::web::api::Version; use url::Url; @@ -72,10 +72,9 @@ impl Default for TestEnv { /// Provides a configuration with ephemeral data for testing. fn ephemeral(temp_dir: &TempDir) -> config::Settings { - let mut configuration = config::Settings { - log_level: Some(LogLevel::Off), // Change to `debug` for tests debugging - ..config::Settings::default() - }; + let mut configuration = config::Settings::default(); + + configuration.logging.log_level = LogLevel::Off; // Change to `debug` for tests debugging // Ephemeral API port configuration.net.port = FREE_PORT; From cd8248af42d2e26c801677e9fd9791c01ff157b3 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 12 Jun 2024 18:39:35 +0100 Subject: [PATCH 5/9] feat!: [#591] extract new type for password constrains in configuration From: ```toml [auth] email_on_signup = "Optional" max_password_length = 64 min_password_length = 6 secret_key = "MaxVerstappenWC2021" ``` To: ```toml [auth] email_on_signup = "Optional" secret_key = "MaxVerstappenWC2021" [auth.password_constraints] max_password_length = 64 min_password_length = 6 ``` --- src/config/mod.rs | 7 ++- src/config/v1/auth.rs | 55 ++++++++++++++----- src/lib.rs | 4 +- src/services/user.rs | 21 +++---- .../api/client/v1/contexts/settings/mod.rs | 23 ++++++-- src/web/api/server/v1/contexts/user/mod.rs | 5 +- tests/common/contexts/settings/mod.rs | 23 ++++++-- 7 files changed, 94 insertions(+), 44 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 275732da..1bc8b407 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -30,6 +30,7 @@ pub type Tracker = v1::tracker::Tracker; pub type Logging = v1::logging::Logging; 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_"; @@ -344,10 +345,12 @@ mod tests { [auth] email_on_signup = "optional" - min_password_length = 6 - max_password_length = 64 secret_key = "MaxVerstappenWC2021" + [auth.password_constraints] + min_password_length = 6 + max_password_length = 64 + [database] connect_url = "sqlite://data.db?mode=rwc" diff --git a/src/config/v1/auth.rs b/src/config/v1/auth.rs index 0a685757..007d3084 100644 --- a/src/config/v1/auth.rs +++ b/src/config/v1/auth.rs @@ -7,25 +7,21 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Auth { /// Whether or not to require an email on signup. - #[serde(default = "EmailOnSignup::default")] + #[serde(default = "Auth::default_email_on_signup")] pub email_on_signup: EmailOnSignup, - /// The minimum password length. - #[serde(default = "Auth::default_min_password_length")] - pub min_password_length: usize, - /// The maximum password length. - #[serde(default = "Auth::default_max_password_length")] - pub max_password_length: usize, /// The secret key used to sign JWT tokens. #[serde(default = "Auth::default_secret_key")] pub secret_key: SecretKey, + /// The password constraints + #[serde(default = "Auth::default_password_constraints")] + pub password_constraints: PasswordConstraints, } impl Default for Auth { fn default() -> Self { Self { email_on_signup: EmailOnSignup::default(), - min_password_length: Self::default_min_password_length(), - max_password_length: Self::default_max_password_length(), + password_constraints: Self::default_password_constraints(), secret_key: Self::default_secret_key(), } } @@ -36,17 +32,17 @@ impl Auth { self.secret_key = SecretKey::new(secret_key); } - fn default_min_password_length() -> usize { - 6 - } - - fn default_max_password_length() -> usize { - 64 + fn default_email_on_signup() -> EmailOnSignup { + EmailOnSignup::default() } fn default_secret_key() -> SecretKey { SecretKey::new("MaxVerstappenWC2021") } + + fn default_password_constraints() -> PasswordConstraints { + PasswordConstraints::default() + } } /// Whether the email is required on signup or not. @@ -119,6 +115,35 @@ impl fmt::Display for SecretKey { } } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct PasswordConstraints { + /// The minimum password length. + #[serde(default = "PasswordConstraints::default_min_password_length")] + pub min_password_length: usize, + /// The maximum password length. + #[serde(default = "PasswordConstraints::default_max_password_length")] + pub max_password_length: usize, +} + +impl Default for PasswordConstraints { + fn default() -> Self { + Self { + min_password_length: Self::default_min_password_length(), + max_password_length: Self::default_max_password_length(), + } + } +} + +impl PasswordConstraints { + fn default_min_password_length() -> usize { + 6 + } + + fn default_max_password_length() -> usize { + 64 + } +} + #[cfg(test)] mod tests { use super::SecretKey; diff --git a/src/lib.rs b/src/lib.rs index fa7fc351..0d7cd435 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -181,9 +181,11 @@ //! //! [auth] //! email_on_signup = "optional" +//! secret_key = "MaxVerstappenWC2021" +//! +//! [auth.password_constraints] //! min_password_length = 6 //! max_password_length = 64 -//! secret_key = "MaxVerstappenWC2021" //! //! [database] //! connect_url = "sqlite://data.db?mode=rwc" diff --git a/src/services/user.rs b/src/services/user.rs index 4d521029..6d0e4c4e 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -11,7 +11,7 @@ use pbkdf2::password_hash::rand_core::OsRng; use tracing::{debug, info}; use super::authentication::DbUserAuthenticationRepository; -use crate::config::{Configuration, EmailOnSignup}; +use crate::config::{Configuration, EmailOnSignup, PasswordConstraints}; use crate::databases::database::{Database, Error}; use crate::errors::ServiceError; use crate::mailer; @@ -97,11 +97,11 @@ impl RegistrationService { } let password_constraints = PasswordConstraints { - min_password_length: settings.auth.min_password_length, - max_password_length: settings.auth.max_password_length, + min_password_length: settings.auth.password_constraints.min_password_length, + max_password_length: settings.auth.password_constraints.max_password_length, }; - validate_password_constrains( + validate_password_constraints( ®istration_form.password, ®istration_form.confirm_password, &password_constraints, @@ -216,11 +216,11 @@ impl ProfileService { verify_password(change_password_form.current_password.as_bytes(), &user_authentication)?; let password_constraints = PasswordConstraints { - min_password_length: settings.auth.min_password_length, - max_password_length: settings.auth.max_password_length, + min_password_length: settings.auth.password_constraints.min_password_length, + max_password_length: settings.auth.password_constraints.max_password_length, }; - validate_password_constrains( + validate_password_constraints( &change_password_form.password, &change_password_form.confirm_password, &password_constraints, @@ -415,12 +415,7 @@ impl DbBannedUserList { } } -struct PasswordConstraints { - pub min_password_length: usize, - pub max_password_length: usize, -} - -fn validate_password_constrains( +fn validate_password_constraints( password: &str, confirm_password: &str, password_rules: &PasswordConstraints, diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index d762a98f..3a8ea60f 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -6,8 +6,8 @@ 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, Settings as DomainSettings, Tracker as DomainTracker, - TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, + Network as DomainNetwork, PasswordConstraints as DomainPasswordConstraints, Settings as DomainSettings, + Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, }; #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -46,9 +46,14 @@ pub struct Network { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Auth { pub email_on_signup: String, + pub secret_key: String, + pub password_constraints: PasswordConstraints, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct PasswordConstraints { pub min_password_length: usize, pub max_password_length: usize, - pub secret_key: String, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -135,9 +140,17 @@ impl From for Auth { fn from(auth: DomainAuth) -> Self { Self { email_on_signup: format!("{:?}", auth.email_on_signup), - min_password_length: auth.min_password_length, - max_password_length: auth.max_password_length, secret_key: auth.secret_key.to_string(), + password_constraints: auth.password_constraints.into(), + } + } +} + +impl From for PasswordConstraints { + fn from(password_constraints: DomainPasswordConstraints) -> Self { + Self { + min_password_length: password_constraints.min_password_length, + max_password_length: password_constraints.max_password_length, } } } diff --git a/src/web/api/server/v1/contexts/user/mod.rs b/src/web/api/server/v1/contexts/user/mod.rs index 2e3ce8d0..1d2eb2a3 100644 --- a/src/web/api/server/v1/contexts/user/mod.rs +++ b/src/web/api/server/v1/contexts/user/mod.rs @@ -45,9 +45,8 @@ //! //! ```toml //! [auth] -//! email_on_signup = "optional" -//! min_password_length = 6 -//! max_password_length = 64 +//! email_on_signup = "Optional" +//! secret_key = "MaxVerstappenWC2021" //! ``` //! //! Refer to the [`RegistrationForm`](crate::web::api::server::v1::contexts::user::forms::RegistrationForm) diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 6e138f8d..74235bd2 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -4,8 +4,8 @@ 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, Settings as DomainSettings, Tracker as DomainTracker, - TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, + Mail as DomainMail, Network as DomainNetwork, PasswordConstraints as DomainPasswordConstraints, Settings as DomainSettings, + Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, }; use url::Url; @@ -51,9 +51,14 @@ pub struct Network { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Auth { pub email_on_signup: String, + pub secret_key: String, + pub password_constraints: PasswordConstraints, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct PasswordConstraints { pub min_password_length: usize, pub max_password_length: usize, - pub secret_key: String, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -149,9 +154,17 @@ impl From for Auth { fn from(auth: DomainAuth) -> Self { Self { email_on_signup: auth.email_on_signup.to_string(), - min_password_length: auth.min_password_length, - max_password_length: auth.max_password_length, secret_key: auth.secret_key.to_string(), + password_constraints: auth.password_constraints.into(), + } + } +} + +impl From for PasswordConstraints { + fn from(password_constraints: DomainPasswordConstraints) -> Self { + Self { + min_password_length: password_constraints.min_password_length, + max_password_length: password_constraints.max_password_length, } } } From 1bc00ce07eadfaa366d6c7e7cb44c38e4b84c1f2 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 13 Jun 2024 15:53:22 +0100 Subject: [PATCH 6/9] 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; From 52f3d9c16dccf4df4df688a40e542a1e44ac2238 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 13 Jun 2024 16:11:30 +0100 Subject: [PATCH 7/9] feat!: [#591] inject full socket address in configuration not only the port. Old: ```toml [net] port = 3001 ``` New: ```toml [net] bind_address = "0.0.0.0:3001" ``` --- src/app.rs | 4 ++-- src/config/mod.rs | 2 +- src/config/v1/net.rs | 21 +++++++++++++++---- src/lib.rs | 2 +- .../api/client/v1/contexts/settings/mod.rs | 6 ++++-- tests/common/contexts/settings/mod.rs | 6 ++++-- tests/environments/isolated.rs | 4 +++- 7 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/app.rs b/src/app.rs index 44cc33fc..58d1bee1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -57,8 +57,8 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running let importer_torrent_info_update_interval = settings.tracker_statistics_importer.torrent_info_update_interval; let importer_port = settings.tracker_statistics_importer.port; // From [net] config - let net_ip = "0.0.0.0".to_string(); - let net_port = settings.net.port; + let net_ip = settings.net.bind_address.ip().to_string(); + let net_port = settings.net.bind_address.port(); let opt_net_tsl = settings.net.tsl.clone(); // IMPORTANT: drop settings before starting server to avoid read locks that diff --git a/src/config/mod.rs b/src/config/mod.rs index 2e2b75ce..84c7681f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -354,7 +354,7 @@ mod tests { token_valid_seconds = 7257600 [net] - port = 3001 + bind_address = "0.0.0.0:3001" [auth] email_on_signup = "optional" diff --git a/src/config/v1/net.rs b/src/config/v1/net.rs index 4c1552a2..3df9fc23 100644 --- a/src/config/v1/net.rs +++ b/src/config/v1/net.rs @@ -1,3 +1,5 @@ +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use serde::{Deserialize, Serialize}; use url::Url; @@ -11,13 +13,16 @@ use crate::config::Tsl; /// via port 443, which is a very common setup. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Network { - /// The port to listen on. Default to `3001`. - #[serde(default = "Network::default_port")] - pub port: u16, /// The base URL for the API. For example: `http://localhost`. /// If not set, the base URL will be inferred from the request. #[serde(default = "Network::default_base_url")] pub base_url: Option, + /// The address the tracker will bind to. + /// The format is `ip:port`, for example `0.0.0.0:6969`. If you want to + /// listen to all interfaces, use `0.0.0.0`. If you want the operating + /// system to choose a random port, use port `0`. + #[serde(default = "Network::default_bind_address")] + pub bind_address: SocketAddr, /// TSL configuration. #[serde(default = "Network::default_tsl")] pub tsl: Option, @@ -26,7 +31,7 @@ pub struct Network { impl Default for Network { fn default() -> Self { Self { - port: Self::default_port(), + bind_address: Self::default_bind_address(), base_url: Self::default_base_url(), tsl: Self::default_tsl(), } @@ -34,6 +39,14 @@ impl Default for Network { } impl Network { + fn default_bind_address() -> SocketAddr { + SocketAddr::new(Self::default_ip(), Self::default_port()) + } + + fn default_ip() -> IpAddr { + IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) + } + fn default_port() -> u16 { 3001 } diff --git a/src/lib.rs b/src/lib.rs index c06d407f..d42990d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -177,7 +177,7 @@ //! token_valid_seconds = 7257600 //! //! [net] -//! port = 3001 +//! bind_address = "0.0.0.0:3001" //! //! [auth] //! email_on_signup = "optional" diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index e9ba4974..4c1b371f 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -1,5 +1,7 @@ pub mod responses; +use std::net::SocketAddr; + use serde::{Deserialize, Serialize}; use url::Url; @@ -40,8 +42,8 @@ pub struct Tracker { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Network { - pub port: u16, pub base_url: Option, + pub bind_address: SocketAddr, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -141,8 +143,8 @@ impl From for Tracker { impl From for Network { fn from(net: DomainNetwork) -> Self { Self { - port: net.port, base_url: net.base_url.map(|url_without_port| url_without_port.to_string()), + bind_address: net.bind_address, } } } diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 29763e13..22f33aa3 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -1,5 +1,7 @@ pub mod responses; +use std::net::SocketAddr; + use serde::{Deserialize, Serialize}; use torrust_index::config::{ Api as DomainApi, ApiToken, Auth as DomainAuth, Credentials as DomainCredentials, Database as DomainDatabase, @@ -44,8 +46,8 @@ pub struct Tracker { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Network { - pub port: u16, pub base_url: Option, + pub bind_address: SocketAddr, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -154,7 +156,7 @@ impl From for Tracker { impl From for Network { fn from(net: DomainNetwork) -> Self { Self { - port: net.port, + bind_address: net.bind_address, base_url: net.base_url.as_ref().map(std::string::ToString::to_string), } } diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index 34635f99..c9fbeadb 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -1,3 +1,5 @@ +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use tempfile::TempDir; use torrust_index::config; use torrust_index::config::{LogLevel, FREE_PORT}; @@ -76,7 +78,7 @@ fn ephemeral(temp_dir: &TempDir) -> config::Settings { configuration.logging.log_level = LogLevel::Off; // Change to `debug` for tests debugging // Ephemeral API port - configuration.net.port = FREE_PORT; + configuration.net.bind_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), FREE_PORT); // Ephemeral Importer API port configuration.tracker_statistics_importer.port = FREE_PORT; From cb8935b8638223f6af8dfbcd5df03b051ed735d1 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 13 Jun 2024 16:22:29 +0100 Subject: [PATCH 8/9] feat!: [#591] use full socket addr in net config instead of only the port Old: ```toml [net] port = 3001 ``` New: ```toml [net] bind_address = "0.0.0.0:3001" ``` --- src/app.rs | 5 ++--- src/web/api/mod.rs | 5 ++--- src/web/api/server/mod.rs | 10 +++------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/app.rs b/src/app.rs index 58d1bee1..954f7cb4 100644 --- a/src/app.rs +++ b/src/app.rs @@ -57,8 +57,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running let importer_torrent_info_update_interval = settings.tracker_statistics_importer.torrent_info_update_interval; let importer_port = settings.tracker_statistics_importer.port; // From [net] config - let net_ip = settings.net.bind_address.ip().to_string(); - let net_port = settings.net.bind_address.port(); + let config_bind_address = settings.net.bind_address; let opt_net_tsl = settings.net.tsl.clone(); // IMPORTANT: drop settings before starting server to avoid read locks that @@ -181,7 +180,7 @@ pub async fn run(configuration: Configuration, api_version: &Version) -> Running ); // Start API server - let running_api = web::api::start(app_data, &net_ip, net_port, opt_net_tsl, api_version).await; + let running_api = web::api::start(app_data, config_bind_address, opt_net_tsl, api_version).await; // Full running application Running { diff --git a/src/web/api/mod.rs b/src/web/api/mod.rs index b1ac0601..6615a5d5 100644 --- a/src/web/api/mod.rs +++ b/src/web/api/mod.rs @@ -38,12 +38,11 @@ pub struct Running { #[must_use] pub async fn start( app_data: Arc, - net_ip: &str, - net_port: u16, + config_bind_address: SocketAddr, opt_tsl: Option, implementation: &Version, ) -> api::Running { match implementation { - Version::V1 => server::start(app_data, net_ip, net_port, opt_tsl).await, + Version::V1 => server::start(app_data, config_bind_address, opt_tsl).await, } } diff --git a/src/web/api/server/mod.rs b/src/web/api/server/mod.rs index 1349b54f..dee44ca1 100644 --- a/src/web/api/server/mod.rs +++ b/src/web/api/server/mod.rs @@ -28,11 +28,7 @@ pub type DynError = Arc; /// # Panics /// /// Panics if the API server can't be started. -pub async fn start(app_data: Arc, net_ip: &str, net_port: u16, opt_tsl: Option) -> Running { - let config_socket_addr: SocketAddr = format!("{net_ip}:{net_port}") - .parse() - .expect("API server socket address to be valid."); - +pub async fn start(app_data: Arc, config_bind_address: SocketAddr, opt_tsl: Option) -> Running { let opt_rust_tls_config = make_rust_tls(&opt_tsl) .await .map(|tls| tls.expect("it should have a valid net tls configuration")); @@ -42,9 +38,9 @@ pub async fn start(app_data: Arc, net_ip: &str, net_port: u16, opt_tsl: // Run the API server let join_handle = tokio::spawn(async move { - info!("Starting API server with net config: {} ...", config_socket_addr); + info!("Starting API server with net config: {} ...", config_bind_address); - start_server(config_socket_addr, app_data.clone(), tx_start, rx_halt, opt_rust_tls_config).await; + start_server(config_bind_address, app_data.clone(), tx_start, rx_halt, opt_rust_tls_config).await; info!("API server stopped"); From 35a125ef09fccaac68d7d5a9d3d3abf7174c7f9d Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 13 Jun 2024 16:32:18 +0100 Subject: [PATCH 9/9] refactor: [#591] order keys in config TOML files alphabetically We should produce always the same TOML file from the same configuration deterministically. --- src/config/mod.rs | 14 +++++++------- src/config/v1/api.rs | 1 + src/config/v1/auth.rs | 10 ++++++---- src/config/v1/image_cache.rs | 18 +++++++++++------- src/config/v1/mail.rs | 3 +++ src/config/v1/mod.rs | 9 +++++++++ src/config/v1/net.rs | 2 ++ src/config/v1/tracker.rs | 16 ++++++++++------ src/config/v1/tracker_statistics_importer.rs | 7 ++++--- 9 files changed, 53 insertions(+), 27 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 84c7681f..d0fbe7b2 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -347,11 +347,11 @@ mod tests { name = "Torrust" [tracker] - url = "udp://localhost:6969" - mode = "public" api_url = "http://localhost:1212/" + mode = "public" token = "MyAccessToken" token_valid_seconds = 7257600 + url = "udp://localhost:6969" [net] bind_address = "0.0.0.0:3001" @@ -361,9 +361,9 @@ mod tests { secret_key = "MaxVerstappenWC2021" [auth.password_constraints] - min_password_length = 6 max_password_length = 64 - + min_password_length = 6 + [database] connect_url = "sqlite://data.db?mode=rwc" @@ -381,19 +381,19 @@ mod tests { username = "" [image_cache] - max_request_timeout_ms = 1000 capacity = 128000000 entry_size_limit = 4000000 - user_quota_period_seconds = 3600 + max_request_timeout_ms = 1000 user_quota_bytes = 64000000 + user_quota_period_seconds = 3600 [api] default_torrent_page_size = 10 max_torrent_page_size = 30 [tracker_statistics_importer] - torrent_info_update_interval = 3600 port = 3002 + torrent_info_update_interval = 3600 "# .lines() .map(str::trim_start) diff --git a/src/config/v1/api.rs b/src/config/v1/api.rs index 4b4db9fd..678c52d7 100644 --- a/src/config/v1/api.rs +++ b/src/config/v1/api.rs @@ -6,6 +6,7 @@ pub struct Api { /// The default page size for torrent lists. #[serde(default = "Api::default_default_torrent_page_size")] pub default_torrent_page_size: u8, + /// The maximum page size for torrent lists. #[serde(default = "Api::default_max_torrent_page_size")] pub max_torrent_page_size: u8, diff --git a/src/config/v1/auth.rs b/src/config/v1/auth.rs index 007d3084..71147e4c 100644 --- a/src/config/v1/auth.rs +++ b/src/config/v1/auth.rs @@ -9,9 +9,11 @@ pub struct Auth { /// Whether or not to require an email on signup. #[serde(default = "Auth::default_email_on_signup")] pub email_on_signup: EmailOnSignup, + /// The secret key used to sign JWT tokens. #[serde(default = "Auth::default_secret_key")] pub secret_key: SecretKey, + /// The password constraints #[serde(default = "Auth::default_password_constraints")] pub password_constraints: PasswordConstraints, @@ -117,19 +119,19 @@ impl fmt::Display for SecretKey { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct PasswordConstraints { - /// The minimum password length. - #[serde(default = "PasswordConstraints::default_min_password_length")] - pub min_password_length: usize, /// The maximum password length. #[serde(default = "PasswordConstraints::default_max_password_length")] pub max_password_length: usize, + /// The minimum password length. + #[serde(default = "PasswordConstraints::default_min_password_length")] + pub min_password_length: usize, } impl Default for PasswordConstraints { fn default() -> Self { Self { - min_password_length: Self::default_min_password_length(), max_password_length: Self::default_max_password_length(), + min_password_length: Self::default_min_password_length(), } } } diff --git a/src/config/v1/image_cache.rs b/src/config/v1/image_cache.rs index 88c5ebec..6f9accf1 100644 --- a/src/config/v1/image_cache.rs +++ b/src/config/v1/image_cache.rs @@ -10,23 +10,27 @@ use serde::{Deserialize, Serialize}; #[allow(clippy::module_name_repetitions)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ImageCache { - /// Maximum time in seconds to wait for downloading the image form the original source. - #[serde(default = "ImageCache::default_max_request_timeout_ms")] - pub max_request_timeout_ms: u64, /// Cache size in bytes. #[serde(default = "ImageCache::default_capacity")] pub capacity: usize, + /// Maximum size in bytes for a single image. #[serde(default = "ImageCache::default_entry_size_limit")] pub entry_size_limit: usize, - /// Users have a cache quota per period. For example: 100MB per day. - /// This is the period in seconds (1 day in seconds). - #[serde(default = "ImageCache::default_user_quota_period_seconds")] - pub user_quota_period_seconds: u64, + + /// Maximum time in seconds to wait for downloading the image form the original source. + #[serde(default = "ImageCache::default_max_request_timeout_ms")] + pub max_request_timeout_ms: u64, + /// Users have a cache quota per period. For example: 100MB per day. /// This is the maximum size in bytes (100MB in bytes). #[serde(default = "ImageCache::default_user_quota_bytes")] pub user_quota_bytes: usize, + + /// Users have a cache quota per period. For example: 100MB per day. + /// This is the period in seconds (1 day in seconds). + #[serde(default = "ImageCache::default_user_quota_period_seconds")] + pub user_quota_period_seconds: u64, } impl Default for ImageCache { diff --git a/src/config/v1/mail.rs b/src/config/v1/mail.rs index 7675ceac..e171d41b 100644 --- a/src/config/v1/mail.rs +++ b/src/config/v1/mail.rs @@ -7,12 +7,15 @@ pub struct Mail { /// Whether or not to enable email verification on signup. #[serde(default = "Mail::default_email_verification_enabled")] pub email_verification_enabled: bool, + /// The email address to send emails from. #[serde(default = "Mail::default_from")] pub from: Mailbox, + /// The email address to reply to. #[serde(default = "Mail::default_reply_to")] pub reply_to: Mailbox, + /// The SMTP server configuration. #[serde(default = "Mail::default_smtp")] pub smtp: Smtp, diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs index 1820c923..9e73d93e 100644 --- a/src/config/v1/mod.rs +++ b/src/config/v1/mod.rs @@ -29,30 +29,39 @@ pub struct Settings { /// The logging configuration. #[serde(default)] pub logging: Logging, + /// The website customizable values. #[serde(default)] pub website: Website, + /// The tracker configuration. #[serde(default)] pub tracker: Tracker, + /// The network configuration. #[serde(default)] pub net: Network, + /// The authentication configuration. #[serde(default)] pub auth: Auth, + /// The database configuration. #[serde(default)] pub database: Database, + /// The SMTP configuration. #[serde(default)] pub mail: Mail, + /// The image proxy cache configuration. #[serde(default)] pub image_cache: ImageCache, + /// The API configuration. #[serde(default)] pub api: Api, + /// The tracker statistics importer job configuration. #[serde(default)] pub tracker_statistics_importer: TrackerStatisticsImporter, diff --git a/src/config/v1/net.rs b/src/config/v1/net.rs index 3df9fc23..cb41c046 100644 --- a/src/config/v1/net.rs +++ b/src/config/v1/net.rs @@ -17,12 +17,14 @@ pub struct Network { /// If not set, the base URL will be inferred from the request. #[serde(default = "Network::default_base_url")] pub base_url: Option, + /// The address the tracker will bind to. /// The format is `ip:port`, for example `0.0.0.0:6969`. If you want to /// listen to all interfaces, use `0.0.0.0`. If you want the operating /// system to choose a random port, use port `0`. #[serde(default = "Network::default_bind_address")] pub bind_address: SocketAddr, + /// TSL configuration. #[serde(default = "Network::default_tsl")] pub tsl: Option, diff --git a/src/config/v1/tracker.rs b/src/config/v1/tracker.rs index 67c31bfe..889e77ad 100644 --- a/src/config/v1/tracker.rs +++ b/src/config/v1/tracker.rs @@ -9,23 +9,27 @@ use crate::config::TrackerMode; /// Configuration for the associated tracker. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Tracker { - /// Connection string for the tracker. For example: `udp://TRACKER_IP:6969`. - #[serde(default = "Tracker::default_url")] - pub url: Url, + /// The url of the tracker API. For example: `http://localhost:1212/`. + #[serde(default = "Tracker::default_api_url")] + pub api_url: Url, + /// The mode of the tracker. For example: `Public`. /// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives) /// crate for more information. #[serde(default = "Tracker::default_mode")] pub mode: TrackerMode, - /// The url of the tracker API. For example: `http://localhost:1212/`. - #[serde(default = "Tracker::default_api_url")] - pub api_url: Url, + /// The token used to authenticate with the tracker API. #[serde(default = "Tracker::default_token")] pub token: ApiToken, + /// The amount of seconds the tracker API token is valid. #[serde(default = "Tracker::default_token_valid_seconds")] pub token_valid_seconds: u64, + + /// Connection string for the tracker. For example: `udp://TRACKER_IP:6969`. + #[serde(default = "Tracker::default_url")] + pub url: Url, } impl Validator for Tracker { diff --git a/src/config/v1/tracker_statistics_importer.rs b/src/config/v1/tracker_statistics_importer.rs index 531629b1..c9d5c306 100644 --- a/src/config/v1/tracker_statistics_importer.rs +++ b/src/config/v1/tracker_statistics_importer.rs @@ -3,12 +3,13 @@ use serde::{Deserialize, Serialize}; /// Configuration for the tracker statistics importer. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct TrackerStatisticsImporter { - /// The interval in seconds to get statistics from the tracker. - #[serde(default = "TrackerStatisticsImporter::default_torrent_info_update_interval")] - pub torrent_info_update_interval: u64, /// The port the Importer API is listening on. Default to `3002`. #[serde(default = "TrackerStatisticsImporter::default_port")] pub port: u16, + + /// The interval in seconds to get statistics from the tracker. + #[serde(default = "TrackerStatisticsImporter::default_torrent_info_update_interval")] + pub torrent_info_update_interval: u64, } impl Default for TrackerStatisticsImporter {