rhombus 0.2.21

Next generation extendable CTF framework with batteries included
Documentation
use std::sync::Arc;

use async_trait::async_trait;
use lettre::{message::MultiPart, AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor};
use rand::{
    distributions::{Alphanumeric, DistString},
    thread_rng,
};
use tokio::sync::RwLock;

use crate::{
    internal::{email::provider::OutboundEmailProvider, settings::Settings},
    Result,
};

pub struct SmtpProvider {
    pub transport: AsyncSmtpTransport<Tokio1Executor>,
    pub settings: Arc<RwLock<Settings>>,
}

impl SmtpProvider {
    pub async fn new(settings: Arc<RwLock<Settings>>) -> Result<Self> {
        let connection_url = {
            settings
                .read()
                .await
                .email
                .as_ref()
                .unwrap()
                .smtp_connection_url
                .as_ref()
                .unwrap()
                .clone()
        };
        let transport = AsyncSmtpTransport::<Tokio1Executor>::from_url(&connection_url)?.build();
        Ok(Self {
            transport,
            settings,
        })
    }
}

#[async_trait]
impl OutboundEmailProvider for SmtpProvider {
    async fn send_email(
        &self,
        to: &str,
        subject: &str,
        plaintext: &str,
        html: &str,
        in_reply_to: Option<&str>,
        references: &[String],
    ) -> Result<String> {
        let from = {
            self.settings
                .read()
                .await
                .email
                .as_ref()
                .unwrap()
                .from
                .clone()
        };

        let message_id = format!("<{}@localhost>", create_message_id());

        let message = Message::builder()
            .from(from.parse()?)
            .to(to.parse()?)
            .subject(subject)
            .references(
                format!("{} {}", &message_id, references.join(" "))
                    .trim()
                    .to_string(),
            )
            .message_id(Some(message_id));

        let message = if let Some(in_reply_to) = in_reply_to {
            message.in_reply_to(in_reply_to.to_owned())
        } else {
            message
        };

        let message = message.multipart(MultiPart::alternative_plain_html(
            plaintext.to_owned(),
            html.to_owned(),
        ))?;

        let message_id = message.headers().get_raw("Message-ID").unwrap().to_owned();

        _ = self.transport.send(message).await?;

        Ok(message_id)
    }
}

pub fn create_message_id() -> String {
    Alphanumeric.sample_string(&mut thread_rng(), 36)
}