From 9ed458ebfaa9847ad09a096e366999669651a20e Mon Sep 17 00:00:00 2001 From: realaravinth Date: Wed, 30 Jun 2021 14:16:48 +0530 Subject: [PATCH] create email client --- src/data.rs | 139 ++++++++++++++++++++++++-------------- src/email.rs | 77 --------------------- src/email/mod.rs | 18 +++++ src/email/verification.rs | 75 ++++++++++++++++++++ 4 files changed, 180 insertions(+), 129 deletions(-) delete mode 100644 src/email.rs create mode 100644 src/email/mod.rs create mode 100644 src/email/verification.rs diff --git a/src/data.rs b/src/data.rs index 9ebbab26..3647b708 100644 --- a/src/data.rs +++ b/src/data.rs @@ -14,10 +14,14 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +//! App data: redis cache, database connections, etc. use std::sync::Arc; use actix::prelude::*; use argon2_creds::{Config, ConfigBuilder, PasswordPolicy}; +use lettre::{ + transport::smtp::authentication::Credentials, AsyncSmtpTransport, Tokio1Executor, +}; use libmcaptcha::cache::hashcache::HashCache; use libmcaptcha::cache::redis::RedisCache; use libmcaptcha::master::redis::master::Master as RedisMaster; @@ -39,12 +43,9 @@ use sqlx::PgPool; use crate::SETTINGS; -pub struct Data { - pub db: PgPool, - pub creds: Config, - pub captcha: SystemGroup, -} - +/// Represents mCaptcha cache and master system. +/// When Redis is configured, [SystemGroup::Redis] is used and +/// in its absense, [SystemGroup::Embedded] is used pub enum SystemGroup { Embedded(System), Redis(System), @@ -78,21 +79,70 @@ impl SystemGroup { } } - // /// utility function to AddSite - // pub async fn add_site( - // &self, - // msg: AddSite, - // ) -> CaptchaResult<()> { - // match self { - // Self::Embedded(val) => val.master.send(msg).await?, - // Self::Redis(val) => val.master.send(msg).await?, - // }; - // Ok(()) - // } + // /// utility function to AddSite + // pub async fn add_site( + // &self, + // msg: AddSite, + // ) -> CaptchaResult<()> { + // match self { + // Self::Embedded(val) => val.master.send(msg).await?, + // Self::Redis(val) => val.master.send(msg).await?, + // }; + // Ok(()) + // } + + fn new_system(m: Addr, c: Addr) -> System { + let pow = PoWConfigBuilder::default() + .salt(SETTINGS.pow.salt.clone()) + .build() + .unwrap(); + + SystemBuilder::default().pow(pow).cache(c).master(m).build() + } + + // read settings, if Redis is configured then produce a Redis mCaptcha cache + // based SystemGroup + async fn new() -> Self { + match &SETTINGS.redis { + Some(val) => { + let master = RedisMaster::new(RedisConfig::Single(val.url.clone())) + .await + .unwrap() + .start(); + let cache = RedisCache::new(RedisConfig::Single(val.url.clone())) + .await + .unwrap() + .start(); + let captcha = Self::new_system(master, cache); + + SystemGroup::Redis(captcha) + } + None => { + let master = EmbeddedMaster::new(SETTINGS.pow.gc).start(); + let cache = HashCache::default().start(); + let captcha = Self::new_system(master, cache); + + SystemGroup::Embedded(captcha) + } + } + } +} + +/// App data +pub struct Data { + /// databse pool + pub db: PgPool, + /// credential management configuration + pub creds: Config, + /// mCaptcha system: Redis cache, etc. + pub captcha: SystemGroup, + /// email client + pub mailer: Option, } impl Data { #[cfg(not(tarpaulin_include))] + /// create new instance of app data pub async fn new() -> Arc { let db = PgPoolOptions::new() .max_connections(SETTINGS.database.pool) @@ -112,46 +162,31 @@ impl Data { creds.init(); log::info!("Initialized credential manager"); - let data = match &SETTINGS.redis { - Some(val) => { - let master = RedisMaster::new(RedisConfig::Single(val.url.clone())) - .await - .unwrap() - .start(); - let cache = RedisCache::new(RedisConfig::Single(val.url.clone())) - .await - .unwrap() - .start(); - let captcha = Self::new_system(master, cache); - - Data { - creds, - db, - captcha: SystemGroup::Redis(captcha), - } - } - None => { - let master = EmbeddedMaster::new(SETTINGS.pow.gc).start(); - let cache = HashCache::default().start(); - let captcha = Self::new_system(master, cache); - - Data { - creds, - db, - captcha: SystemGroup::Embedded(captcha), - } - } + let data = Data { + creds, + db, + captcha: SystemGroup::new().await, + mailer: Self::get_mailer(), }; Arc::new(data) } - fn new_system(m: Addr, c: Addr) -> System { - let pow = PoWConfigBuilder::default() - .salt(SETTINGS.pow.salt.clone()) - .build() - .unwrap(); + fn get_mailer() -> Option { + if let Some(smtp) = SETTINGS.smtp.as_ref() { + let creds = + Credentials::new(smtp.username.to_string(), smtp.password.to_string()); // "smtp_username".to_string(), "smtp_password".to_string()); - SystemBuilder::default().pow(pow).cache(c).master(m).build() + let mailer: Mailer = AsyncSmtpTransport::::relay(&smtp.url) //"smtp.gmail.com") + .unwrap() + .credentials(creds) + .build(); + Some(mailer) + } else { + None + } } } + +/// Mailer data type AsyncSmtpTransport +pub type Mailer = AsyncSmtpTransport; diff --git a/src/email.rs b/src/email.rs deleted file mode 100644 index bed458ea..00000000 --- a/src/email.rs +++ /dev/null @@ -1,77 +0,0 @@ -/* -* Copyright (C) 2021 Aravinth Manivannan -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License as -* published by the Free Software Foundation, either version 3 of the -* License, or (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -*/ - -// The html we want to send. -const HTML: &str = r#" - - - - - Hello from Lettre! - - -
-

