use core::{str, time::Duration};
use ::base64::prelude::*;
use anyhow::Ok;
use header::ContentTransferEncoding;
use lettre::message::*;
use quoted_printable::ParseMode;
pub use crate::fnd::totp::TOTP;
pub trait TotpEmailMessage {
fn email_subject(&self) -> String;
fn email_body(&self, totp_secret_base32: String, seconds: u64) -> anyhow::Result<SinglePart>;
fn email_message(
&self,
from: Mailbox,
to: Mailbox,
totp_secret_base32: String,
seconds: u64,
) -> anyhow::Result<Message> {
let msg = Message::builder()
.from(from)
.to(to)
.subject(self.email_subject())
.singlepart(self.email_body(totp_secret_base32, seconds)?)?;
Ok(msg)
}
}
pub struct DefaultTotpMessageCN {
app_name: String,
use_html: bool,
}
impl DefaultTotpMessageCN {
pub const fn new(app_name: String, use_html: bool) -> Self {
DefaultTotpMessageCN { app_name, use_html }
}
}
impl TotpEmailMessage for DefaultTotpMessageCN {
fn email_subject(&self) -> String {
format!("验证码: {} 验证您的账户", self.app_name)
}
fn email_body(&self, totp_secret_base32: String, seconds: u64) -> anyhow::Result<SinglePart> {
let code = TOTP::default().with_step(seconds).gen_current(totp_secret_base32)?;
let duration = humantime::format_duration(Duration::from_secs(seconds)).to_string();
let app_name = &self.app_name;
if self.use_html {
const HTML: &str = include_str!("default_code_msg.html");
#[allow(clippy::literal_string_with_formatting_args)]
let body = HTML
.replace("{code}", &code)
.replace("{duration}", &duration)
.replace("{app_name}", app_name);
Ok(SinglePart::html(body))
} else {
let body = format!(
r###"
尊敬的用户,
您好!感谢您使用{app_name}。您的验证码是:
{code}
请在 {duration} 内输入该验证码以完成验证。
如果您没有请求此验证码,请忽略此邮件。为了确保您的账户安全,请勿将验证码透露给他人。
如有任何问题或需要进一步帮助,请联系我们的客服支持团队。
此致,
{app_name}团队
注意:本邮件为系统自动发送,请勿直接回复。
"###
);
Ok(SinglePart::plain(body))
}
}
}
pub fn display_body_plaintext(single_part: SinglePart) -> anyhow::Result<String> {
let Some(maybe_encoding) = single_part.headers().get::<ContentTransferEncoding>() else {
return Ok(String::from_utf8(single_part.formatted())?);
};
let content = str::from_utf8(single_part.raw_body())?;
let decoded_content = match maybe_encoding {
ContentTransferEncoding::Base64 => {
BASE64_STANDARD.decode(content.chars().filter(|c| !c.is_whitespace()).collect::<String>())?
}
ContentTransferEncoding::QuotedPrintable => quoted_printable::decode(content, ParseMode::Strict)?,
ContentTransferEncoding::SevenBit => todo!(),
ContentTransferEncoding::EightBit => todo!(),
ContentTransferEncoding::Binary => todo!(),
};
let decoded_string =
single_part.headers().to_string() + "\r\n" + str::from_utf8(decoded_content.as_slice())? + "\r\n";
Ok(decoded_string)
}
#[cfg(test)]
mod tests {
use super::*;
fn totp_email_msg(use_html: bool) {
let totp_msg = DefaultTotpMessageCN::new("Dipper".to_owned(), use_html);
let subject = totp_msg.email_subject();
let totp_secret_base32 = TOTP::gen_secret_base32();
let body = totp_msg.email_body(totp_secret_base32, 30 * 60).unwrap();
println!("subject={subject}\nbody={}", display_body_plaintext(body).unwrap());
}
#[test]
fn echo_current_plaintext() {
totp_email_msg(false)
}
#[test]
fn echo_current_html() {
totp_email_msg(true)
}
}