diff --git a/crates/api/src/local_user.rs b/crates/api/src/local_user.rs index f4686d814..e4878683a 100644 --- a/crates/api/src/local_user.rs +++ b/crates/api/src/local_user.rs @@ -10,6 +10,9 @@ use lemmy_api_common::{ is_admin, password_length_check, person::*, + send_email_verification_success, + send_password_reset_email, + send_verification_email, }; use lemmy_db_schema::{ diesel_option_overwrite, @@ -47,14 +50,12 @@ use lemmy_db_views_actor::{ }; use lemmy_utils::{ claims::Claims, - email::send_email, location_info, - utils::{generate_random_string, is_valid_display_name, is_valid_matrix_id, naive_from_unix}, + utils::{is_valid_display_name, is_valid_matrix_id, naive_from_unix}, ConnectionId, LemmyError, }; use lemmy_websocket::{ - email::send_verification_email, messages::{CaptchaItem, SendAllMessage}, LemmyContext, UserOperation, @@ -96,6 +97,10 @@ impl Perform for Login { return Err(LemmyError::from_message("email_not_verified")); } + if site.require_application && !local_user_view.local_user.accepted_application { + return Err(LemmyError::from_message("registration_application_pending")); + } + // Return the jwt Ok(LoginResponse { jwt: Some( @@ -187,7 +192,8 @@ impl Perform for SaveUserSettings { local_user_view.local_user.id, email, &local_user_view.person.name, - context, + context.pool(), + &context.settings(), ) .await?; None @@ -774,31 +780,8 @@ impl Perform for PasswordReset { .map_err(LemmyError::from) .map_err(|e| e.with_message("couldnt_find_that_username_or_email"))?; - // Generate a random token - let token = generate_random_string(); - - // Insert the row - let token2 = token.clone(); - let local_user_id = local_user_view.local_user.id; - blocking(context.pool(), move |conn| { - PasswordResetRequest::create_token(conn, local_user_id, &token2) - }) - .await??; - // Email the pure token to the user. - // TODO no i18n support here. - let email = &local_user_view.local_user.email.expect("email"); - let subject = &format!("Password reset for {}", local_user_view.person.name); - let protocol_and_hostname = &context.settings().get_protocol_and_hostname(); - let html = &format!("

Password Reset Request for {}


Click here to reset your password", local_user_view.person.name, protocol_and_hostname, &token); - send_email( - subject, - email, - &local_user_view.person.name, - html, - &context.settings(), - )?; - + send_password_reset_email(&local_user_view, context.pool(), &context.settings()).await?; Ok(PasswordResetResponse {}) } } @@ -963,6 +946,13 @@ impl Perform for VerifyEmail { }) .await??; + let local_user_view = blocking(context.pool(), move |conn| { + LocalUserView::read(conn, local_user_id) + }) + .await??; + + send_email_verification_success(&local_user_view, &context.settings())?; + Ok(VerifyEmailResponse {}) } } diff --git a/crates/api/src/site.rs b/crates/api/src/site.rs index c7eaf633d..c653a212c 100644 --- a/crates/api/src/site.rs +++ b/crates/api/src/site.rs @@ -9,6 +9,7 @@ use lemmy_api_common::{ get_local_user_view_from_jwt, get_local_user_view_from_jwt_opt, is_admin, + send_application_approved_email, site::*, }; use lemmy_apub::{ @@ -656,6 +657,14 @@ impl Perform for ApproveRegistrationApplication { }) .await??; + let require_email_verification = blocking(context.pool(), Site::read_simple) + .await?? + .require_email_verification; + + if require_email_verification && data.approve { + send_application_approved_email(&local_user_view, &context.settings())?; + } + // Read the view let registration_application = blocking(context.pool(), move |conn| { RegistrationApplicationView::read(conn, app_id) diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs index 614c75151..94e7b1d8e 100644 --- a/crates/api_common/src/lib.rs +++ b/crates/api_common/src/lib.rs @@ -10,6 +10,8 @@ use lemmy_db_schema::{ newtypes::{CommunityId, LocalUserId, PersonId, PostId}, source::{ community::Community, + email_verification::{EmailVerification, EmailVerificationForm}, + password_reset_request::PasswordResetRequest, person_block::PersonBlock, post::{Post, PostRead, PostReadForm}, secret::Secret, @@ -23,7 +25,14 @@ use lemmy_db_views_actor::{ community_person_ban_view::CommunityPersonBanView, community_view::CommunityView, }; -use lemmy_utils::{claims::Claims, settings::structs::FederationConfig, LemmyError, Sensitive}; +use lemmy_utils::{ + claims::Claims, + email::send_email, + settings::structs::{FederationConfig, Settings}, + utils::generate_random_string, + LemmyError, + Sensitive, +}; use url::Url; pub async fn blocking(pool: &DbPool, f: F) -> Result @@ -333,3 +342,122 @@ pub fn honeypot_check(honeypot: &Option) -> Result<(), LemmyError> { Ok(()) } } + +pub fn send_email_to_user( + local_user_view: &LocalUserView, + subject_text: &str, + body_text: &str, + comment_content: &str, + settings: &Settings, +) { + if local_user_view.person.banned || !local_user_view.local_user.send_notifications_to_email { + return; + } + + if let Some(user_email) = &local_user_view.local_user.email { + let subject = &format!( + "{} - {} {}", + subject_text, settings.hostname, local_user_view.person.name, + ); + let html = &format!( + "

{}


{} - {}

inbox", + body_text, + local_user_view.person.name, + comment_content, + settings.get_protocol_and_hostname() + ); + match send_email( + subject, + user_email, + &local_user_view.person.name, + html, + settings, + ) { + Ok(_o) => _o, + Err(e) => tracing::error!("{}", e), + }; + } +} + +pub async fn send_password_reset_email( + local_user_view: &LocalUserView, + pool: &DbPool, + settings: &Settings, +) -> Result<(), LemmyError> { + // Generate a random token + let token = generate_random_string(); + + // Insert the row + let token2 = token.clone(); + let local_user_id = local_user_view.local_user.id; + blocking(pool, move |conn| { + PasswordResetRequest::create_token(conn, local_user_id, &token2) + }) + .await??; + + let email = &local_user_view.local_user.email.to_owned().expect("email"); + let subject = &format!("Password reset for {}", local_user_view.person.name); + let protocol_and_hostname = settings.get_protocol_and_hostname(); + let html = &format!("

Password Reset Request for {}


Click here to reset your password", local_user_view.person.name, protocol_and_hostname, &token); + send_email(subject, email, &local_user_view.person.name, html, settings) +} + +/// Send a verification email +pub async fn send_verification_email( + local_user_id: LocalUserId, + new_email: &str, + username: &str, + pool: &DbPool, + settings: &Settings, +) -> Result<(), LemmyError> { + let form = EmailVerificationForm { + local_user_id, + email: new_email.to_string(), + verification_token: generate_random_string(), + }; + let verify_link = format!( + "{}/verify_email/{}", + settings.get_protocol_and_hostname(), + &form.verification_token + ); + blocking(pool, move |conn| EmailVerification::create(conn, &form)).await??; + + let subject = format!("Verify your email address for {}", settings.hostname); + let body = format!( + concat!( + "Please click the link below to verify your email address ", + "for the account @{}@{}. Ignore this email if the account isn't yours.\n\n", + "" + ), + username, settings.hostname, verify_link + ); + send_email(&subject, new_email, username, &body, settings)?; + + Ok(()) +} + +pub fn send_email_verification_success( + local_user_view: &LocalUserView, + settings: &Settings, +) -> Result<(), LemmyError> { + let email = &local_user_view.local_user.email.to_owned().expect("email"); + let subject = &format!("Email verified for {}", local_user_view.person.name); + let html = "Your email has been verified."; + send_email(subject, email, &local_user_view.person.name, html, settings) +} + +pub fn send_application_approved_email( + local_user_view: &LocalUserView, + settings: &Settings, +) -> Result<(), LemmyError> { + let email = &local_user_view.local_user.email.to_owned().expect("email"); + let subject = &format!( + "Registration to {} approved for {}", + settings.hostname, local_user_view.person.name + ); + let html = &format!( + "Your registration application has been approved. Welcome to {}!", + settings.hostname + ); + send_email(subject, email, &local_user_view.person.name, html, settings) +} diff --git a/crates/api_crud/src/private_message/create.rs b/crates/api_crud/src/private_message/create.rs index ae75d9eaf..54edc2408 100644 --- a/crates/api_crud/src/private_message/create.rs +++ b/crates/api_crud/src/private_message/create.rs @@ -5,6 +5,7 @@ use lemmy_api_common::{ check_person_block, get_local_user_view_from_jwt, person::{CreatePrivateMessage, PrivateMessageResponse}, + send_email_to_user, }; use lemmy_apub::{ generate_local_apub_endpoint, @@ -20,11 +21,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::local_user_view::LocalUserView; use lemmy_utils::{utils::remove_slurs, ConnectionId, LemmyError}; -use lemmy_websocket::{ - send::{send_email_to_user, send_pm_ws_message}, - LemmyContext, - UserOperationCrud, -}; +use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud}; #[async_trait::async_trait(?Send)] impl PerformCrud for CreatePrivateMessage { diff --git a/crates/api_crud/src/user/create.rs b/crates/api_crud/src/user/create.rs index 006aeacf5..2160661ac 100644 --- a/crates/api_crud/src/user/create.rs +++ b/crates/api_crud/src/user/create.rs @@ -1,6 +1,12 @@ use crate::PerformCrud; use actix_web::web::Data; -use lemmy_api_common::{blocking, honeypot_check, password_length_check, person::*}; +use lemmy_api_common::{ + blocking, + honeypot_check, + password_length_check, + person::*, + send_verification_email, +}; use lemmy_apub::{ generate_followers_url, generate_inbox_url, @@ -34,7 +40,7 @@ use lemmy_utils::{ ConnectionId, LemmyError, }; -use lemmy_websocket::{email::send_verification_email, messages::CheckCaptcha, LemmyContext}; +use lemmy_websocket::{messages::CheckCaptcha, LemmyContext}; #[async_trait::async_trait(?Send)] impl PerformCrud for Register { @@ -258,7 +264,8 @@ impl PerformCrud for Register { // we check at the beginning of this method that email is set &inserted_local_user.email.expect("email was provided"), &inserted_person.name, - context, + context.pool(), + &context.settings(), ) .await?; LoginResponse { diff --git a/crates/websocket/src/email.rs b/crates/websocket/src/email.rs deleted file mode 100644 index 21b003de7..000000000 --- a/crates/websocket/src/email.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::LemmyContext; -use lemmy_api_common::blocking; -use lemmy_db_schema::{ - newtypes::LocalUserId, - source::email_verification::{EmailVerification, EmailVerificationForm}, - traits::Crud, -}; -use lemmy_utils::{email::send_email, utils::generate_random_string, LemmyError}; - -pub async fn send_verification_email( - local_user_id: LocalUserId, - new_email: &str, - username: &str, - context: &LemmyContext, -) -> Result<(), LemmyError> { - let settings = context.settings(); - let form = EmailVerificationForm { - local_user_id, - email: new_email.to_string(), - verification_token: generate_random_string(), - }; - // TODO: link should be replaced with a frontend route once that exists - let verify_link = format!( - "{}/api/v3/user/verify_email?token={}", - settings.get_protocol_and_hostname(), - &form.verification_token - ); - blocking(context.pool(), move |conn| { - EmailVerification::create(conn, &form) - }) - .await??; - - let subject = format!("Verify your email address for {}", settings.hostname); - let body = format!( - concat!( - "Please click the link below to verify your email address ", - "for the account @{}@{}. Ignore this email if the account isn't yours.\n\n", - "" - ), - username, settings.hostname, verify_link - ); - send_email(&subject, new_email, username, &body, &context.settings())?; - - Ok(()) -} diff --git a/crates/websocket/src/lib.rs b/crates/websocket/src/lib.rs index 3035dfe27..cf2f6a257 100644 --- a/crates/websocket/src/lib.rs +++ b/crates/websocket/src/lib.rs @@ -10,7 +10,6 @@ use reqwest::Client; use serde::Serialize; pub mod chat_server; -pub mod email; pub mod handlers; pub mod messages; pub mod routes; diff --git a/crates/websocket/src/send.rs b/crates/websocket/src/send.rs index ac0752c64..e7f265b50 100644 --- a/crates/websocket/src/send.rs +++ b/crates/websocket/src/send.rs @@ -10,6 +10,7 @@ use lemmy_api_common::{ community::CommunityResponse, person::PrivateMessageResponse, post::PostResponse, + send_email_to_user, }; use lemmy_db_schema::{ newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId, PrivateMessageId}, @@ -28,14 +29,7 @@ use lemmy_db_views::{ private_message_view::PrivateMessageView, }; use lemmy_db_views_actor::community_view::CommunityView; -use lemmy_utils::{ - email::send_email, - settings::structs::Settings, - utils::MentionData, - ConnectionId, - LemmyError, -}; -use tracing::error; +use lemmy_utils::{utils::MentionData, ConnectionId, LemmyError}; pub async fn send_post_ws_message( post_id: PostId, @@ -296,39 +290,3 @@ pub async fn send_local_notifs( }; Ok(recipient_ids) } - -pub fn send_email_to_user( - local_user_view: &LocalUserView, - subject_text: &str, - body_text: &str, - comment_content: &str, - settings: &Settings, -) { - if local_user_view.person.banned || !local_user_view.local_user.send_notifications_to_email { - return; - } - - if let Some(user_email) = &local_user_view.local_user.email { - let subject = &format!( - "{} - {} {}", - subject_text, settings.hostname, local_user_view.person.name, - ); - let html = &format!( - "

{}


{} - {}

inbox", - body_text, - local_user_view.person.name, - comment_content, - settings.get_protocol_and_hostname() - ); - match send_email( - subject, - user_email, - &local_user_view.person.name, - html, - settings, - ) { - Ok(_o) => _o, - Err(e) => error!("{}", e), - }; - } -}