177 lines
8.7 KiB
Rust
177 lines
8.7 KiB
Rust
|
/*
|
||
|
* 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 <https://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
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<mpsc::UnboundedSender<EmailMessage>> = OnceCell::const_new();
|
||
|
const EMAIL_RETRY_TIME_SECS: u64 = 10;
|
||
|
const DEVWARNING_TARGET: &str = "AskLyphe Developer <devnull@voremicrocomputers.com>";
|
||
|
|
||
|
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 <no-reply@voremicrocomputers.com>".parse().unwrap())
|
||
|
.reply_to("Vore Microcomputers Support <devnull@voremicrocomputers.com>".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 <no-reply@voremicrocomputers.com>".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 <no-reply@voremicrocomputers.com>".parse().unwrap())
|
||
|
.reply_to("Vore Microcomputers Support <devnull@voremicrocomputers.com>".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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|