headless_lms_server/domain/
email_ownership_verification.rs1use chrono::Duration;
7use headless_lms_models::{
8 email_deliveries,
9 email_templates::{self, EmailTemplateType},
10 user_details, user_email_codes,
11 user_email_codes::UserEmailCodePurpose,
12};
13
14use crate::prelude::*;
15
16pub const FALLBACK_EMAIL_LANGUAGE: &str = "en";
18
19const MIN_RESEND_INTERVAL_MINUTES: i64 = 2;
21
22pub const MAX_CODE_ATTEMPTS: i32 = 5;
24
25const PURPOSE: UserEmailCodePurpose = UserEmailCodePurpose::EmailOwnershipVerification;
26
27#[derive(Debug, PartialEq, Eq, Clone, Copy)]
28pub enum VerificationEmailOutcome {
29 Queued,
30 AlreadyVerified,
31 RecentlySent,
33}
34
35pub async fn queue_verification_email(
40 conn: &mut PgConnection,
41 user_id: Uuid,
42 language: &str,
43) -> anyhow::Result<VerificationEmailOutcome> {
44 if user_details::get_email_verification(conn, user_id)
45 .await?
46 .is_some()
47 {
48 return Ok(VerificationEmailOutcome::AlreadyVerified);
49 }
50
51 let live_code =
54 user_email_codes::get_unused_user_email_code_with_user_id(conn, user_id, PURPOSE).await?;
55 if let Some(live_code) = live_code
56 && Utc::now() - live_code.created_at < Duration::minutes(MIN_RESEND_INTERVAL_MINUTES)
57 {
58 return Ok(VerificationEmailOutcome::RecentlySent);
59 }
60
61 let template = email_templates::get_generic_email_template_by_type_and_language(
63 conn,
64 EmailTemplateType::VerifyEmailAddress,
65 language,
66 )
67 .await
68 .map_err(|e| {
69 anyhow::anyhow!(
70 "Email ownership verification is enabled but the verify_email_address email template is missing: {}",
71 e.message()
72 )
73 })?;
74
75 let mut tx = conn.begin().await?;
76 let code = user_email_codes::generate_code();
77 user_email_codes::insert_user_email_code(&mut tx, user_id, PURPOSE, &code).await?;
78 email_deliveries::insert_email_delivery(&mut tx, user_id, template.id).await?;
81 tx.commit().await?;
82
83 Ok(VerificationEmailOutcome::Queued)
84}
85
86pub async fn queue_verification_email_best_effort(
92 conn: &mut PgConnection,
93 enabled: bool,
94 user_id: Uuid,
95) {
96 if !enabled {
97 return;
98 }
99 match queue_verification_email(conn, user_id, FALLBACK_EMAIL_LANGUAGE).await {
100 Ok(outcome) => {
101 info!("Email ownership verification mail for {user_id}: {outcome:?}");
102 }
103 Err(e) => {
104 error!("Failed to queue email ownership verification mail for {user_id}: {e}");
105 }
106 }
107}