Skip to main content

sova_auth/
mail.rs

1//! Mail helpers for verify / reset ([`Mailable`](sova_mail::Mailable) + templates).
2
3use sova_core::{Request, Result};
4use sova_mail::{Content, Envelope, MailExt, Mailable};
5#[cfg(feature = "templates")]
6use serde_json::json;
7
8/// Email-verification message.
9pub struct VerifyEmailMail {
10    pub link: String,
11    /// Prefer MiniJinja `mail/verify.html` when Templates is installed.
12    pub(crate) prefer_view: bool,
13}
14
15impl VerifyEmailMail {
16    pub fn new(link: impl Into<String>) -> Self {
17        Self {
18            link: link.into(),
19            prefer_view: true,
20        }
21    }
22}
23
24impl Mailable for VerifyEmailMail {
25    fn envelope(&self) -> Envelope {
26        Envelope::new("Verify your email")
27    }
28
29    fn content(&self) -> Content {
30        let text = format!("Verify your email:\n\n{}\n", self.link);
31        let html = format!(
32            "<p>Verify your email:</p><p><a href=\"{0}\">{0}</a></p>",
33            self.link
34        );
35        #[cfg(feature = "templates")]
36        if self.prefer_view {
37            return Content::view_with_text(
38                "mail/verify.html",
39                json!({ "link": self.link }),
40                text,
41            );
42        }
43        Content::html_with_text(html, text)
44    }
45}
46
47/// Password-reset message.
48pub struct ResetPasswordMail {
49    pub link: String,
50    pub(crate) prefer_view: bool,
51}
52
53impl ResetPasswordMail {
54    pub fn new(link: impl Into<String>) -> Self {
55        Self {
56            link: link.into(),
57            prefer_view: true,
58        }
59    }
60}
61
62impl Mailable for ResetPasswordMail {
63    fn envelope(&self) -> Envelope {
64        Envelope::new("Reset your password")
65    }
66
67    fn content(&self) -> Content {
68        let text = format!("Reset your password:\n\n{}\n", self.link);
69        let html = format!(
70            "<p>Reset your password:</p><p><a href=\"{0}\">{0}</a></p>",
71            self.link
72        );
73        #[cfg(feature = "templates")]
74        if self.prefer_view {
75            return Content::view_with_text(
76                "mail/reset.html",
77                json!({ "link": self.link }),
78                text,
79            );
80        }
81        Content::html_with_text(html, text)
82    }
83}
84
85fn templates_ready(req: &Request) -> bool {
86    #[cfg(feature = "templates")]
87    {
88        req.try_state::<sova_templates::MiniJinjaTemplates>()
89            .is_some()
90    }
91    #[cfg(not(feature = "templates"))]
92    {
93        let _ = req;
94        false
95    }
96}
97
98pub async fn send_verify(req: &Request, to: &str, link: &str) -> Result<()> {
99    let mut mail = VerifyEmailMail::new(link);
100    mail.prefer_view = templates_ready(req);
101    req.mail().to(to).send_mail(mail).await
102}
103
104pub async fn send_reset(req: &Request, to: &str, link: &str) -> Result<()> {
105    let mut mail = ResetPasswordMail::new(link);
106    mail.prefer_view = templates_ready(req);
107    req.mail().to(to).send_mail(mail).await
108}