Yii, best way to implement "user change of password"

12,870

Solution 1

Its simple create a action that has logic for update pass.

Make target for form to new action in this case actionChangePass and validate there the way you want .

A rough example can be put like this

    public function actionChangePass($id)
    {  
    $user = loadModel($id)
    if(md5($_POST['User']['old_password']) === $user->password)
    {
       $user->setScenario('changePassword');
       $user->attributes = $_POST['User'];                
       $user->password = md5($_POST['User']['new_password']);
       if($user->save())
         Yii::app()->user->setFlash('passChanged', 'Your password has been changed <strong>successfully</strong>.');
    }            
    else
    {
      Yii::app()->user->setFlash('passChangeError', 'Your password was not changed because it did not matched the <strong>old password</strong>.');                    
    }  
 }

Also make sure you have $old_password in your user User Model. Also you can do some validations in rules of model to make new password required

there can be some different ways too but i do it like this

Also create your custom validation scenario changePassword

Solution 2

You should not pollute your model with rubbish. Please, always have in mind these basic MVC principles:

  • Your controller must not be aware of your model's implementation.
  • Don't pollute your model with stuff not connected with your application's business model.

Always create reusable code, make your code "DRY" (Don't repeat yourself)

By the way, what is the purpose of the username field? Since the form would be available to the logged user only, the username can be accessed already with Yii::app()->user.

<?php
// models/ChangePasswordForm.php

class ChangePasswordForm extends CFormModel
{
    /**
     * @var string
     */
    public $currentPassword;

    /**
     * @var string
     */
    public $newPassword;

    /**
     * @var string
     */
    public $newPasswordRepeat;

    /**
     * Validation rules for this form.
     *
     * @return array
     */
    public function rules()
    {
        return array(
            array('currentPassword, newPassword, newPasswordRepeat', 'required'),
            array('currentPassword', 'validateCurrentPassword', 'message'=>'This is not your password.'),
            array('newPassword', 'compare', 'compareAttribute'=>'validateNewPassword'),
            array('newPassword', 'match', 'pattern'=>'/^[a-z0-9_\-]{5,}/i', 'message'=>'Your password does not meet our password complexity policy.'),
        );
    }

    /**
     * I don't know your hashing policy, so I assume it's simple MD5 hashing method.
     * 
     * @return string Hashed password
     */
    protected function createPasswordHash($password)
    {
        return md5($password);
    }

    /**
     * I don't know how you access user's password as well.
     *
     * @return string
     */
    protected function getUserPassword()
    {
        return Yii::app()->user->password;
    }

    /**
     * Saves the new password.
     */
    public function saveNewPassword()
    {
        $user = UserModel::findByPk(Yii::app()->user->username);
        $user->password = $this->createPasswordHash($this->newPassword);
        $user->update();
    }

    /**
     * Validates current password.
     *
     * @return bool Is password valid
     */
    public function validateCurrentPassword()
    {
        return $this->createPasswordHash($this->currentPassword) == $this->getUserPassword();
    }
}

example controller action:

public function actionChangePassword()
{
    $model=new ChangePasswordForm();
    if (isset($_POST['ChangePasswordForm'])) {
        $model->setAttributes($_POST['ChangePasswordForm']);
        if ($model->validate()) {
            $model->save();
            // you can redirect here
        }
    }

    $this->render('changePasswordTemplate', array('model'=>$model));
}

example template code:

    <?php echo CHtml::errorSummary($model); ?>

    <div class="row">
        <?php echo CHtml::activeLabel($model,'currentPassword'); ?>
        <?php echo CHtml::activePasswordField($model,'currentPassword') ?>
    </div>

    <div class="row">
        <?php echo CHtml::activeLabel($model,'newPassword'); ?>
        <?php echo CHtml::activePasswordField($model,'newPassword') ?>
    </div>

    <div class="row">
        <?php echo CHtml::activeLabel($model,'newPasswordRepeat'); ?>
        <?php echo CHtml::activePasswordField($model,'newPasswordRepeat') ?>
    </div>

    <div class="row submit">
        <?php echo CHtml::submitButton('Change password'); ?>
    </div>

    <?php echo CHtml::endForm(); ?>
</div><!-- form -->

The template should be easy enough to create. This code, with some minor tweaks, is ready to be copied & pasted to another Yii project.

Solution 3

Here is what I personally like to do. It is a complicated version of this. Add to model two fields that will help you process the password. Note these two fields do not exists in database and are not present in Gii generated code. Something like

class UserModel extends CActiveRecord
{
    /*Password attributes*/
    public $initial_password;
    public $repeat_password;
    //..................
}

In the form, do not associate the actual password field in the database with any input. The two field in database should be associated with these two fields. The short version of the form becomes:

    <?php echo $form->errorSummary($model); ?>


    <?php echo $form->passwordFieldRow($model,'initial_password',array('class'=>'span5','maxlength'=>128)); ?> 

    <?php echo $form->passwordFieldRow($model,'repeat_password',array('class'=>'span5','maxlength'=>128)); ?>  

Now how do I know that user changed password? I simply check in beforeSave() if the two fields are empty and compare them and then change the password. If they are empty then I just skip the whole thing altogether. So simple version of beforeSave is:

/**
     * Called before saving the model
     */
    protected function beforeSave()
    {
        if(parent::beforeSave())
        { 
            if($this->isNewRecord)
            { 
                $this->password = HashYourPass($this->initial_password); 
            }
            else
                { 
                    //should we update password?
                    if($this->initial_password !== '')
                    {
                        //encrypt password and assign to password field
                        $this->password = HashYourPass($this->initial_password);
                       //Check old password match here
                    }
                }
            return true;
        }
        else
            return false;
    }

Now according to your question, one thing is missing. Checking old password! You can add new Model fields called aold password and its form input control. Then in beforesave method (as indicated by comment) you can compare the input with actual password field from the database and if they match then do change password.

You can add them as validation rules with scenarios but I found it complicated somehow and with little time at hand I went with this method.

Share:
12,870
mahsa.teimourikia
Author by

mahsa.teimourikia

PhD in Computer Science, passionate about data science and machine learning.

Updated on June 15, 2022

Comments

  • mahsa.teimourikia
    mahsa.teimourikia almost 2 years

    I'm using Yii for an application, I'm writing a very simple user management, like registering, deleting and updating users... For updating the existing user I need to check the old password first before change it to the new inserted password. So here is the fields I have in the form:

    username:----
    old_password:---
    new_password:---
    

    and my user table looks like this:

    id, username, password
    

    How can I validate the old_password before updating it with the new_password? I know the usual php coding, but I want to know if there are any Yii tricks that does this automatically...

    Thanks in advance

    • caw
      caw over 7 years
      You should re-use an existing authentication framework whenever possible, because, really, it's complex. For example, take a look at github.com/delight-im/PHP-Auth which is both framework-agnostic and database-agnostic.
  • ThomasVdBerge
    ThomasVdBerge over 11 years
    Not really... Yiish would be to create a new Form and put all the logic in there
  • Afnan Bashir
    Afnan Bashir over 11 years
    @ThomasVdBerge its not about view as forms are in view. Action is simple you can create new form and redirect to the action at the end of the day its action who has to do work
  • GusDeCooL
    GusDeCooL about 11 years
    i will prefer create new model form, i didn't want to dirty my model with unreal field property.
  • emix
    emix over 10 years
    I updated the post with example template code. You should follow official Yii tutorial on how to manage forms in views.
  • RN Kushwaha
    RN Kushwaha over 8 years
    @ThomasVdBerge Can you provide a working example of yii2sh.