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;
 | 
									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());
 | 
								Authentication::setResetKey($user->getUserId());
 | 
				
			||||||
			Email::resetMail($user->getUserId());
 | 
								Email::resetMail($user->getUserId());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -76,6 +94,10 @@ class ResetPassword extends HTMLController
 | 
				
			|||||||
			if (empty($missing))
 | 
								if (empty($missing))
 | 
				
			||||||
			{
 | 
								{
 | 
				
			||||||
				Authentication::updatePassword($user->getUserId(), Authentication::computeHash($_POST['password1']));
 | 
									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'];
 | 
									$_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/');
 | 
									header('Location: ' . BASEURL . '/login/');
 | 
				
			||||||
				exit;
 | 
									exit;
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,8 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
class Authentication
 | 
					class Authentication
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						const DEFAULT_RESET_TIMEOUT = 30;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Checks a password for a given username against the database.
 | 
						 * Checks a password for a given username against the database.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@ -57,6 +59,27 @@ class Authentication
 | 
				
			|||||||
		return $hash;
 | 
							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.
 | 
						 * Verifies whether the user is currently logged in.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@ -92,7 +115,8 @@ class Authentication
 | 
				
			|||||||
	{
 | 
						{
 | 
				
			||||||
		return Registry::get('db')->query('
 | 
							return Registry::get('db')->query('
 | 
				
			||||||
			UPDATE users
 | 
								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}',
 | 
								WHERE id_user = {int:id}',
 | 
				
			||||||
			[
 | 
								[
 | 
				
			||||||
				'id' => $id_user,
 | 
									'id' => $id_user,
 | 
				
			||||||
@ -117,4 +141,26 @@ class Authentication
 | 
				
			|||||||
				'blank' => '',
 | 
									'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 $ip_address;
 | 
				
			||||||
	protected $is_admin;
 | 
						protected $is_admin;
 | 
				
			||||||
	protected $reset_key;
 | 
						protected $reset_key;
 | 
				
			||||||
 | 
						protected $reset_blocked_until;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected bool $is_logged;
 | 
						protected bool $is_logged;
 | 
				
			||||||
	protected bool $is_guest;
 | 
						protected bool $is_guest;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user