Yii2: Customize login failure message with exact reason

597 views Asked by At

I want to customize login failure message in Yii2. Right now, when I type wrong username or wrong password it will show same message for both.

Now i have one column in user table i.e. is_verified if user email is verified then I set value of this field to 1 else 0

If user register and doesn't verify his email the the value of is_verified=0

Now if user want to login then it showing that Incorrect username or password.

But i want to tell user that his email is not verified.

In LoginForm.php a function validatePassword will through this error

public function validatePassword($attribute, $params) {
        if (!$this->hasErrors()) {
            $user = $this->getUser();
            if (!$user || !$user->validatePassword($this->password)) {
                $this->addError($attribute, 'Incorrect username or password.');
            }
        }
    }

Please help in this or give any idea how can i override default login function

I am using yii2-basic-app

One more thing $user = $this->getUser(); this will return only those user whose status=10 by default i set user status=0 until he is not verify his email

what is want to do:

    if(username not exist in db){
        echo "username not found in db";
    }elseif(password not valid){
        echo "password not valid";
    }elseif(is_verified=0){
        echo "email not verified";
    }elseif(status = 0){
        echo "account is deactive";
    }

code from app\models\User.php

/**
     * @inheritdoc
     */
    public static function findIdentity($id) {
        return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
    }

    /**
     * @inheritdoc
     */
    public static function findIdentityByAccessToken($token, $type = null) {
        throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
    }

    /**
     * Finds user by username
     *
     * @param string $username
     * @return static|null
     */
    public static function findByUsername($username) {
        return static::findOne(['email' => $username, 'status' => self::STATUS_ACTIVE]);
    }

    /**
     * Finds user by password reset token
     *
     * @param string $token password reset token
     * @return static|null
     */
    public static function findByPasswordResetToken($token) {
        if (!static::isPasswordResetTokenValid($token)) {
            return null;
        }

        return static::findOne([
            'password_reset_token' => $token,
            'status' => self::STATUS_ACTIVE,
        ]);
    }

    /**
     * Finds out if password reset token is valid
     *
     * @param string $token password reset token
     * @return boolean
     */
    public static function isPasswordResetTokenValid($token) {
        if (empty($token)) {
            return false;
        }
        $expire = Yii::$app->params['user.passwordResetTokenExpire'];
        $parts = explode('_', $token);
        $timestamp = (int) end($parts);
        return $timestamp + $expire >= time();
    }

    /**
     * @inheritdoc
     */
    public function getId() {
        return $this->getPrimaryKey();
    }

    /**
     * @inheritdoc
     */
    public function getAuthKey() {
        return $this->auth_key;
    }

    /**
     * @inheritdoc
     */
    public function validateAuthKey($authKey) {
        return $this->getAuthKey() === $authKey;
    }

    /**
     * Validates password
     *
     * @param string $password password to validate
     * @return boolean if password provided is valid for current user
     */
    public function validatePassword($password) {
        return Yii::$app->security->validatePassword($password, $this->password_hash);
    }

    /**
     * Generates password hash from password and sets it to the model
     *
     * @param string $password
     */
    public function setPassword($password) {
        $this->password_hash = Yii::$app->security->generatePasswordHash($password);
    }

    /**
     * Generates "remember me" authentication key
     */
    public function generateAuthKey() {
        $this->auth_key = Yii::$app->security->generateRandomString();
    }

    /**
     * Generates new password reset token
     */
    public function generatePasswordResetToken() {
        $this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time();
    }

    /**
     * Removes password reset token
     */
    public function removePasswordResetToken() {
        $this->password_reset_token = null;
    }

Code from app\models\LoginForm.php

