diff --git a/crates/api/src/local_user/reset_password.rs b/crates/api/src/local_user/reset_password.rs index 3c07b2e4d..b5325608d 100644 --- a/crates/api/src/local_user/reset_password.rs +++ b/crates/api/src/local_user/reset_password.rs @@ -5,6 +5,7 @@ use lemmy_api_common::{ person::{PasswordReset, PasswordResetResponse}, utils::send_password_reset_email, }; +use lemmy_db_schema::source::password_reset_request::PasswordResetRequest; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::error::LemmyError; @@ -25,6 +26,16 @@ impl Perform for PasswordReset { .await .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_that_username_or_email"))?; + // Check for too many attempts (to limit potential abuse) + let recent_resets_count = PasswordResetRequest::get_recent_password_resets_count( + context.pool(), + local_user_view.local_user.id, + ) + .await?; + if recent_resets_count >= 3 { + return Err(LemmyError::from_message("password_reset_limit_reached")); + } + // Email the pure token to the user. send_password_reset_email(&local_user_view, context.pool(), context.settings()).await?; Ok(PasswordResetResponse {}) diff --git a/crates/db_schema/src/impls/password_reset_request.rs b/crates/db_schema/src/impls/password_reset_request.rs index ebe8a00ec..85ad4cf01 100644 --- a/crates/db_schema/src/impls/password_reset_request.rs +++ b/crates/db_schema/src/impls/password_reset_request.rs @@ -1,6 +1,11 @@ use crate::{ newtypes::LocalUserId, - schema::password_reset_request::dsl::{password_reset_request, published, token_encrypted}, + schema::password_reset_request::dsl::{ + local_user_id, + password_reset_request, + published, + token_encrypted, + }, source::password_reset_request::{PasswordResetRequest, PasswordResetRequestForm}, traits::Crud, utils::{get_conn, DbPool}, @@ -74,6 +79,19 @@ impl PasswordResetRequest { .first::(conn) .await } + + pub async fn get_recent_password_resets_count( + pool: &DbPool, + user_id: LocalUserId, + ) -> Result { + let conn = &mut get_conn(pool).await?; + password_reset_request + .filter(local_user_id.eq(user_id)) + .filter(published.gt(now - 1.days())) + .count() + .get_result(conn) + .await + } } fn bytes_to_hex(bytes: Vec) -> String {