php - Why do attempts to update a user's password in this Codeigniter 3 application fail? ← (PHP, CodeIgniter, Bootstrap)

I am working on a basic blog application in Codeigniter 3.1.8 and Bootstrap 4.

I have added a registration and login system to this application. I am current working on a password reset system.

I can't figure out why updating a user's password fails as below:

enter image description here

In the Newpassword controller I have:

class Newpassword extends CI_Controller {
    private $token = NULL;

    public function index($token = NULL) {
        //echo $token; die();
        $data = $this->Static_model->get_static_data();
        $data['pages'] = $this->Pages_model->get_pages();
        $data['tagline'] = 'New password';
        $data['categories'] = $this->Categories_model->get_categories();
        $this->token = $token;
    
         // Form validation rules
         $this->form_validation->set_rules('password', 'Password', 'required|min_length[6]');
         $this->form_validation->set_rules('cpassword', 'Confirm password', 'required|matches[password]');
         $this->form_validation->set_error_delimiters('<p class="error-message">', '</p>');
    
        if(!$this->form_validation->run()) {
            $this->load->view('partials/header', $data);
            $this->load->view('auth/newpassword');
            $this->load->view('partials/footer');
        } else {
            $this->add();
        }
    }

    public function add() {
        $data = $this->Static_model->get_static_data();
        $data['pages'] = $this->Pages_model->get_pages();
        $data['tagline'] = 'New password';
        $data['categories'] = $this->Categories_model->get_categories();
    
        // Encrypt new password
        $enc_password = password_hash($this->input->post('password'), PASSWORD_DEFAULT);
    
        // Update password column
        $token = $this->token;
    
        if ($this->Usermodel->set_new_password($token, $enc_password)) {
            redirect('login'); 
            $this->session->set_flashdata("new_password_success", "Your new password was set. You can login");
        } else {
            $this->session->set_flashdata("new_password_fail", "We have failed updating your password");
            redirect('/newpassword'); 
        }
      }
    }

In the Usermodel model I have:

public function set_new_password($token, $enc_password) {
    $this->db
        ->where(['token' => $token])
        // set new password and reset token to NULL
        ->update('authors', array('password' => $enc_password, 'token' => NULL));
}

The form:

<?php echo form_open(base_url('newpassword/add')); ?>
  <div class="form-group <?php if(form_error('password')) echo 'has-error';?>">
    <input type="password" name="password" id="password" class="form-control" placeholder="Password">
    <?php if(form_error('password')) echo form_error('password'); ?> 
  </div>
  <div class="form-group <?php if(form_error('cpassword')) echo 'has-error';?>">
    <input type="password" name="cpassword" id="cpassword" class="form-control" placeholder="Confirm password">
    <?php if(form_error('cpassword')) echo form_error('cpassword'); ?> 
  </div>
  <div class="form-group mb-2">
    <input type="submit" value="Set password" class="btn btn-block btn-md btn-success">
  </div>            
<?php echo form_close(); ?>

If I uncomment //echo $token; die(); from the controller, the token is printed with the expected value. Yet, the password does not update (and the failure to update it is correctly displayed).

What am I doing wrong?

Answer



Solution:

When you click Set password button redirected to newpassword/add but the token not exists here, because used it only a previous step:

  1. Password reset request
  2. Click link in email (token exist)
  3. Fill the form and click "Set password" (send form data to newpassword/add) - the token is not exist here but you want to use it: $token = $this->token; but now the "token" is: add

Sorry, you must be refactoring your password reset code logic. I think this is harder to fix than rewriting the whole process

UPDATE:

Add $data['token'] = $token; into index method in Newpassword controller

AND

modify form action url in views/auth/newpassword.php to this: <?php echo form_open(base_url('newpassword/add/'.$token)); ?>

IMPORTANT!!!

Add return to ell functions in model: return $this->db-> .... And put redirect('<url>') after set session flashdata!

I tested it and I think fixed the problem.

Answer



Solution:

This is the final, working code for the password update (in case anybody else needs it):

In the Newpassword controller:

class Newpassword extends CI_Controller
{
    public function index($token = NULL)
    {
        $data               = $this->Static_model->get_static_data();
        $data['pages']      = $this->Pages_model->get_pages();
        $data['tagline']    = 'New password';
        $data['categories'] = $this->Categories_model->get_categories();
        $data['token']      = $token;
        
        // Form validation rules
        $this->form_validation->set_rules('password', 'Password', 'required|min_length[6]');
        $this->form_validation->set_rules('cpassword', 'Confirm password', 'required|matches[password]');
        $this->form_validation->set_error_delimiters('<p class="error-message">', '</p>');
        
        if (!$this->form_validation->run()) {
            $this->load->view('partials/header', $data);
            $this->load->view('auth/newpassword');
            $this->load->view('partials/footer');
        } else {
            // Encrypt new password
            $enc_password = password_hash($this->input->post('password'), PASSWORD_DEFAULT);
            
            if ($this->Usermodel->set_new_password($token, $enc_password)) {
                $this->session->set_flashdata("new_password_success", "Your new password was set. You can login");
                redirect('login');
            } else {
                $this->session->set_flashdata("new_password_fail", "We have failed updating your password");
                redirect('/newpassword/' . $token);
            }
        }
    }
}

In the model:

public function set_new_password($token, $enc_password) {
    return $this->db
        ->where('token', $token)
        // set new password and reset token to NULL
        ->update('authors', array('password' => $enc_password, 'token' => NULL));
}

The form (view):

<?php echo form_open(base_url('newpassword/'. $token)); ?>
  <div class="form-group <?php if(form_error('password')) echo 'has-error';?>">
    <input type="password" name="password" id="password" class="form-control" placeholder="Password">
    <?php if(form_error('password')) echo form_error('password'); ?> 
  </div>
  <div class="form-group <?php if(form_error('cpassword')) echo 'has-error';?>">
    <input type="password" name="cpassword" id="cpassword" class="form-control" placeholder="Confirm password">
    <?php if(form_error('cpassword')) echo form_error('cpassword'); ?> 
  </div>
  <div class="form-group mb-2">
    <input type="submit" value="Set password" class="btn btn-block btn-md btn-success">
  </div>            
<?php echo form_close(); ?>

Source