/**
     * Validates the password.
     * This method serves as the inline validation for password.
     *
     * @param string $attribute the attribute currently being validated
     * @param array $params the additional name-value pairs given in the rule
     */
    public function validatePassword($attribute, $params) {
        if (!$this->hasErrors()) {
            $user = $this->getUser();
            if (!$user || !$user->validatePassword($this->password)) {
                $this->addError($attribute, 'Incorrect username or password.');
            }
        }
    }

    /**
     * Logs in a user using the provided username and password.
     *
     * @return boolean whether the user is logged in successfully
     */
    public function login() {
        if ($this->validate()) {
            return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);
        } else {
            return false;
        }
    }

    /**
     * Finds user by [[username]]
     *
     * @return User|null
     */
    public function getUser() {
        if ($this->_user === false) {
            $this->_user = User::findByusername($this->username);
        }

        return $this->_user;
    }

Thank You.

4

There are 4 answers

0
Fahad Ali On BEST ANSWER

I have found the solution and now its working as I expected. All Credit goes to @scaisEdge. I get idea from his suggestions. Now i am posting actual code that works:

public function validatePassword($attribute, $params) {

    if (!$this->hasErrors()) {
        $user = $this->getUser();
        if (!$user) {
            $this->addError("username", 'Invalid username.');
            return;
        }
        if (!$user->validatePassword($this->password)) {
            $this->addError($attribute, 'Incorrect password.');
            return;
        }
        if (User::find()->where(['email' => $this->username])->andwhere(['verified' => 0])->one()) {

            $this->addError("username", 'email not varified ');
            return;
        }
        if (User::find()->where(['username' => $this->username])->andwhere(['status' => 0])->one()) {

            $this->addError($attribute, 'account is not active');
            return;
        }
    }
}

Thank You for all who answer and being kind to find out some time for me.

I posted this answer so someone else can get help from this.

8
ScaisEdge On

you could check the condition !$user and !$user->validatePassword($this->password) separatly

public function validatePassword($attribute, $params) {
        if (!$this->hasErrors()) {
            $user = $this->getUser();
            if (!$user |) {
                $this->addError($attribute, 'Invalid username.');
                return;
            }   
            if ( !$user->validatePassword($this->password)) {
                $this->addError($attribute, 'Incorrect password.');
                return;
            }
        }
    }

you could check if the User is properly setted this way

Assuming that you have a User Model Class

  $checkUser = User::find()->where('username' => $your_userName)->one();
  if isset($checkUser){
      if($checkUser->status = 0 ){
      // User not activated 
     }
  } else {
     // user not present 
}

looking to your code you can check the if username entered have if is already enabled or not eg:

public function login() {
    if ($this->validate()) {
        if (  User::find()->where(['username' => $this->username , 'status' => 0  ) {
          // mean the user is not  validated ..  
          $this->addError($attribute, 'User not validated .. ');
          return false 
        } else {
          return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);

      }
    } else {
        return false;
    }
}
0
Kalu On

Override the beforeValidate() function and add your check. ie:

public function beforeValidate()
{
    if (parent::beforeValidate()) {
        $this->user = findUserByUsernameOrEmail(trim($this->username));
        if ($this->user !== null && $this->user->is_verified === 0) {
             $this->addError('username' , ' Email not verified....etc' );
        }
    }

}
2
vityapro On

As far i understand you, function will:

public function validatePassword($attribute, $params) {
    if (!$this->hasErrors()) {
        $user = $this->getUser();
        if (!$user || !$user->validatePassword($this->password)) {
            $this->addError($attribute, 'Incorrect username or password.');
            return false;
        }
        if(!$user->is_verified){
           $this->addError('username', 'Email is not verified.');
           return false;
       }    
    }
}

Of course if you get your users from db, in other case you must write one more method in User or LoginForm model to get email status form db, using ActiveRecords, for example:

 public function validatePassword($attribute, $params) {
    if (!$this->hasErrors()) {
        $user = $this->getUser();
        if (!$user || !$user->validatePassword($this->password)) {
            $this->addError($attribute, 'Incorrect username or password.');
            return false;
        }
        if(!$user->getUserEmailStatus()){
           $this->addError('username', 'Email is not verified.');
           return false;
       }    
    }
}