From d0cdfa97c7b6c927a18eae3f49c9caf22d183a69 Mon Sep 17 00:00:00 2001 From: Valentin Tolmer Date: Wed, 2 Aug 2023 14:24:40 +0200 Subject: [PATCH] server: Add a message ID to sent emails Fixes #608 --- Cargo.lock | 3 +++ server/Cargo.toml | 6 ++++- server/src/infra/cli.rs | 3 ++- server/src/infra/configuration.rs | 7 +++--- server/src/infra/mail.rs | 39 +++++++++++++++++++++++++------ server/src/infra/tcp_server.rs | 4 ++-- 6 files changed, 48 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 36b7add..d7e91b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2466,6 +2466,7 @@ dependencies = [ "tracing-forest", "tracing-log", "tracing-subscriber", + "url", "urlencoding", "uuid 1.3.1", "webpki-roots", @@ -4509,6 +4510,7 @@ dependencies = [ "form_urlencoded", "idna 0.3.0", "percent-encoding", + "serde", ] [[package]] @@ -4538,6 +4540,7 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b55a3fef2a1e3b3a00ce878640918820d3c51081576ac657d23af9fc7928fdb" dependencies = [ + "atomic", "getrandom 0.2.8", "md-5", ] diff --git a/server/Cargo.toml b/server/Cargo.toml index f959035..1691208 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -96,7 +96,7 @@ features = ["full"] version = "1.25" [dependencies.uuid] -features = ["v3"] +features = ["v1", "v3"] version = "*" [dependencies.tracing-forest] @@ -126,6 +126,10 @@ features = ["rustls-tls-webpki-roots"] version = "0.20" features = ["dangerous_configuration"] +[dependencies.url] +version = "2" +features = ["serde"] + [dev-dependencies] assert_cmd = "2.0" mockall = "0.11.4" diff --git a/server/src/infra/cli.rs b/server/src/infra/cli.rs index b17eedb..998283a 100644 --- a/server/src/infra/cli.rs +++ b/server/src/infra/cli.rs @@ -1,6 +1,7 @@ use clap::{builder::EnumValueParser, Parser}; use lettre::message::Mailbox; use serde::{Deserialize, Serialize}; +use url::Url; /// lldap is a lightweight LDAP server #[derive(Debug, Parser, Clone)] @@ -82,7 +83,7 @@ pub struct RunOpts { /// URL of the server, for password reset links. #[clap(long, env = "LLDAP_HTTP_URL")] - pub http_url: Option, + pub http_url: Option, /// Database connection URL #[clap(short, long, env = "LLDAP_DATABASE_URL")] diff --git a/server/src/infra/configuration.rs b/server/src/infra/configuration.rs index 2138812..523b1f5 100644 --- a/server/src/infra/configuration.rs +++ b/server/src/infra/configuration.rs @@ -11,6 +11,7 @@ use lettre::message::Mailbox; use lldap_auth::opaque::{server::ServerSetup, KeyPair}; use secstr::SecUtf8; use serde::{Deserialize, Serialize}; +use url::Url; #[derive(Clone, Debug, Deserialize, Serialize, derive_builder::Builder)] #[builder(pattern = "owned")] @@ -100,8 +101,8 @@ pub struct Configuration { pub smtp_options: MailOptions, #[builder(default)] pub ldaps_options: LdapsOptions, - #[builder(default = r#"String::from("http://localhost")"#)] - pub http_url: String, + #[builder(default = r#"Url::parse("http://localhost").unwrap()"#)] + pub http_url: Url, #[serde(skip)] #[builder(field(private), default = "None")] server_setup: Option, @@ -237,7 +238,7 @@ impl ConfigOverrider for RunOpts { } if let Some(url) = self.http_url.as_ref() { - config.http_url = url.to_string(); + config.http_url = url.clone(); } if let Some(database_url) = self.database_url.as_ref() { diff --git a/server/src/infra/mail.rs b/server/src/infra/mail.rs index ef489dd..6bccf04 100644 --- a/server/src/infra/mail.rs +++ b/server/src/infra/mail.rs @@ -6,7 +6,13 @@ use lettre::{ }; use tracing::debug; -async fn send_email(to: Mailbox, subject: &str, body: String, options: &MailOptions) -> Result<()> { +async fn send_email( + to: Mailbox, + subject: &str, + body: String, + options: &MailOptions, + server_url: &url::Url, +) -> Result<()> { let from = options .from .clone() @@ -17,6 +23,14 @@ async fn send_email(to: Mailbox, subject: &str, body: String, options: &MailOpti &to, &from, &options.user, &options.server, options.port ); let email = Message::builder() + .message_id(Some(format!( + "<{}@{}>", + uuid::Uuid::new_v1( + uuid::Timestamp::now(uuid::NoContext), + "lldap!".as_bytes().try_into().unwrap() + ), + server_url.domain().unwrap_or_default() + ))) .from(from) .reply_to(reply_to) .to(to) @@ -58,24 +72,34 @@ pub async fn send_password_reset_email( username: &str, to: &str, token: &str, - domain: &str, + server_url: &url::Url, options: &MailOptions, ) -> Result<()> { let to = to.parse()?; + let mut reset_url = server_url.clone(); + reset_url + .path_segments_mut() + .unwrap() + .extend(["reset-password", "step2", token]); let body = format!( "Hello {}, This email has been sent to you in order to validate your identity. If you did not initiate the process your credentials might have been compromised. You should reset your password and contact an administrator. -To reset your password please visit the following URL: {}/reset-password/step2/{} +To reset your password please visit the following URL: {} Please contact an administrator if you did not initiate the process.", - username, - domain.trim_end_matches('/'), - token + username, reset_url ); - send_email(to, "[LLDAP] Password reset requested", body, options).await + send_email( + to, + "[LLDAP] Password reset requested", + body, + options, + server_url, + ) + .await } pub async fn send_test_email(to: Mailbox, options: &MailOptions) -> Result<()> { @@ -84,6 +108,7 @@ pub async fn send_test_email(to: Mailbox, options: &MailOptions) -> Result<()> { "LLDAP test email", "The test is successful! You can send emails from LLDAP".to_string(), options, + &url::Url::parse("http://localhost").unwrap(), ) .await } diff --git a/server/src/infra/tcp_server.rs b/server/src/infra/tcp_server.rs index 989688d..d4bf64d 100644 --- a/server/src/infra/tcp_server.rs +++ b/server/src/infra/tcp_server.rs @@ -87,7 +87,7 @@ fn http_config( backend_handler: Backend, jwt_secret: secstr::SecUtf8, jwt_blacklist: HashSet, - server_url: String, + server_url: url::Url, mail_options: MailOptions, ) where Backend: TcpBackendHandler + BackendHandler + LoginHandler + OpaqueHandler + Clone + 'static, @@ -132,7 +132,7 @@ pub(crate) struct AppState { pub backend_handler: AccessControlledBackendHandler, pub jwt_key: Hmac, pub jwt_blacklist: RwLock>, - pub server_url: String, + pub server_url: url::Url, pub mail_options: MailOptions, }