ResetPassword: add time-out to password resets; prevent repeated mails
This commit is contained in:
		
							parent
							
								
									eb7a40a70d
								
							
						
					
					
						commit
						f511e678ca
					
				@ -37,6 +37,24 @@ class ResetPassword extends HTMLController
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (Authentication::getResetTimeOut($user->getUserId()) > 0)
 | 
			
		||||
			{
 | 
			
		||||
				// Update the reset time-out to prevent hammering
 | 
			
		||||
				$resetTimeOut = Authentication::updateResetTimeOut($user->getUserId());
 | 
			
		||||
 | 
			
		||||
				// Present it to the user in a readable way
 | 
			
		||||
				if ($resetTimeOut > 3600)
 | 
			
		||||
					$timeOut = sprintf('%d hours', ceil($resetTimeOut / 3600));
 | 
			
		||||
				elseif ($resetTimeOut > 60)
 | 
			
		||||
					$timeOut = sprintf('%d minutes', ceil($resetTimeOut / 60));
 | 
			
		||||
				else
 | 
			
		||||
					$timeOut = sprintf('%d seconds', $resetTimeOut);
 | 
			
		||||
 | 
			
		||||
				$form->adopt(new Alert('Password reset token already sent', 'We already sent a password reset token to this email address recently. ' .
 | 
			
		||||
					'If no email was received, please wait ' . $timeOut . ' to try again.', 'error'));
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Authentication::setResetKey($user->getUserId());
 | 
			
		||||
			Email::resetMail($user->getUserId());
 | 
			
		||||
 | 
			
		||||
@ -76,6 +94,10 @@ class ResetPassword extends HTMLController
 | 
			
		||||
			if (empty($missing))
 | 
			
		||||
			{
 | 
			
		||||
				Authentication::updatePassword($user->getUserId(), Authentication::computeHash($_POST['password1']));
 | 
			
		||||
 | 
			
		||||
				// Consume token, ensuring it isn't used again
 | 
			
		||||
				Authentication::consumeResetKey($user->getUserId());
 | 
			
		||||
 | 
			
		||||
				$_SESSION['login_msg'] = ['Your password has been reset', 'You can now use the form below to log in to your account.', 'success'];
 | 
			
		||||
				header('Location: ' . BASEURL . '/login/');
 | 
			
		||||
				exit;
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,8 @@
 | 
			
		||||
 */
 | 
			
		||||
class Authentication
 | 
			
		||||
{
 | 
			
		||||
	const DEFAULT_RESET_TIMEOUT = 30;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Checks a password for a given username against the database.
 | 
			
		||||
	 */
 | 
			
		||||
@ -57,6 +59,27 @@ class Authentication
 | 
			
		||||
		return $hash;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static function consumeResetKey($id_user)
 | 
			
		||||
	{
 | 
			
		||||
		return Registry::get('db')->query('
 | 
			
		||||
			UPDATE users
 | 
			
		||||
			SET reset_key = NULL,
 | 
			
		||||
				reset_blocked_until = NULL
 | 
			
		||||
			WHERE id_user = {int:id_user}',
 | 
			
		||||
			['id_user' => $id_user]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static function getResetTimeOut($id_user)
 | 
			
		||||
	{
 | 
			
		||||
		$resetTime = Registry::get('db')->queryValue('
 | 
			
		||||
			SELECT reset_blocked_until
 | 
			
		||||
			FROM users
 | 
			
		||||
			WHERE id_user = {int:id_user}',
 | 
			
		||||
			['id_user' => $id_user]);
 | 
			
		||||
 | 
			
		||||
		return max(0, $resetTime - time());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Verifies whether the user is currently logged in.
 | 
			
		||||
	 */
 | 
			
		||||
@ -92,7 +115,8 @@ class Authentication
 | 
			
		||||
	{
 | 
			
		||||
		return Registry::get('db')->query('
 | 
			
		||||
			UPDATE users
 | 
			
		||||
			SET reset_key = {string:key}
 | 
			
		||||
			SET reset_key = {string:key},
 | 
			
		||||
				reset_blocked_until = UNIX_TIMESTAMP() + ' . static::DEFAULT_RESET_TIMEOUT . '
 | 
			
		||||
			WHERE id_user = {int:id}',
 | 
			
		||||
			[
 | 
			
		||||
				'id' => $id_user,
 | 
			
		||||
@ -117,4 +141,26 @@ class Authentication
 | 
			
		||||
				'blank' => '',
 | 
			
		||||
			]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static function updateResetTimeOut($id_user)
 | 
			
		||||
	{
 | 
			
		||||
		$currentResetTimeOut = static::getResetTimeOut($id_user);
 | 
			
		||||
 | 
			
		||||
		// New timeout: between 30 seconds, double the current timeout, and a full day
 | 
			
		||||
		$newResetTimeOut = min(max(static::DEFAULT_RESET_TIMEOUT, $currentResetTimeOut * 2), 60 * 60 * 24);
 | 
			
		||||
 | 
			
		||||
		$success = Registry::get('db')->query('
 | 
			
		||||
			UPDATE users
 | 
			
		||||
			SET reset_blocked_until = {int:new_time_out}
 | 
			
		||||
			WHERE id_user = {int:id_user}',
 | 
			
		||||
			[
 | 
			
		||||
				'id_user' => $id_user,
 | 
			
		||||
				'new_time_out' => time() + $newResetTimeOut,
 | 
			
		||||
			]);
 | 
			
		||||
 | 
			
		||||
		if (!$success)
 | 
			
		||||
			throw new UnexpectedValueException('Could not set password reset timeout!');
 | 
			
		||||
 | 
			
		||||
		return $newResetTimeOut;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,7 @@ abstract class User
 | 
			
		||||
	protected $ip_address;
 | 
			
		||||
	protected $is_admin;
 | 
			
		||||
	protected $reset_key;
 | 
			
		||||
	protected $reset_blocked_until;
 | 
			
		||||
 | 
			
		||||
	protected bool $is_logged;
 | 
			
		||||
	protected bool $is_guest;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user