I'm curious to know: once a pw_reset token is sent via email, is it possible to get the email address of the referrer's account for validating, or would it be best to validate against an IP? I've set my token expire for only 1 hour, and even dynamic IPs don't change at that rate, right?
Some code I'm playing around with (very much WIP), open to improvements/suggestions/critique.
Thanks in advance.
/**
* Reset Password Form
*
* $route['auth/reset-password'] = 'reset_password_form';
*
*/
public function reset_password_form()
{
$this->load->view('templates/public', array(
'content' => 'auth/reset_password_form',
'title' => 'Password reset',
'description' => '',
'canonical' => 'auth'
));
}
/**
* Reset Password Authentication
*
* $route['auth/reset_password/validate'] = 'auth/reset_password';
*
*/
public function reset_password()
{
//setup validation rules
$config = array(
array('field'=>'email_address', 'label' =>'Email address', 'rules'=>'trim|required|max_lenght[128]|valid_email|valid_user_email')
);
$this->form_validation->CI =& $this;
$this->form_validation->set_rules($config);
//run validator and confirm OR
if($this->form_validation->run($this))
{
//create the token data
$tokenhash = Auth::create_token();
$password_token = json_encode(array(
'token' => $tokenhash,
'expires' => date('h:i:s A', strtotime($this->config->item('pw_token_expires'))), // default 1 hours
'ip_address' => $this->input->ip_address()
)); // output {"token":"3513f5ee34ED3","expires":"01:14:06 AM","ip_address":"127.0.0.1"}
//grab a userid to use via php-activerecord custom USER model
$user = User::get_by_email_address($this->input->post('email_address'));
//update the user pw_token field
try{
if($user->update_attribute('pw_token', $password_token))
{
throw new \ActiveRecord\ActiveRecordException($user->errors);
}
}catch(\ActiveRecord\ActiveRecordException $e){
log_message('error', $e->getMessage());
}
//setup email data
//TODO : move this to USER activeRecord\Model (pre_)
$email_data = array(
'email_to' => $user->email_address,
'token' => anchor(site_url('auth/reset_password/confirm/'.$tokenhash.'/'.$user->id.''), 'click here to reset your password'),
'site_link' => anchor(site_url(), site_url())
);
try{
if(Mail::send($email_data, 'reset_password.tpl.html'))
{
throw new Exception(Mail::errors());
}
}catch(Exception $e){
log_message('error', $e->getMessage());
}
}
else
{
$this->reset_password_form();
}
}
/**
* Reset Password Confirmation
*
* $route['auth/reset_password/confirm/(:any)/(:num)'] = 'auth/confirm_password_reset';
*/
public function confirm_password_reset($token='', $userid='')
{
//check for null values
if(!$token || !$id)
{
redirect('/');
}
//ugly
$attempts = $this->session->set_userdata('reset_pw_attempt', (int)0);
$this->session->set_userdata('reset_pw_attempts', $attempts++);
if(!$this->user->id != $userid && $this->session->userdata('logged_in') === (int)0)
{
//not great but cant validate the email, so we check to see if they have a logged in session.
//this is not a "forgot_password request" so we should be good as long as they are logged in
show_404();
}
else
{
//looking good so far, now lets see do they have the correct permissions
if(in_array(PERM_UPDATE, Auth::permissions($this->user->permissions)))
{
if($attempts == (int)3)
{
$this->session->set_flashdata('', $this->lang->line('pw_reset_attempt_over'));
redirect('/');
}
else
{
$tokn[] = json_decode($this->user->pw_token);
if(date('h:i:s A', time()) > $tokn['expires'] && $token===$tokn['token'])
{
$this->load->view('do-pw_reset_form');
}
}
}
else
{
$this->session->set_flashdata('info', $this->lang->line('update_permission_denied'));
redirect('/');
}
}
}
Checking the email address via a querystring parameter or utilizing IP restriction are both completely superfluous. If the password token is random and long enough it will be impossible to brute-force it, especially when combined with a token expiration AND rate-limiting of token guesses.
The IP address restriction can be a usability issue as well. For instance a user might request a password reset just before leaving work then finish the reset process from home or during the commute home - that is, the user's IP address changes between the reset request and the reset confirmation.