/* * authservice email/mod.rs * - email thread, most of everything needed for emails are in this file * * Copyright (C) 2025 Real Microsoft, LLC * * 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, version 3. * * 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 . */ mod templates; use lettre::{Message, SmtpTransport, Transport}; use lettre::message::header::ContentType; use lettre::transport::smtp::authentication::Credentials; use log::{error, info}; use tokio::sync::{mpsc, OnceCell}; use crate::{SMTP_DISABLE, SMTP_PASSWORD, SMTP_URL, SMTP_USERNAME}; pub enum EmailMessage { VerificationCode { to: String, username: String, url: String, }, DeveloperWarning { message: String, }, Announcement { to: String, username: String, title: String, text_contents: String, } } static EMAIL_SENDER: OnceCell> = OnceCell::const_new(); const EMAIL_RETRY_TIME_SECS: u64 = 10; const DEVWARNING_TARGET: &str = "AskLyphe Developer "; pub fn send_devwarn_email(message: &str) { EMAIL_SENDER.get().unwrap().send(EmailMessage::DeveloperWarning { message: message.to_string(), }).unwrap(); } pub fn send_verification_code_email(to: &str, username: &str, url: &str) { EMAIL_SENDER.get().unwrap().send(EmailMessage::VerificationCode { to: to.to_string(), username: username.to_string(), url: url.to_string(), }).unwrap(); } pub fn send_announcement_email(to: &str, username: &str, title: &str, content: &str) { EMAIL_SENDER.get().unwrap().send(EmailMessage::Announcement { to: to.to_string(), username: username.to_string(), title: title.to_string(), text_contents: content.to_string(), }).unwrap() } pub async fn email_init() { let (email_sender, email_receiver) = mpsc::unbounded_channel(); EMAIL_SENDER.set(email_sender).unwrap(); tokio::spawn(async move { info!("email thread spawned"); if *SMTP_DISABLE { info!("SMTP disable"); } let creds = Credentials::new(SMTP_USERNAME.to_string(), SMTP_PASSWORD.to_string()); let mailer = SmtpTransport::starttls_relay(&SMTP_URL).expect("FAILED TO CONNECT TO EMAIL SMTP RELAY") .credentials(creds) .build(); let mut rx = email_receiver; loop { if let Ok(message) = rx.try_recv() { match message { EmailMessage::VerificationCode { to, username, url } => { if *SMTP_DISABLE { info!("not sending smtp verification code due to smtp disable"); continue; } let body = templates::gen_verification_code_email(&url); let email = Message::builder() .from("Vore Microcomputers No-Reply ".parse().unwrap()) .reply_to("Vore Microcomputers Support ".parse().unwrap()); let to_mbox = format!("{username} <{to}>").parse(); if let Err(e) = to_mbox { error!("couldn't parse \"to\" field for verification code email! \"{username}\" \"{to}\""); send_devwarn_email(&format!("couldn't parse \"to\" field for verification code email! \"{username}\" \"{to}\" you need to send the email manually and investigate")); continue; } let email = email.to(to_mbox.unwrap()) .subject("AskLyphe Account Verification") .header(ContentType::TEXT_PLAIN) .body(body); if let Err(e) = email { error!("couldn't generate email for verification! \"{e}\" \"{username}\" \"{to}\""); send_devwarn_email(&format!("couldn't generate verification code email! \"{e}\" \"{username}\" \"{to}\" you need to send the email manually and investigate")); continue; } let email = email.unwrap(); if let Err(e) = mailer.send(&email) { error!("couldn't send email for verification! \"{e}\" \"{username}\" \"{to}\""); send_devwarn_email(&format!("couldn't send verification code email! \"{e}\" \"{username}\" \"{to}\" you need to send the email manually and investigate")); continue; } } EmailMessage::DeveloperWarning { message } => { if *SMTP_DISABLE { info!("not sending smtp developer warning due to smtp disable"); continue; } let body = templates::gen_developer_warning_email(&message); let email = Message::builder() .from("Vore Microcomputers No-Reply ".parse().unwrap()) .to(DEVWARNING_TARGET.parse().unwrap()) .subject("AskLyphe Developer Warning") .header(ContentType::TEXT_PLAIN) .body(body); if let Err(e) = email { error!("couldn't generate email for developer warning!! \"{e}\" \"{message}\""); continue; } let email = email.unwrap(); if let Err(e) = mailer.send(&email) { error!("couldn't send email for developer warning!!! \"{e}\" \"{message}\""); continue; } } EmailMessage::Announcement { to, username, title, text_contents } => { if *SMTP_DISABLE { info!("not sending smtp announcement due to smtp disable"); continue; } let body = templates::gen_announcement_email(&text_contents); let email = Message::builder() .from("Vore Microcomputers No-Reply ".parse().unwrap()) .reply_to("Vore Microcomputers Support ".parse().unwrap()); let to_mbox = format!("{username} <{to}>").parse(); if let Err(e) = to_mbox { error!("couldn't parse \"to\" field for announcement email! \"{username}\" \"{to}\""); send_devwarn_email(&format!("couldn't parse \"to\" field for announcement email! \"{username}\" \"{to}\" you need to send the email manually and investigate")); continue; } let email = email.to(to_mbox.unwrap()) .subject(format!("AskLyphe Announcement: {title}").as_str()) .header(ContentType::TEXT_PLAIN) .body(body); if let Err(e) = email { error!("couldn't generate email for announcement! \"{e}\" \"{username}\" \"{to}\""); send_devwarn_email(&format!("couldn't generate announcement email! \"{e}\" \"{username}\" \"{to}\" you need to send the email manually and investigate")); continue; } let email = email.unwrap(); if let Err(e) = mailer.send(&email) { error!("couldn't send email for announcement! \"{e}\" \"{username}\" \"{to}\""); send_devwarn_email(&format!("couldn't send announcement email! \"{e}\" \"{username}\" \"{to}\" you need to send the email manually and investigate")); continue; } } } } } }); }