Registration Forms little tips

Hi all,

Im just new on the board and I started using the Yii framework only few days ago. And I love it :), I find it beautiful. I started testing it with this tutorial and tried to add more functionalities which could interest some of you.

The idea is to limit the range of characters used in a field (username for example).

Here is how to do it (working with CActiveRecord) with username:

  1. Declare $usernameLegal

class RegisterForm extends CActiveRecord

{

	...

	public $usernameLegal;

	...



  1. $usernameLegal is actually $username with only the allowed characters remaining. Because it’s not a field of your form, it must be affected between the time the server receives post data and the time the validation process takes place => beforeValidate()

Add this function in you CActiveRecord class (in this case I allow letters, numbers and few other characters).


public function beforeValidate()

{

    	$this->usernameLegal = preg_replace('/[^A-Za-z0-9@_#?!&-]/','',$this->username);

    	return true;

}



  1. Add a new rule which will compare $usernameLegal with $username, and throw a custom message error if they don’t match (you don’t want the default message ‘Username must be repeated exactly’, it won’t make sense for your website user).

public function rules()

	{

		return array(

			...

			array('username', 'compare', 'compareAttribute'=>'usernameLegal', 'message'=>'Username contains illegal characters'),

			...


		);

	}



Of cource, you can play around with preg_replace and specify a list of illegal characters instead of a list of legal characters but this is strictly the same (half full glass or half empty :D). You just need to remove ‘^’ in the preg_replace pattern. Example (will replace < > \ / ; by ’ ')


preg_replace('/[<>\/\;]/','',$this->username);

I hope this helps.

Hello, one question:

In the User Controler you do:

// Save to the user table    


 &#036;user-&gt;save();


// Delete the record


 &#036;validate-&gt;delete();

This is after the user clicks to confirm his registration so you put his details in the user table and delete them from the validate table.

What happens if I try to sign up with the same email since it check uniquness against the validate table and now it would be empty?

I could just comment this delete line and all will be well, but, after 7 days the record should be deleted so the user can register with that email. Then how to make it unique by email on user table when the register is on validate table?

Thank You.

Hello, i hope you find solution after half year :)

I solved this problem by using a filter:




class Validate extends CActiveRecord

{

    public function rules()

    {

        return array(

            array('email', 'checkuser'),

        );

    }


     public function checkuser($attribute,$params)

    {

        $user = User::model()->find('LOWER(email)=?', array(strtolower($this->email)));

        if ($user === null){

            return true;

        }

        $this->addError('email','Error text');

        return false;

    }

}



I’ve implemented the technique described above.

However I have a question - doesn’t this mean that the password will be encrypted every time I call model->save()?

So what will happen if the user edits his profile, or if I want to update his last_login date or change anything on this user object - the password will get re-encrypted?

Hello and thank you for such a great tutorial. I followed your steps and made some improvements (I think so). Insteed of md5 I am using Sha512 encrypting with salt key that is changing after every login, but now I got error and can’t find out what is wrong with my code. If you help me I think that my improvement (that doesn’t work without your help) will improve security. Here is what I have now.

It shows this error massege:




Property "Users.password2" is not defined.


/framework/db/ar/CActiveRecord.php(144)



SiteController.php


	

public function actionRegister()

	{

		$model = new Users;

		// collect user input data

		

		if(isset($_POST['ajax']) && $_POST['ajax']==='register-form')

		{

			echo CActiveForm::validate($model);

			Yii::app()->end();

		}

		

		

		if(isset($_POST['RegsiterForm']))

		{

			$model->attributes=$_POST['RegsiterForm']; // set all attributes with post values


			// NOTE Changes to any $model->value have to be performed BEFORE $model-validate() 

			// or else it won't save to the database. 


			// Check if question has a ? at the end


			// validate user input and redirect to previous page if valid

			if($model->validate())

			{

				// save user registration

				$model->save();

				$this->redirect($this->render('finished',array('model'=>$model))); // Yii::app()->user->returnUrl

			}

		}

		// display the registration model

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

	}



Register.php (The view file, form was generated from Gii)




<div class="form">


<?php $form=$this->beginWidget('CActiveForm', array(

	'id'=>'register-form',

	'enableAjaxValidation'=>true,

)); ?>


	<p class="note">Fields with <span class="required">*</span> are required.</p>


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


	<div class="row">

		<?php echo $form->labelEx($model,'username'); ?>

		<?php echo $form->textField($model,'username'); ?>

		<?php echo $form->error($model,'username'); ?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($model,'password'); ?>

		<?php echo $form->textField($model,'password'); ?>

		<?php echo $form->error($model,'password'); ?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($model,'password2'); ?>

		<?php echo $form->textField($model,'password2'); ?>

		<?php echo $form->error($model,'password2'); ?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($model,'email'); ?>

		<?php echo $form->textField($model,'email'); ?>

		<?php echo $form->error($model,'email'); ?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($model,'fullname'); ?>

		<?php echo $form->textField($model,'fullname'); ?>

		<?php echo $form->error($model,'fullname'); ?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($model,'question'); ?>

		<?php echo $form->textField($model,'question'); ?>

		<?php echo $form->error($model,'question'); ?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($model,'answer'); ?>

		<?php echo $form->textField($model,'answer'); ?>

		<?php echo $form->error($model,'answer'); ?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($model,'verifyCode'); ?>

		<?php echo $form->textField($model,'verifyCode'); ?>

		<?php echo $form->error($model,'verifyCode'); ?>

	</div>

	

	<div class="row buttons">

		<?php echo CHtml::submitButton('Submit'); ?>

	</div>


<?php $this->endWidget(); ?>


</div><!-- form -->



user.php (the model)




	public function rules()

	{

		// NOTE: you should only define rules for those attributes that

		// will receive user inputs.

		return array(

			array('username','length','max'=>16),

			// convert username to lower case

			array('username', 'filter', 'filter'=>'strtolower'),

			array('password','length','max'=>32, 'min'=>6),

			array('password2','length','max'=>32, 'min'=>6),

			// compare password to repeated password

			array('password', 'compare', 'compareAttribute'=>'password2'), 

			array('email, fullname', 'length', 'max'=>128),

			// make sure email is a valid email

			array('email','email'),

			// make sure username and email are unique

			array('username, email, password, salt', 'unique'), 

			// convert question to lower case

			array('question', 'filter', 'filter'=>'strtolower'),

			array('answer','length','max'=>64),

			// convert answer to lower case

			array('answer', 'filter', 'filter'=>'strtolower'),

			array('privilages, worns, status', 'numerical', 'integerOnly'=>true),

			array('privilages','length','max'=>4, 'min'=>1),

			array('worns','length','max'=>3, 'min'=>0),

			array('status','length','max'=>2, 'min'=>0),

			array('username, password, password2, salt, email, fullname, register_date, login_date, privilages, worns, status', 'required'),

			// verifyCode needs to be entered correctly

			array('verifyCode', 'captcha', 'allowEmpty'=>!extension_loaded('gd')),

		);

	}


 [..........]


	public function beforeSave()

	{

		while ($record2 === null){

			$salt = Randomness::randomString(32);

			$record2 = Users::model()->findByAttributes(array('salt'=>$salt));

		}

		$pass = hash('sha512', $this->password.$salt);

		$this->salt = $salt;

		$this->password = $pass;

		return true;

	}



and UserIdentity.php




<?php


/**

 * UserIdentity represents the data needed to identity a user.

 * It contains the authentication method that checks if the provided

 * data can identity the user.

 */

class UserIdentity extends CUserIdentity

{

	private $_id;

	

    public function authenticate()

    {

        $record=Users::model()->findByAttributes(array('username'=>$this->username));

        if($record===null)

            $this->errorCode=self::ERROR_USERNAME_INVALID;

        else if($record->password!==hash('sha512', $this->password.$record->salt))

            $this->errorCode=self::ERROR_PASSWORD_INVALID;

        else

        {

			while ($record2 !== null){

				$salt = Randomness::randomString(32);

				$record2 = Users::model()->findByAttributes(array('salt'=>$salt));

			}

			$record->salt = $salt;

			$record->password = hash('sha512', $this->password.$salt);

			$record->save;

            $this->_id=$record->id;

            $this->setState('user_id', $record->id);

			$this->setState('user_username', $record->username);

			$this->setState('user_privilages', $record->privilages);

            $this->errorCode=self::ERROR_NONE;

        }

        return !$this->errorCode;

    }

 

    public function getId()

    {

        return $this->_id;

    }

}



Also I must mention that I am using the Randomness extention .

Regarding password security, I’ve read and learned alot from tom[] last couple days, and I ended up doing a simple registration using this guide and his extention Randomness.

Md5,sha1 and even salted md5 is crap compared to a true random hash.

http://www.yiiframework.com/extension/randomness/#hh4 I STRONGLY recommend everyone to use it and

Hope someone can help me…

This is my code in protected/model/RegistrationForm.php




<?php

/**

 * RegistrationForm class.

 * RegistrationForm is the data structure for keeping

 * user registration form data. It is used by the 'registration' action of 'UserController'.

 */

class RegistrationForm extends User {

	

	public $verifyPassword;

	public $verifyCode;

		

public function rules() {

		$rules = array(

			array('username, password, verifyPassword, email','required'),

			array('username', 'length', 'max'=>20, 'min' => 3,'message'=> UserModule::t("Incorrect username (length between 3 and 20 characters).")),

			array('password', 'length', 'max'=>128, 'min' =>4,'message' => UserModule::t("Incorrect password (minimal length 4 symbols).")),

			array('email', 'email'),

			array('username', 'unique', 'message' => UserModule::t("This user's name already exists.")),

			array('email', 'unique', 'message' => UserModule::t("This user's email address already exists.")),

			array('verifyPassword', 'compare','compareAttribute'=>'password', 'message' => UserModule::t("Retype Password is incorrect.")),

			array('username', 'match', 'pattern' => '/^[A-Za-z0-9_]+$/u','message' => UserModule::t("Incorrect symbols (A-z0-9).")),);

		

		if (isset($_POST['ajax']) && $_POST['ajax']==='registration-form') 

			return $rules;

		else 

			array_push($rules,array('verifyCode', 'captcha', 


'allowEmpty'=>!UserModule::doCaptcha('registration')));

		return $rules;

	}

	

}

?>




and this is my controllers/SiteController.php




 public function actionRegister()

        {

                $form=new User;

                // collect user input data

                if(isset($_POST['User']))

                {

                        $form->attributes=$_POST['User']; // set all attributes with post values

                        

                        // NOTE Changes to any $form->value have to be performed BEFORE $form-validate() 

                        // or else it won't save to the database. 

                        

                        // Check if question has a ? at the end

                        $last = $form->question[strlen($form->question)-1]; 

                        if($last !== "?")

                        {

                                $form->question .= '?';

                        }

                        

                        // validate user input and redirect to previous page if valid

                        if($form->validate())

                        {                               

                                // save user registration

                                $form->save();

                                $this->redirect($this->render('finished',array('form'=>$form))); // Yii::app()->user->returnUrl

                        }

                }

                // display the registration form

                $this->render('register',array('form'=>$form));

        }



and this is my view page views/site/pages/register.php




<?php

$this->pageTitle=Yii::app()->name . ' - Register';

?>

<h1>Register</h1>


<div class="yiiForm">

<?php echo CHtml::beginForm(); ?>


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


<div class="simple">

<br/>

<p class="hint" style="margin-left:70px;">

Note: Your login name must be unique and a max of 32 characters.

</p>

<?php echo CHtml::activeLabel($form,'username', array('style'=>'width:150px;')); ?>

<?php echo CHtml::activeTextField($form,'username') ?>

</div>


<div class="simple">

<?php echo CHtml::activeLabel($form,'password', array('style'=>'width:150px;')); ?>

<?php echo CHtml::activePasswordField($form,'password') ?>

</div>


<div class="simple">

<?php echo CHtml::activeLabel($form,'password2', array('style'=>'width:150px;')); ?>

<?php echo CHtml::activePasswordField($form,'password2') ?>

</div>


<div class="simple">

<?php echo CHtml::activeLabel($form,'email', array('style'=>'width:150px;')); ?>

<?php echo CHtml::activeTextField($form,'email') ?>

</div>


<br/>


<div class="simple">

<p class="hint" style="margin-left:70px;">

Note: Your secret question that will pop up for you to reset your password (if needed).

</p>

<?php echo CHtml::activeLabel($form,'question', array('style'=>'width:150px;')); ?>

<?php echo CHtml::activeTextField($form,'question') ?>

</div>


<div class="simple">

<p class="hint" style="margin-left:70px;">

Note: Your secret answer you'll need to type in when asked your secret question.

</p>

<?php echo CHtml::activeLabel($form,'answer', array('style'=>'width:150px;')); ?>

<?php echo CHtml::activeTextField($form,'answer') ?>

</div>


<br/>


<?php if(extension_loaded('gd')): ?>

<div class="simple">

        <?php echo CHtml::activeLabel($form,'verifyCode', array('style'=>'width:150px;')); ?>

        <div>

        <?php $this->widget('CCaptcha'); ?>

        <?php echo CHtml::activeTextField($form,'verifyCode'); ?>

        </div>

        <p class="hint">Please enter the letters as they are shown in the image above.

        <br/>Letters are not case-sensitive.</p>

</div>

<?php endif; ?>


<div class="action">

<?php echo CHtml::submitButton('register'); ?>

</div>


<?php echo CHtml::endForm(); ?>


</div><!-- yiiForm -->



but I have problem with this because when I click register on the menu, it will display this message

Fatal error: Call to a member function getErrors() on a non-object in C:\xampp\htdocs\yii\framework\web\helpers\CHtml.php on line 1592

I really don’t know what to do because I’m still newbie using Yii… I hope someone can help me to make a registration. Thank you

looking for more tutorials as i want to be an expert.

That is not really a yii problem, its a OOP problem. If you do not understand that error message I suggest you go thrue some tutorials on the matter.

Because it is very straight forward error message.

Good hack, but when I am sending an email verification mail and user comes to verify the mail id, I update the DB for a flag change. It calls the beforeSave function once again and encrypt the password once again. Any solution?

What if i want a new user to confirm his registration by email prior to let him login?

Awesome tutorial. I really love it. Thanks for sharing your experience. Please let us know the[size="4"] url [/size]for your all tutorials.