# verification.rs - source
Source of the Rust file `src/email/verification.rs`.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129

    // Copyright (C) 2022  Aravinth Manivannan <realaravinth@batsense.net>
    // SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
    //
    // SPDX-License-Identifier: AGPL-3.0-or-later

    //! Email operations: verification, notification, etc
    use lettre::{
        message::{header, MultiPart, SinglePart},
        AsyncTransport, Message,
    };
    use sailfish::TemplateOnce;

    use crate::errors::*;
    use crate::Data;

    const PAGE: &str = "Login";

    #[derive(Clone, TemplateOnce)]
    #[template(path = "email/verification/index.html")]
    struct IndexPage<'a> {
        verification_link: &'a str,
    }

    impl<'a> IndexPage<'a> {
        fn new(verification_link: &'a str) -> Self {
            Self { verification_link }
        }
    }

    async fn verification(
        data: &Data,
        to: &str,
        verification_link: &str,
    ) -> ServiceResult<()> {
        if let Some(smtp) = data.settings.smtp.as_ref() {
            let from = format!("mCaptcha Admin <{}>", smtp.from);
            let reply_to = format!("mCaptcha Admin <{}>", smtp.reply);
            const SUBJECT: &str = "[mCaptcha] Please verify your email";

            let plain_text = format!(
                "
    Welcome to mCaptcha!

    Please verify your email address to continue.

    VERIFICATION LINK: {}

    Please ignore this email if you weren't expecting it.

    With best regards,
    Admin
    instance: {}
    project website: {}",
                verification_link,
                &data.settings.server.domain,
                crate::PKG_HOMEPAGE
            );

            let html = IndexPage::new(verification_link).render_once().unwrap();

            let email = Message::builder()
                .from(from.parse().unwrap())
                .reply_to(reply_to.parse().unwrap())
                .to(to.parse().unwrap())
                .subject(SUBJECT)
                .multipart(
                    MultiPart::alternative() // This is composed of two parts.
                        .singlepart(
                            SinglePart::builder()
                                .header(header::ContentType::TEXT_PLAIN)
                                .body(plain_text), // Every message should have a plain text fallback.
                        )
                        .singlepart(
                            SinglePart::builder()
                                .header(header::ContentType::TEXT_HTML)
                                .body(html),
                        ),
                )
                .unwrap();

            data.mailer.as_ref().unwrap().send(email).await?;
        }
        Ok(())
    }

    #[cfg(test)]
    mod tests {
        use super::*;

        use awc::Client;

        #[actix_rt::test]
        async fn email_verification_works_pg() {
            let data = crate::tests::pg::get_data().await;
            email_verification_works(data).await;
        }

        #[actix_rt::test]
        async fn email_verification_works_maria() {
            let data = crate::tests::maria::get_data().await;
            email_verification_works(data).await;
        }

        async fn email_verification_works(data: crate::ArcData) {
            const TO_ADDR: &str = "Hello <realaravinth@localhost>";
            const VERIFICATION_LINK: &str = "https://localhost";
            let settings = &data.settings;
            verification(&data, TO_ADDR, VERIFICATION_LINK)
                .await
                .unwrap();

            let client = Client::default();
            let mut resp = client
                .get("http://localhost:1080/email")
                .send()
                .await
                .unwrap();
            let data: serde_json::Value = resp.json().await.unwrap();
            let data = &data[0];
            let smtp = settings.smtp.as_ref().unwrap();

            let from_addr = &data["headers"]["from"];

            assert!(from_addr.to_string().contains(&smtp.from));

            let body = &data["html"];
            assert!(body.to_string().contains(VERIFICATION_LINK));
        }
    }