From ea8bc89483c196ef2b807fe5800ca4764cc3d1f7 Mon Sep 17 00:00:00 2001 From: Valentin Tolmer Date: Sun, 21 Nov 2021 18:56:16 +0100 Subject: [PATCH] server: Send an email for password resets --- server/src/infra/auth_service.rs | 20 +++++++++++++++-- server/src/infra/mail.rs | 37 +++++++++++++++++++++++++++++--- server/src/infra/tcp_server.rs | 25 +++++++++++++++++++-- 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/server/src/infra/auth_service.rs b/server/src/infra/auth_service.rs index 25e871d..80c11ea 100644 --- a/server/src/infra/auth_service.rs +++ b/server/src/infra/auth_service.rs @@ -23,6 +23,7 @@ use futures_util::{FutureExt, TryFutureExt}; use hmac::Hmac; use jwt::{SignWithKey, VerifyWithKey}; use lldap_auth::{login, registration, JWTClaims}; +use log::*; use sha2::Sha512; use std::collections::{hash_map::DefaultHasher, HashSet}; use std::hash::{Hash, Hasher}; @@ -123,12 +124,27 @@ where None => return HttpResponse::BadRequest().body("Missing user ID"), Some(id) => id, }; - let _token = match data.backend_handler.start_password_reset(user_id).await { + let token = match data.backend_handler.start_password_reset(user_id).await { Err(e) => return HttpResponse::InternalServerError().body(e.to_string()), Ok(None) => return HttpResponse::Ok().finish(), Ok(Some(token)) => token, }; - // TODO: Send email. + let user = match data.backend_handler.get_user_details(user_id).await { + Err(e) => { + warn!("Error getting used details: {:#?}", e); + return HttpResponse::Ok().finish(); + } + Ok(u) => u, + }; + if let Err(e) = super::mail::send_password_reset_email( + &user.display_name, + &user.email, + &token, + &data.server_url, + &data.mail_options, + ) { + warn!("Error sending email: {:#?}", e); + } HttpResponse::Ok().finish() } diff --git a/server/src/infra/mail.rs b/server/src/infra/mail.rs index 7ecb343..2bb9a11 100644 --- a/server/src/infra/mail.rs +++ b/server/src/infra/mail.rs @@ -6,7 +6,7 @@ use lettre::{ }; use log::debug; -pub fn send_test_email(to: Mailbox, options: &MailOptions) -> Result<()> { +fn send_email(to: Mailbox, subject: &str, body: String, options: &MailOptions) -> Result<()> { let from = options .from .clone() @@ -20,8 +20,8 @@ pub fn send_test_email(to: Mailbox, options: &MailOptions) -> Result<()> { .from(from) .reply_to(reply_to) .to(to) - .subject("LLDAP test email") - .body("The test is successful! You can send emails from LLDAP".to_string())?; + .subject(subject) + .body(body)?; let creds = Credentials::new( options.user.clone(), options.password.unsecure().to_string(), @@ -32,3 +32,34 @@ pub fn send_test_email(to: Mailbox, options: &MailOptions) -> Result<()> { mailer.send(&email)?; Ok(()) } + +pub fn send_password_reset_email( + username: &str, + to: &str, + token: &str, + domain: &str, + options: &MailOptions, +) -> Result<()> { + let to = to.parse()?; + let body = format!( + "Hello {}, +This email has been sent to you in order to validate your identity. +If you did not initiate the process your credentials might have been +compromised. You should reset your password and contact an administrator. + +To reset your password please visit the following URL: {}/reset-password/step2/{} + +Please contact an administrator if you did not initiate the process.", + username, domain, token + ); + send_email(to, "[LLDAP] Password reset requested", body, options) +} + +pub fn send_test_email(to: Mailbox, options: &MailOptions) -> Result<()> { + send_email( + to, + "LLDAP test email", + "The test is successful! You can send emails from LLDAP".to_string(), + options, + ) +} diff --git a/server/src/infra/tcp_server.rs b/server/src/infra/tcp_server.rs index 1f45816..36231f8 100644 --- a/server/src/infra/tcp_server.rs +++ b/server/src/infra/tcp_server.rs @@ -4,7 +4,11 @@ use crate::{ handler::{BackendHandler, LoginHandler}, opaque_handler::OpaqueHandler, }, - infra::{auth_service, configuration::Configuration, tcp_backend_handler::*}, + infra::{ + auth_service, + configuration::{Configuration, MailOptions}, + tcp_backend_handler::*, + }, }; use actix_files::{Files, NamedFile}; use actix_http::HttpServiceBuilder; @@ -46,6 +50,8 @@ fn http_config( backend_handler: Backend, jwt_secret: secstr::SecUtf8, jwt_blacklist: HashSet, + server_url: String, + mail_options: MailOptions, ) where Backend: TcpBackendHandler + BackendHandler + LoginHandler + OpaqueHandler + Sync + 'static, { @@ -53,6 +59,8 @@ fn http_config( backend_handler, jwt_key: Hmac::new_varkey(jwt_secret.unsecure().as_bytes()).unwrap(), jwt_blacklist: RwLock::new(jwt_blacklist), + server_url, + mail_options, })) // Serve index.html and main.js, and default to index.html. .route( @@ -76,6 +84,8 @@ pub(crate) struct AppState { pub backend_handler: Backend, pub jwt_key: Hmac, pub jwt_blacklist: RwLock>, + pub server_url: String, + pub mail_options: MailOptions, } pub async fn build_tcp_server( @@ -91,15 +101,26 @@ where .get_jwt_blacklist() .await .context("while getting the jwt blacklist")?; + let server_url = config.http_url.clone(); + let mail_options = config.smtp_options.clone(); server_builder .bind("http", ("0.0.0.0", config.http_port), move || { let backend_handler = backend_handler.clone(); let jwt_secret = jwt_secret.clone(); let jwt_blacklist = jwt_blacklist.clone(); + let server_url = server_url.clone(); + let mail_options = mail_options.clone(); HttpServiceBuilder::new() .finish(map_config( App::new().configure(move |cfg| { - http_config(cfg, backend_handler, jwt_secret, jwt_blacklist) + http_config( + cfg, + backend_handler, + jwt_secret, + jwt_blacklist, + server_url, + mail_options, + ) }), |_| AppConfig::default(), ))