diff --git a/src/config/GeneralConfig.php b/src/config/GeneralConfig.php index 9654c10756c..24b2ad4681c 100644 --- a/src/config/GeneralConfig.php +++ b/src/config/GeneralConfig.php @@ -2026,6 +2026,20 @@ class GeneralConfig extends BaseConfig */ public string $partialTemplatesPath = '_partials'; + /** + * @var int The duration in seconds of the cooldown timer after a reset password mail has been sent. + * + * Prevents other from exploiting the reset password functionality by applying a phase within it is not possible to + * trigger another password reset mail. Set the duration to zero to disable the cooldown. + * + * ::: code + * ```php Static Config + * ->passwordResetCooldownDuration(300) + * ``` + * ::: + */ + public int $passwordResetCooldownDuration = 60; + /** * @var string|null The query string param that Craft will check when determining the request’s path. * @@ -5553,6 +5567,27 @@ public function pageTrigger(string $value): self return $this; } + + /** + * The duration in seconds of the cooldown timer after a reset password mail has been sent. + * + * Prevents other from exploiting the reset password functionality by applying a phase within it is not possible to + * trigger another password reset mail. Set the duration to zero to disable the cooldown. + * + * ```php + * ->passwordResetCooldownDuration(300) + * ``` + * + * @param int $value + * @return self + * @see $passwordResetCooldownDuration + */ + public function passwordResetCooldownDuration(int $value): self + { + $this->passwordResetCooldownDuration = $value; + return $this; + } + /** * The path within the `templates` folder where element partial templates will live. * diff --git a/src/services/Users.php b/src/services/Users.php index 48adf33716a..2c2fde4d267 100644 --- a/src/services/Users.php +++ b/src/services/Users.php @@ -485,6 +485,24 @@ public function sendNewEmailVerifyEmail(User $user): bool */ public function sendPasswordResetEmail(User $user): bool { + $cooldown = Craft::$app->getConfig()->getGeneral()->passwordResetCooldownDuration; + if ($cooldown > 0) { + try { + $userRecord = $this->_getUserRecordById($user->id); + $issuedAtRaw = $userRecord->verificationCodeIssuedDate; + $issuedAt = $issuedAtRaw ? new DateTime($issuedAtRaw, new DateTimeZone('UTC')) : null; + } catch (\Throwable) { + return false; + } + + if ($issuedAt) { + $elapsed = time() - $issuedAt->getTimestamp(); + if ($elapsed < $cooldown) { + return false; + } + } + } + $url = $this->getPasswordResetUrl($user); return Craft::$app->getMailer()