Hello from Lettre!

-

A mailer library for Rust

-
- -"#; - -use lettre::{ - message::{header, MultiPart, SinglePart}, - transport::smtp::authentication::Credentials, - AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor, -}; - -async fn email() { - let email = Message::builder() - .from("NoBody ".parse().unwrap()) - .reply_to("Yuin ".parse().unwrap()) - .to("Hei ".parse().unwrap()) - .subject("Happy new async year") - .multipart( - MultiPart::alternative() // This is composed of two parts. - .singlepart( - SinglePart::builder() - .header(header::ContentType::TEXT_PLAIN) - .body(String::from( - "Hello from Lettre! A mailer library for Rust", - )), // Every message should have a plain text fallback. - ) - .singlepart( - SinglePart::builder() - .header(header::ContentType::TEXT_HTML) - .body(String::from(HTML)), - ), - ) - .unwrap(); - - let creds = - Credentials::new("smtp_username".to_string(), "smtp_password".to_string()); - - let mailer: AsyncSmtpTransport = - AsyncSmtpTransport::::relay("smtp.gmail.com") - .unwrap() - .credentials(creds) - .build(); - - // Send the email - match mailer.send(email).await { - Ok(_) => println!("Email sent successfully!"), - Err(e) => panic!("Could not send email: {:?}", e), - } -} diff --git a/src/email/mod.rs b/src/email/mod.rs new file mode 100644 index 00000000..ee59faed --- /dev/null +++ b/src/email/mod.rs @@ -0,0 +1,18 @@ +/* +* Copyright (C) 2021 Aravinth Manivannan +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +*/ + +pub mod verification; diff --git a/src/email/verification.rs b/src/email/verification.rs new file mode 100644 index 00000000..3768156e --- /dev/null +++ b/src/email/verification.rs @@ -0,0 +1,75 @@ +/* +* Copyright (C) 2021 Aravinth Manivannan +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +*/ +//! Email operations: verification, notification, etc +use lettre::{ + message::{header, MultiPart, SinglePart}, + AsyncTransport, Message, +}; + +use crate::AppData; +use crate::SETTINGS; + +// The html we want to send. +const HTML: &str = r#" + + + + + Hello from Lettre! + + +
+

Hello from Lettre!

+

A mailer library for Rust

+
+ +"#; + +async fn verification(data: &AppData) { + if let Some(smtp) = SETTINGS.smtp.as_ref() { + let from = format!("mCaptcha Admin <{}>", smtp.from); + const SUBJECT: &str = "[mCaptcha] Please verify your email"; + + let email = Message::builder() + .from(from.parse().unwrap()) + .reply_to("Yuin ".parse().unwrap()) + .to("Hei ".parse().unwrap()) + .subject(SUBJECT) + .multipart( + MultiPart::alternative() // This is composed of two parts. + .singlepart( + SinglePart::builder() + .header(header::ContentType::TEXT_PLAIN) + .body(String::from( + "Hello from Lettre! A mailer library for Rust", + )), // Every message should have a plain text fallback. + ) + .singlepart( + SinglePart::builder() + .header(header::ContentType::TEXT_HTML) + .body(String::from(HTML)), + ), + ) + .unwrap(); + + // unwrap is OK as SETTINGS.smtp is check at the start + match data.mailer.as_ref().unwrap().send(email).await { + Ok(_) => println!("Email sent successfully!"), + Err(e) => panic!("Could not send email: {:?}", e), + } + } +}