tuwunel_api/client/account/3pid/
email_token.rs1use std::{net::IpAddr, time::Duration};
2
3use ruma::{OwnedSessionId, thirdparty::Medium};
4use tuwunel_core::{Result, err, utils::html::escape as html_escape};
5use tuwunel_service::Services;
6use url::form_urlencoded;
7
8const PENDING_TTL: Duration = Duration::from_hours(1);
10
11pub(super) async fn send_email_token(
16 services: &Services,
17 client: IpAddr,
18 client_secret: &str,
19 email_canon: &str,
20 send_attempt: u64,
21) -> Result<OwnedSessionId> {
22 services.sendmail.check_address(email_canon)?;
23 services.threepid.check_ip_rate_limit(client)?;
24 services
25 .threepid
26 .check_address_rate_limit(email_canon)?;
27
28 let outcome = services
29 .threepid
30 .create_or_reuse_pending(
31 client_secret,
32 Medium::Email,
33 email_canon,
34 send_attempt,
35 PENDING_TTL,
36 )
37 .await?;
38
39 if let Some(token) = outcome.freshly_minted_token {
40 let link = validate_link(services, &outcome.sid, client_secret, &token)?;
41 let body = verification_html(&link);
42
43 services
44 .sendmail
45 .send_to(email_canon, "Verify your email address", body)
46 .await?;
47 }
48
49 outcome
50 .sid
51 .parse()
52 .map_err(|_| err!("Generated an invalid session id"))
53}
54
55fn validate_link(
56 services: &Services,
57 sid: &str,
58 client_secret: &str,
59 token: &str,
60) -> Result<String> {
61 let base = services
62 .config
63 .well_known
64 .client
65 .as_ref()
66 .map(ToString::to_string)
67 .ok_or_else(|| {
68 err!(Config(
69 "well_known.client",
70 "A public client base URL must be set to send email"
71 ))
72 })?;
73
74 let base = base.trim_end_matches('/');
75 let query = form_urlencoded::Serializer::new(String::new())
76 .append_pair("sid", sid)
77 .append_pair("client_secret", client_secret)
78 .append_pair("token", token)
79 .finish();
80
81 Ok(format!("{base}/_tuwunel/3pid/email/validate?{query}"))
82}
83
84fn verification_html(link: &str) -> String {
85 let link = html_escape(link);
86
87 format!(
88 "<!DOCTYPE html>
89<html lang=\"en\">
90 <body>
91 <h1>Verify your email address</h1>
92 <p>Open the link below to confirm this address.</p>
93 <p><a href=\"{link}\">{link}</a></p>
94 <p>If you did not request this, you can ignore this message.</p>
95 </body>
96</html>"
97 )
98}