use lettre::{ transport::smtp::authentication::Credentials, AsyncSmtpTransport, AsyncTransport, Tokio1Executor, // Address, message::{ Mailbox, header::ContentType, MultiPart, SinglePart, }, Message, }; use crate::defs::AppDBs; use pasetoken_lib::ConfigPaSeToken; use serde_json::json; #[derive(Clone,Debug)] pub struct MailMessage { pub from: Mailbox, pub to: Mailbox, pub reply_to: Mailbox, } impl MailMessage { pub fn new(from: &str, to: &str, reply: &str) -> anyhow::Result { let reply_to = if reply.is_empty() { to } else { reply }; Ok( Self { from: from.parse()?, to: to.parse()?, reply_to: reply_to.parse()?, } ) } pub fn check(app_dbs: &AppDBs) -> String { if app_dbs.config.smtp.is_empty() { String::from("Error: no mail server") } else if app_dbs.config.mail_from.is_empty() { String::from("Error: no mail from address") } else { String::from("") } } #[allow(dead_code)] pub async fn send_message(&self, subject: &str, body: &str, app_dbs: &AppDBs) -> std::io::Result<()> { match Message::builder() .from(self.from.to_owned()) .reply_to(self.reply_to.to_owned()) .to(self.to.to_owned()) .subject(subject) .header(ContentType::TEXT_PLAIN) .body(body.to_owned()) { Ok(message) => self.mail_message(message, app_dbs).await, Err(e) => return Err(std::io::Error::new( std::io::ErrorKind::NotFound, format!("ERROR: Invalid mail: {}",e) )) } } pub async fn send_html_message(&self, subject: &str, body: &str, html_body: &str, app_dbs: &AppDBs) -> std::io::Result<()> { match Message::builder() .from(self.from.to_owned()) .reply_to(self.reply_to.to_owned()) .to(self.to.to_owned()) .subject(subject) .multipart( MultiPart::alternative() // This is composed of two parts. .singlepart( SinglePart::builder() .header(ContentType::TEXT_PLAIN) .body(body.to_owned()), ) .singlepart( SinglePart::builder() .header(ContentType::TEXT_HTML) .body(html_body.to_owned()), ), ) { Ok(message) => self.mail_message(message, app_dbs).await, Err(e) => return Err(std::io::Error::new( std::io::ErrorKind::NotFound, format!("ERROR: Invalid mail: {}",e) )) } } pub async fn mail_message(&self, message: Message, app_dbs: &AppDBs) -> std::io::Result<()> { let mail_cred = crate::defs::MailMessage::get_credentials(&app_dbs.config.smtp_auth, &app_dbs.config.paseto); if ! mail_cred.contains("|") { return Err(std::io::Error::new( std::io::ErrorKind::NotFound, format!("ERROR: Invalid mail credentials") )); } let auth_data: Vec = mail_cred.split("|").map(|s| s.to_string()).collect(); if auth_data.len() < 2 { return Err(std::io::Error::new( std::io::ErrorKind::NotFound, format!("ERROR: Invalid mail credentials") )); } let creds = Credentials::new(auth_data[0].to_owned(), auth_data[1].to_owned()); // Open a remote connection to gmail let mailer: AsyncSmtpTransport = AsyncSmtpTransport::::relay(&app_dbs.config.smtp) .unwrap() .credentials(creds) .build(); // Send the email match mailer.send(message).await { Ok(_) => Ok(()), Err(e) => Err(std::io::Error::new( std::io::ErrorKind::NotConnected, format!("ERROR: Could not send email: {e:?}") )) } } pub fn get_credentials(token: &str, paseto_config: &ConfigPaSeToken) -> String { match paseto_config.pasetoken() { Ok(paseto) => { match paseto.trusted(token, false) { Ok(trusted_token) => { if let Some(claims) = trusted_token.payload_claims() { claims.get_claim("smtp_auth").unwrap_or(&json!("")).to_string().replace("\"","") } else { String::from("") } }, Err(e) => { println!("Token not trusted: {}",e); String::from("") }, } }, Err(e) => { println!("Error collecting notify data: {}",e); String::from("") } } } }