From b696aa4fe0a67e37202ec4d22d449c2b2fb6f42e Mon Sep 17 00:00:00 2001 From: Max Hohlfeld Date: Fri, 24 Jan 2025 15:51:22 +0100 Subject: [PATCH] refactor: use async email sending --- Cargo.lock | 161 ++++----------------------- web/Cargo.toml | 2 +- web/src/endpoints/user/post_new.rs | 2 +- web/src/endpoints/user/post_reset.rs | 2 +- web/src/mail/forgot_password.rs | 8 +- web/src/mail/mod.rs | 73 +++++++----- web/src/mail/registration.rs | 12 +- web/src/mail/testmail.rs | 6 +- web/src/utils/manage_commands.rs | 2 +- 9 files changed, 86 insertions(+), 182 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b2043dd..b235c003 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1038,16 +1038,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1447,21 +1437,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1554,6 +1529,17 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "futures-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" +dependencies = [ + "futures-io", + "rustls", + "rustls-pki-types", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -1769,17 +1755,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "hostname" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" -dependencies = [ - "cfg-if", - "libc", - "windows", -] - [[package]] name = "http" version = "0.2.12" @@ -2115,23 +2090,29 @@ version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab4c9a167ff73df98a5ecc07e8bf5ce90b583665da3d1762eb1f775ad4d0d6f5" dependencies = [ + "async-std", + "async-trait", "base64 0.22.1", "chumsky", "email-encoding", "email_address", "fastrand 2.3.0", + "futures-io", + "futures-rustls", "futures-util", - "hostname", "httpdate", "idna", "mime", - "native-tls", "nom", "percent-encoding", "quoted_printable", + "rustls", + "rustls-pemfile", + "rustls-pki-types", "socket2 0.5.8", "tokio", "url", + "webpki-roots", ] [[package]] @@ -2306,23 +2287,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "native-tls" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nom" version = "7.1.3" @@ -2413,50 +2377,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "openssl" -version = "0.10.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" -dependencies = [ - "bitflags 2.8.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "parking" version = "2.2.1" @@ -2955,6 +2875,7 @@ version = "0.23.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" dependencies = [ + "log", "once_cell", "ring", "rustls-pki-types", @@ -3001,44 +2922,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.8.0", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "1.0.25" @@ -3939,16 +3828,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core", - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.52.0" diff --git a/web/Cargo.toml b/web/Cargo.toml index a0d151bf..dd034a14 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -22,7 +22,7 @@ serde_json = "1.0.114" pico-args = "0.5.0" rand = { version = "0.8.5", features = ["getrandom"] } async-trait = "0.1.79" -lettre = "0.11.11" +lettre = { version = "0.11.11", default-features = false, features = ["builder", "smtp-transport", "async-std1-rustls-tls"] } quick-xml = { version = "0.37", features = ["serde", "serialize"] } actix-web-static-files = "4.0" static-files = "0.2.1" diff --git a/web/src/endpoints/user/post_new.rs b/web/src/endpoints/user/post_new.rs index 4b0a45c6..9f7398c5 100644 --- a/web/src/endpoints/user/post_new.rs +++ b/web/src/endpoints/user/post_new.rs @@ -49,7 +49,7 @@ pub async fn post_new( let registration = Registration::insert_new_for_user(pool.get_ref(), id).await?; let new_user = User::read_by_id(pool.get_ref(), id).await?.unwrap(); - mailer.send_registration_mail(&new_user, ®istration.token)?; + mailer.send_registration_mail(&new_user, ®istration.token).await?; Ok(HttpResponse::Found() .insert_header((LOCATION, "/users")) diff --git a/web/src/endpoints/user/post_reset.rs b/web/src/endpoints/user/post_reset.rs index fc7552e7..32cf8414 100644 --- a/web/src/endpoints/user/post_reset.rs +++ b/web/src/endpoints/user/post_reset.rs @@ -31,7 +31,7 @@ async fn post( { if let Ok(user) = User::read_for_login(pool.get_ref(), form.email.as_ref().unwrap()).await { let reset = PasswordReset::insert_new_for_user(pool.get_ref(), user.id).await?; - mailer.send_forgot_password_mail(&user, &reset.token)?; + mailer.send_forgot_password_mail(&user, &reset.token).await?; } return Ok(HttpResponse::Ok().body("E-Mail versandt!")); diff --git a/web/src/mail/forgot_password.rs b/web/src/mail/forgot_password.rs index 20641fb2..d4efdbc4 100644 --- a/web/src/mail/forgot_password.rs +++ b/web/src/mail/forgot_password.rs @@ -1,6 +1,6 @@ use lettre::{ message::{Mailbox, MultiPart, SinglePart}, - Message, Transport, + AsyncTransport, Message, }; use rinja::Template; @@ -9,13 +9,13 @@ use crate::{models::User, utils::ApplicationError}; use super::Mailer; impl Mailer { - pub fn send_forgot_password_mail( + pub async fn send_forgot_password_mail( &self, user: &User, token: &str, ) -> Result<(), ApplicationError> { let message = build(&self.hostname, &user.name, &user.email, token)?; - self.transport.send(&message)?; + self.transport.send(message).await?; Ok(()) } @@ -74,7 +74,7 @@ fn build( mod tests { use super::*; use crate::utils::test_helper::assert_mail_snapshot; - use lettre::transport::stub::StubTransport; + use lettre::{transport::stub::StubTransport, Transport}; #[test] fn build_mail_snapshot() { diff --git a/web/src/mail/mod.rs b/web/src/mail/mod.rs index ff900ac0..2302e2f1 100644 --- a/web/src/mail/mod.rs +++ b/web/src/mail/mod.rs @@ -1,10 +1,11 @@ use brass_config::{Config, SmtpTlsType}; use lettre::{ + address::Envelope, transport::{ smtp::{authentication::Credentials, extension::ClientId}, - stub::StubTransport, + stub::AsyncStubTransport, }, - SmtpTransport, Transport, + AsyncSmtpTransport, AsyncStd1Executor, AsyncTransport, }; use crate::utils::ApplicationError; @@ -21,30 +22,46 @@ pub struct Mailer { #[derive(Clone)] enum Transports { - SmtpTransport(SmtpTransport), + SmtpTransport(AsyncSmtpTransport), #[allow(unused)] - StubTransport(StubTransport), + StubTransport(AsyncStubTransport), } -impl Transport for Transports { +impl AsyncTransport for Transports { type Ok = (); type Error = ApplicationError; - fn send_raw( - &self, - envelope: &lettre::address::Envelope, - email: &[u8], - ) -> Result { - match self { - Transports::SmtpTransport(smtp_transport) => smtp_transport - .send_raw(envelope, email) - .map(|_| ()) - .map_err(|err| ApplicationError::EmailTransport(err)), - Transports::StubTransport(stub_transport) => stub_transport - .send_raw(envelope, email) - .map(|_| ()) - .map_err(|err| ApplicationError::EmailStubTransport(err)), - } + fn send_raw<'life0, 'life1, 'life2, 'async_trait>( + &'life0 self, + envelope: &'life1 Envelope, + email: &'life2 [u8], + ) -> ::core::pin::Pin< + Box< + dyn ::core::future::Future> + + ::core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + 'life1: 'async_trait, + 'life2: 'async_trait, + Self: 'async_trait, + { + return Box::pin(async move { + match self { + Transports::SmtpTransport(smtp_transport) => smtp_transport + .send_raw(envelope, email) + .await + .map(|_| ()) + .map_err(|err| ApplicationError::EmailTransport(err)), + Transports::StubTransport(stub_transport) => stub_transport + .send_raw(envelope, email) + .await + .map(|_| ()) + .map_err(|err| ApplicationError::EmailStubTransport(err)), + } + }); } } @@ -52,11 +69,16 @@ impl Mailer { pub fn new(config: &Config) -> anyhow::Result { let mut builder = match config.smtp_tlstype { SmtpTlsType::StartTLS => { - SmtpTransport::starttls_relay(&config.smtp_server)?.port(config.smtp_port) + AsyncSmtpTransport::::starttls_relay(&config.smtp_server)? + .port(config.smtp_port) + } + SmtpTlsType::TLS => { + AsyncSmtpTransport::::relay(&config.smtp_server)? + .port(config.smtp_port) } - SmtpTlsType::TLS => SmtpTransport::relay(&config.smtp_server)?.port(config.smtp_port), SmtpTlsType::NoTLS => { - SmtpTransport::builder_dangerous(&config.smtp_server).port(config.smtp_port) + AsyncSmtpTransport::::builder_dangerous(&config.smtp_server) + .port(config.smtp_port) } }; @@ -80,13 +102,12 @@ impl Mailer { } } - #[cfg(test)] impl Mailer { pub fn new_stub() -> Self { Mailer { - transport: Transports::StubTransport(StubTransport::new_ok()), - hostname: String::from("testhostname") + transport: Transports::StubTransport(AsyncStubTransport::new_ok()), + hostname: String::from("testhostname"), } } } diff --git a/web/src/mail/registration.rs b/web/src/mail/registration.rs index ab0dd1e6..8e8f528e 100644 --- a/web/src/mail/registration.rs +++ b/web/src/mail/registration.rs @@ -1,6 +1,6 @@ use lettre::{ message::{Mailbox, MultiPart, SinglePart}, - Message, Transport, + AsyncTransport, Message, }; use rinja::Template; @@ -9,9 +9,13 @@ use crate::{models::User, utils::ApplicationError}; use super::Mailer; impl Mailer { - pub fn send_registration_mail(&self, user: &User, token: &str) -> Result<(), ApplicationError> { + pub async fn send_registration_mail( + &self, + user: &User, + token: &str, + ) -> Result<(), ApplicationError> { let message = build(&self.hostname, &user.name, &user.email, token)?; - self.transport.send(&message)?; + self.transport.send(message).await?; Ok(()) } @@ -73,7 +77,7 @@ fn build( mod tests { use super::*; use crate::utils::test_helper::assert_mail_snapshot; - use lettre::transport::stub::StubTransport; + use lettre::{transport::stub::StubTransport, Transport}; #[test] fn build_mail_snapshot() { diff --git a/web/src/mail/testmail.rs b/web/src/mail/testmail.rs index 1a68f818..e9946fee 100644 --- a/web/src/mail/testmail.rs +++ b/web/src/mail/testmail.rs @@ -1,11 +1,11 @@ -use lettre::{message::SinglePart, Message, Transport}; +use lettre::{message::SinglePart, AsyncTransport, Message}; use crate::utils::ApplicationError; use super::Mailer; impl Mailer { - pub fn send_test_mail(&self, to: &str) -> Result<(), ApplicationError> { + pub async fn send_test_mail(&self, to: &str) -> Result<(), ApplicationError> { let message = Message::builder() .from("noreply ".parse()?) .reply_to("noreply ".parse()?) @@ -15,7 +15,7 @@ impl Mailer { "Testmail von Brass. E-Mail Versand funktioniert!".to_string(), ))?; - self.transport.send(&message)?; + self.transport.send(message).await?; Ok(()) } } diff --git a/web/src/utils/manage_commands.rs b/web/src/utils/manage_commands.rs index a4605b70..220ba315 100644 --- a/web/src/utils/manage_commands.rs +++ b/web/src/utils/manage_commands.rs @@ -98,7 +98,7 @@ pub async fn handle_command( exit(0); } Some(Command::TestMail(to)) => { - match mailer.send_test_mail(&to) { + match mailer.send_test_mail(&to).await { Ok(_) => println!("Successfully sent mail to {to}."), Err(e) => { if let Some(source) = e.source() {