- Add EmailTokenService for handling email verification and password reset tokens. - Create EmailService for sending verification and reset emails via SMTP. - Update AuthService to handle email verification status during login. - Modify user registration to redirect to a check email screen instead of issuing a token. - Implement resend verification email functionality. - Add deep link handling for email verification and password reset in the mobile app. - Update mobile app routes and components to support new email verification flow. - Enhance error handling for unverified emails during login attempts. - Update configuration to include SMTP settings for email service.
68 lines
2.4 KiB
Rust
68 lines
2.4 KiB
Rust
use lettre::{
|
|
message::header::ContentType,
|
|
transport::smtp::authentication::Credentials,
|
|
AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor,
|
|
};
|
|
|
|
use crate::{config::Config, error::AppError};
|
|
|
|
pub struct EmailService {
|
|
mailer: AsyncSmtpTransport<Tokio1Executor>,
|
|
from: String,
|
|
}
|
|
|
|
impl EmailService {
|
|
pub fn new(config: &Config) -> Result<Self, AppError> {
|
|
let creds = Credentials::new(config.smtp_user.clone(), config.smtp_pass.clone());
|
|
|
|
let mailer = AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(&config.smtp_host)
|
|
.map_err(|e| AppError::Internal(anyhow::anyhow!("SMTP config error: {e}")))?
|
|
.port(config.smtp_port)
|
|
.credentials(creds)
|
|
.build();
|
|
|
|
Ok(Self {
|
|
mailer,
|
|
from: config.email_from.clone(),
|
|
})
|
|
}
|
|
|
|
pub async fn send_password_reset(&self, to: &str, reset_link: &str) -> Result<(), AppError> {
|
|
let body = format!(
|
|
"Você solicitou a redefinição de senha do MeowSpool.\n\nClique no link abaixo para redefinir sua senha (válido por 1 hora):\n\n{reset_link}\n\nSe você não solicitou isso, ignore este email."
|
|
);
|
|
|
|
self.send(to, "Redefinição de senha — MeowSpool", &body).await
|
|
}
|
|
|
|
pub async fn send_email_verification(&self, to: &str, verify_link: &str) -> Result<(), AppError> {
|
|
let body = format!(
|
|
"Bem-vindo ao MeowSpool! Confirme seu endereço de email clicando no link abaixo:\n\n{verify_link}\n\nO link expira em 24 horas."
|
|
);
|
|
|
|
self.send(to, "Confirme seu email — MeowSpool", &body).await
|
|
}
|
|
|
|
async fn send(&self, to: &str, subject: &str, body: &str) -> Result<(), AppError> {
|
|
let email = Message::builder()
|
|
.from(
|
|
self.from
|
|
.parse()
|
|
.map_err(|e| AppError::Internal(anyhow::anyhow!("Invalid from address: {e}")))?,
|
|
)
|
|
.to(to
|
|
.parse()
|
|
.map_err(|e| AppError::Internal(anyhow::anyhow!("Invalid to address: {e}")))?)
|
|
.subject(subject)
|
|
.header(ContentType::TEXT_PLAIN)
|
|
.body(body.to_string())
|
|
.map_err(|e| AppError::Internal(anyhow::anyhow!("Email build error: {e}")))?;
|
|
|
|
AsyncTransport::send(&self.mailer, email)
|
|
.await
|
|
.map_err(|e| AppError::Internal(anyhow::anyhow!("SMTP send error: {e}")))?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|