feat: lettre email adapter: load email config and test account_validation_link
This commit is contained in:
parent
26ba1f4e1d
commit
1815637a38
4 changed files with 356 additions and 52 deletions
275
Cargo.lock
generated
275
Cargo.lock
generated
|
@ -39,8 +39,8 @@ dependencies = [
|
|||
"encoding_rs",
|
||||
"flate2",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"h2 0.3.26",
|
||||
"http 0.2.12",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
|
@ -91,7 +91,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d22475596539443685426b6bdadb926ad0ecaefdfc5fb05e5e3441f15463c511"
|
||||
dependencies = [
|
||||
"bytestring",
|
||||
"http",
|
||||
"http 0.2.12",
|
||||
"regex",
|
||||
"serde",
|
||||
"tracing",
|
||||
|
@ -396,6 +396,12 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.3.0"
|
||||
|
@ -1389,7 +1395,26 @@ dependencies = [
|
|||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http 0.2.12",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.1.0",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
|
@ -1495,6 +1520,40 @@ dependencies = [
|
|||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http 1.1.0",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.8.0"
|
||||
|
@ -1522,6 +1581,62 @@ version = "2.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2 0.4.5",
|
||||
"http 1.1.0",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.60"
|
||||
|
@ -1612,6 +1727,12 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.12"
|
||||
|
@ -2500,6 +2621,48 @@ version = "0.8.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.4.5",
|
||||
"http 1.1.0",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls-pemfile 2.1.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.8"
|
||||
|
@ -3233,6 +3396,33 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.10.1"
|
||||
|
@ -3476,6 +3666,34 @@ dependencies = [
|
|||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
|
@ -3521,6 +3739,12 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
|
@ -3757,8 +3981,10 @@ dependencies = [
|
|||
"mockall",
|
||||
"pretty_env_logger",
|
||||
"rand",
|
||||
"reqwest",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"tera",
|
||||
"time",
|
||||
|
@ -3790,6 +4016,15 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
|
||||
dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
@ -3827,6 +4062,18 @@ dependencies = [
|
|||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.92"
|
||||
|
@ -3856,6 +4103,16 @@ version = "0.2.92"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.25.4"
|
||||
|
@ -4069,6 +4326,16 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
|
|
|
@ -33,3 +33,7 @@ tracing = { version = "0.1.40", features = ["log"] }
|
|||
tracing-actix-web = "0.7.10"
|
||||
url = { version = "2.5.0", features = ["serde"] }
|
||||
validator = { version = "0.18.1", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
reqwest = { version = "0.12.4", features = ["json"] }
|
||||
serde_json = "1.0.117"
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use lettre::{message::header::ContentType, AsyncTransport, Message};
|
||||
|
||||
use super::*;
|
||||
use crate::identity::application::port::output::mailer::{account_validation_link::*, errors::*};
|
||||
|
||||
|
@ -13,53 +15,83 @@ impl AccountValidationLinkOutMailerPort for LettreMailer {
|
|||
username: &str,
|
||||
validation_secret: &str,
|
||||
) -> OutMailerPortResult<()> {
|
||||
|
||||
let email = Message::builder()
|
||||
.from(&self.from)
|
||||
.reply_to(&self.reply_to)
|
||||
.to(to)
|
||||
.subject("Please validate your account on Vanikam") // TODO: use better title
|
||||
.from(self.from.parse().unwrap())
|
||||
.reply_to(self.reply_to.parse().unwrap())
|
||||
.to(format!("{username} <{to}>").parse().unwrap())
|
||||
.subject("Please verify your account on Vanikam") // TODO: use better title
|
||||
.header(ContentType::TEXT_PLAIN)
|
||||
.body(format!(r#"Hello {username},
|
||||
Please click here to validate your Vanikam account: {validation_secret}
|
||||
.body(format!(
|
||||
r#"Hello {username},
|
||||
Please click here to verify your Vanikam account: {validation_secret}
|
||||
Warm regards,
|
||||
Vanikam Admin
|
||||
"#)) // TODO: change signature
|
||||
"#
|
||||
)) // TODO: change signature
|
||||
.unwrap();
|
||||
mailer.send(email).await.unwrap();
|
||||
self.mailer.send(email).await.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
// TODO: mailer tests
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use reqwest::Client;
|
||||
use url::Url;
|
||||
|
||||
//#[cfg(test)]
|
||||
//mod tests {
|
||||
// use super::*;
|
||||
//
|
||||
// #[actix_rt::test]
|
||||
// async fn test_postgres_create_verification_secret() {
|
||||
// let settings = crate::settings::tests::get_settings().await;
|
||||
// let db = super::DBOutPostgresAdapter::new(
|
||||
// sqlx::postgres::PgPool::connect(&settings.database.url)
|
||||
// .await
|
||||
// .unwrap(),
|
||||
// );
|
||||
//
|
||||
// let msg = CreateSecretMsgBuilder::default()
|
||||
// .secret("secret".into())
|
||||
// .purpose("purpose".into())
|
||||
// .username("username".into())
|
||||
// .build()
|
||||
// .unwrap();
|
||||
//
|
||||
// db.create_verification_secret(msg.clone()).await.unwrap();
|
||||
//
|
||||
// // duplicate: secret exists
|
||||
// assert_eq!(
|
||||
// db.create_verification_secret(msg).await.err(),
|
||||
// Some(OutDBPortError::VerificationOTPSecretExists)
|
||||
// );
|
||||
//
|
||||
// settings.drop_db().await;
|
||||
// }
|
||||
//}
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
struct MaildevAddress {
|
||||
address: String,
|
||||
name: String,
|
||||
}
|
||||
#[derive(Deserialize, Clone)]
|
||||
struct MaildevEmail {
|
||||
id: String,
|
||||
from: Vec<MaildevAddress>,
|
||||
to: Vec<MaildevAddress>,
|
||||
subject: String,
|
||||
text: String,
|
||||
html: Option<String>,
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_mailer_account_validation_link() {
|
||||
let username = "batman";
|
||||
let email = "batman@account_validation_link.example.com";
|
||||
let validation_secret = "dafsdfasecret";
|
||||
|
||||
let settings = crate::settings::tests::get_settings().await;
|
||||
let m = LettreMailer::new(&settings).await;
|
||||
|
||||
m.account_validation_link(email, username, validation_secret)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let c = Client::default();
|
||||
|
||||
let maildev_url =
|
||||
std::env::var("MAILDEV_URL").expect("Please set maildev instance URL in MAILDEV_URL");
|
||||
|
||||
let mut u = Url::parse(&maildev_url).unwrap();
|
||||
|
||||
u.set_path("/email");
|
||||
let maildev_emails: Vec<MaildevEmail> =
|
||||
c.get(u.clone()).send().await.unwrap().json().await.unwrap();
|
||||
let maildev_email = maildev_emails
|
||||
.iter()
|
||||
.find(|e| e.to.iter().any(|f| f.address == email))
|
||||
.unwrap();
|
||||
assert!(maildev_email.text.contains(validation_secret));
|
||||
assert!(maildev_email.text.contains(username));
|
||||
assert!(maildev_email
|
||||
.to
|
||||
.iter()
|
||||
.any(|t| t.address == email && t.name == username));
|
||||
|
||||
u.set_path(&format!("/email/{}", maildev_email.id));
|
||||
c.delete(u).send().await.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use lettre::{transport::smtp::authentication::Credentials, AsyncSmtpTransport, Tokio1Executor};
|
||||
use lettre::{AsyncSmtpTransport, Tokio1Executor};
|
||||
|
||||
use crate::settings::Settings;
|
||||
|
||||
pub mod account_validation_link;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LettreMailer {
|
||||
mailer: AsyncSmtpTransport<Tokio1Executor>,
|
||||
|
@ -15,18 +17,17 @@ pub struct LettreMailer {
|
|||
|
||||
impl LettreMailer {
|
||||
pub async fn new(s: &Settings) -> Self {
|
||||
let creds = Credentials::new(s.email.username.clone(), s.email.password.clone());
|
||||
|
||||
let mailer: AsyncSmtpTransport<Tokio1Executor> =
|
||||
AsyncSmtpTransport::<Tokio1Executor>::relay(&s.email.server_hostname)
|
||||
AsyncSmtpTransport::<Tokio1Executor>::from_url(s.email.url.as_str())
|
||||
.unwrap()
|
||||
.credentials(creds)
|
||||
.build();
|
||||
|
||||
assert!(mailer.test_connection().await.unwrap());
|
||||
|
||||
Self {
|
||||
mailer,
|
||||
from: String::default(), // TODO: create settings module to read config
|
||||
reply_to: String::default(),
|
||||
from: s.email.from.clone(),
|
||||
reply_to: s.email.reply_to.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue