2

I have written some registration code with HTML, Javascript and php. The registration process(at a higher level) is as follows:

The user:
1. Enters their email, name, and password(twice) to an HTML form.
2. They press "create account"
3. Their email is then searched for in my users table to see if it exists.
4. If it exists then they are told they are already registered and provided with a link to the login screen. 5. If the email doesn't already exist then it is inserted (along with other user details) to the users table in my database

My code works well to achieve this, however on testing it I have found a fault. If the user presses "create account" multiple times very fast it allows the same email address to register twice. Is there anything I can do to stop this?

Here's my code:
JQuery/Javascript

$("#registration_form").on("submit", function(e){
    //this is called when the form is submitted i.e when "create account" is pressed
    e.preventDefault();

    var registration_email = $('#registration_email').val();  
    var registration_password = $('#registration_password').val(); 
    var registration_password_confirmation = $('#confirm_registration_password').val(); 
    var registration_display_name = $('#registration_display_name').val(); 

    //validate fields and check if passwords match.
    //all values have passed validation testing therefore make ajax request

    var params = { 'registration_email' : registration_email, 'registration_password' : registration_password , 'registration_display_name' :  registration_display_name, 'app_root_url':app_root_url}; 

        $.ajax({
        url: app_root_url + 'login_registration/registration_processing.php',
        data: JSON.stringify(params), 
        type: "POST",
        dataType: "json",
        contentType: "application/json;charset=utf-8",

            success: function(data){
            var result = data;

            var emailAlreadyExists = result.email_already_exists; 
            if(emailAlreadyExists){
                //email already exists in our database and therefore the user is already registered so should use the login form

                displayError('registration_feedback_message', 'This email already exists in the system. If you already have an account please <a href="#page-login">login here!</a>');

            }else{
                //email does not already exist in our database

            }               

        }//end success
    });//end ajax


});

registration_processing.php (the main part of this file)

include_once '../includes/app_functions.php';
$app_root_url = filter_var($json->app_root_url, FILTER_SANITIZE_URL);
$email = filter_var($json->registration_email, FILTER_SANITIZE_EMAIL); 
$password = filter_var($json->registration_password, FILTER_SANITIZE_STRING);
$display_name = filter_var($json->registration_display_name, FILTER_SANITIZE_STRING);
//more data filtering is performed here

$userPrivilegeID = 1; //basic user privilege
$userHasPassword = 1; //boolean 1 or 0

$profileImage = "images/profile_images/blank-avatar.png"; 

$results = registerUser($password, $email, $isAvatarImage, $profileImage, $userPrivilegeID, $display_name, $userHasPassword, $pdoConnection);

//create an array to store all values that we want to send back to the client side.
$data = array();
if($results['exception_occurred'] == true){
        $data['exception_occurred'] = true;
        $data['exception_message'] = $results['exception_message']; 
        echo json_encode($data);

}else{
        $data['exception_occurred'] = false;
        if($results['email_already_exists'] == true){
            //email already exists. user is already registered and therefore has a password
            //need to show error to user to say they are already registered and should use the login form.

            $data['email_already_exists'] = true;
            echo json_encode($data);

        }else{
            //email didnt exist so they have been registered
            $data['email_already_exists'] = false;
            //create an array which we will encrypt as our JWT token
            $token = array();
            $token['userID'] = $results['user_id'];
            $token['email'] = $email;

            $data['userID'] = $results['user_id'];
            $data['user_is_subscriber'] = true;
            $data['valid_user'] = true;
            $data['userDetails'] = getProfile($results['user_id'], $pdoConnection);

            $data['usertoken'] = JWT::encode($token, 'secret_server_key');
            //echo data back to ajax request on client side
            echo json_encode($data);
        }
}

registerUser function (in app_functions.php)

function registerUser($password, $email, $isAvatarImage, $profileImage, $userPrivilegeID, $display_name, $userHasPassword, $pdoConnection){
    $data = array();
    try{
        $data['exception_occurred'] = false;
        //first check if that email already exists just in case
        $query = "SELECT COUNT(userID) FROM users WHERE emailAddress=:emailAddress";
        $statement = $pdoConnection->prepare($query);
        $statement->bindValue(':emailAddress', $email, PDO::PARAM_STR);
        $statement->execute();  
        $rowCount = $statement->fetchColumn(0);
        if($rowCount > 0){
            //email already exists. user is already registered and therefore has a password
            //need to show error to user to say they are already registered and should use the login form.
            $data['email_already_exists'] = true; 
            return $data;
        }else{
            $data['email_already_exists'] = false;

            $hashedPassword = password_hash($password, PASSWORD_DEFAULT, ['cost' => 12]);
            $query = "INSERT INTO users (emailAddress, password, isAvatarImage, profileImage, userPrivilegeID, displayName, userHasPassword) VALUES (:emailAddress, :password, :isAvatarImage, :profileImage, :userPrivilegeID, :displayName, :userHasPassword)";
            $statement = $pdoConnection->prepare($query);
            $statement->bindValue(':emailAddress', $email, PDO::PARAM_STR);
            $statement->bindValue(':password', $hashedPassword, PDO::PARAM_STR);
            $statement->bindValue(':isAvatarImage', $isAvatarImage, PDO::PARAM_INT);
            $statement->bindValue(':profileImage', $profileImage, PDO::PARAM_STR);
            $statement->bindValue(':userPrivilegeID', $userPrivilegeID, PDO::PARAM_INT);
            $statement->bindValue(':displayName', $display_name, PDO::PARAM_STR);
            $statement->bindValue(':userHasPassword', $userHasPassword, PDO::PARAM_INT);
            $statement->execute();
            $data['user_id'] = $pdoConnection->lastInsertId();
            return $data;
        }
    }catch(PDOException $e){
        //throw new pdoDbException($e); 
        $data['exception_occurred'] = true;
        $data['exception_message'] =  $e->getMessage();
        return $data;
    }
}

One solution I can think of is to put a timer on the "create account" button so that multiple ajax requests can't be made so close together?

Edit

After reading some of your solutions I am looking into making the email field unique. thanks I am working in phpMyAdmin so is it as simple as just pressing "unique"(highlighted in the image)?

enter image description here

Edit 2

I tried creating the email as a unique key and i get the following error:

enter image description here

Edit 3

To solve the error above (in Edit 2) I changed the collation of the email address field from utf8mb4_unicode_ci to utf8_unicode_ci and then I tried pressing "unique" again and it worked. Thank you all

Sarah
  • 1,943
  • 2
  • 24
  • 39
  • 3
    The correct way to prevent duplicates is to use unique constraints/indexes in the database, not competing queries in the application. – Gordon Linoff Jan 24 '17 at 12:12
  • 1
    Apparently yes, it is as simple as that, but if you are afraid of it, make a copy of the table and test it on the copy before doing it on the other table. – Marc Compte Jan 24 '17 at 12:36

4 Answers4

1

You may add UNIQUE CONSTRAINT to your email field into the database. This way if you try to insert an email that already exists, it fails.

ollaw
  • 2,086
  • 1
  • 20
  • 33
1

Make the email field in your database unique. I don't recommend PK because users may want to change their email address.

Emaro
  • 1,397
  • 1
  • 12
  • 21
1

I always recommend doing validations on both the server and the client.

In this case, as everyone mentioned, in the server (Database) you should define a unique constraint on the e-mail field, so repeated e-mails will never actually happen.

I would also recommend to disable the "Create account" button after the first click on it. You could even change the name of the button to "... please wait" to indicate the operation is being executed. When you get the response from the server (either success or failure) you can enable it again. This will avoid any unnecessary successive calls to the server in the first place.

Marc Compte
  • 4,579
  • 2
  • 16
  • 22
0

You have returned data in else case and not in if case now there are two options. instead of returning data in else block return it outside the else block or return data in if case too.

This causes when it founds the email id but does not return it will be as false if you check hence it allows to register the user to use same email id for two different accounts

M A SIDDIQUI
  • 2,028
  • 1
  • 19
  • 24