Concurrency issue when timing the encryption of bcrypt and creating a new document in MongoDB

My website has a register function that creates a new document with the user's credentials. Below is my implementation, where the input fields for username and password are stored in an object called data:

let users = db.collection('users');

let query = sanitize(data);

users.findOne({username: query.username}).then(res=>{
    if (res){
        socket.emit('usercreated', {
            msg: `User: ${query.username} already exists.`
        });
        return;
    }

    const h = query.username + query.password;

    bcrypt.hash(h, 13, (err, hash)=>{
        users.insert({username: query.username, password: hash}, (err, user)=>{
            if (err){
                socket.emit('usercreated', {
                    msg: `DB is having issues. Please contact admin.`
                });
                return;
            }
            socket.emit('usercreated', {
                msg: `User ${query.username} has been created.`
            });
        });
    });
})

However, a problem arises when a user spams the submit button for username and password. The 'res' variable does not recognize that the user already exists due to the asynchronous nature of the bcrypt.hash function.

I have attempted another method to check 'res' after bcrypt completes its hashing, but this approach does not solve the issue:

let users = db.collection('users');

let query = sanitize(data);

users.findOne({username: query.username}).then(res=>{
    const h = query.username + query.password;

    bcrypt.hash(h, 13, (err, hash)=>{
        if (res){
            socket.emit('usercreated', {
                msg: `User: ${query.username} already exists.`
            });
            return;
        }
        users.insert({username: query.username, password: hash}, (err, user)=>{
            if (err){
                socket.emit('usercreated', {
                    msg: `DB is having issues. Please contact admin.`
                });
                return;
            }
            socket.emit('usercreated', {
                msg: `User ${query.username} has been created.`
            });
        });
    });
})

What would be a more effective way to properly check if the user already exists before proceeding with the insertion?

Answer №1

One issue arises not from the 1-second resolution time of bcrypt.hash, but rather from the way in which you are managing the process.

When dealing with spamming, the situation resembles a classic readers-writers problem. While there are numerous solutions, a straightforward adjustment of mutex locks should suffice.

class NamedLocks {
    constructor() {
        this._pid = {};
    }

    acquire(pid) {
        if (this._pid[pid]) {
            // the process is locked
            // handle it accordingly
            return Promise.reject();
        }

        this._pid[pid] = true;
        return Promise.resolve();
    }

    release(pid) {
        this._pid[pid] = false;
    }
}


let users = db.collection('users');
let query = sanitize(data);
const userLocks = new NamedLocks();

userLocks.acquire(query.username).then(() => {
    users.findOne({
        username: query.username
    }).then(res => {
        const h = query.username + query.password;

        bcrypt.hash(h, 13, (err, hash) => {
            if (res) {
                socket.emit('usercreated', {
                    msg: `User: ${query.username} already exists.`
                });
                return;
            }
            users.insert({
                username: query.username,
                password: hash
            }, (err, user) => {
                if (err) {
                    socket.emit('usercreated', {
                        msg: `DB is having issues. Please contact admin.`
                    });
                    return;
                }
                socket.emit('usercreated', {
                    msg: `User ${query.username} has been created.`
                });
                userLocks.release(query.username);
            });
        });
    })    
}).catch((e) => {
    // handle spamming
})

Answer №2

To prevent excessive server requests when a user repeatedly hits the submit button, consider implementing function throttling. This technique limits the function to be called only once within a specified time frame. For instance, with a timeout of 1 second, if a user rapidly clicks the button, only the first click will register, while subsequent clicks within that second will be ignored. You can utilize the _.throttle function from Underscore.js, or create your own version based on their implementation.

Answer №3

After a thorough investigation, I have the solution. It is imperative to encrypt the password first before verifying the existence of the user.

const usersList = db.collection('users');

const searchQuery = sanitize(data);
const hashedPassword = searchQuery.username + searchQuery.password;
bcrypt.hash(hashedPassword, 13, (error, hashedResult) => {
    usersList.findOne({username: searchQuery.username}).then(response => {
        if (response) {
            socket.emit('usercreated', {
                msg: `The user: ${searchQuery.username} already exists.`
            });
            return;
        }
        usersList.insert({username: searchQuery.username, password: hashedResult}, (error, userResult) => {
            if (error) {
                socket.emit('usercreated', {
                    msg: `Database is encountering issues. Please contact the administrator.`
                });
                return;
            }
            socket.emit('usercreated', {
                msg: `User ${searchQuery.username} has been successfully created.`
            });
        });
    });
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The powerful combination of ES6 and sequelize-cli

How can I execute sequelize migrations and seeds written in ES6? I attempted to use babel-node, but encountered a strange error. Command node_modules/babel-cli/lib/babel-node.js node_modules/sequelize-cli/bin/sequelize db:seed Error node_modules/b ...

Issue with saving documents using mongoose's save function

I am making updates to the document "Order.attempt_status" by changing it from "open" to "closed" with the code snippet below: AttemptSchema.pre('save', function (next) { var attempt = this; Order.findById(this.order).exec(function(err, orde ...

Using AngularJS filters to search through various fields of data

My goal is to conduct a search using multiple fields of a repeating pattern in combination. I am facing an issue where searching by query.$ model does not allow me to search from multiple fields. Specifically, I want to search for the number 1234 along wi ...

jQuery Load - Oops! There seems to be a syntax error: Unexpected token <

Error: Uncaught SyntaxError: Unexpected token < I encountered the error message mentioned above while trying to execute a jQuery load function: $('#footer').load(UrlOfFooterContent); The variable UrlOfFooterContent holds the URL of an MVC c ...

Monitor the console log in the Android Browser for any potential issues

My website runs smoothly on all browsers, except for the Android browser when using JavaScript. I am aware that I can activate JavaScript debugging with about:debug, but I am unsure of where to find the console log/errors. How can I locate and check my b ...

Issue with Date Picker not properly storing selected date in MySQL database

My goal is to save the datepicker date to MySQL by selecting the date format using JavaScript. I have verified that the date format appears correct as YYYY-MM-DD when logging to the console. However, when I try to execute an INSERT query to MySQL, the date ...

AngularJS combined with MVC is causing an issue where the table fails to refresh following an HTTP post request

When working with an $http.post() call and trying to update an HTML table in the success() callback, I encountered an issue: .success(function (data) { $scope.categories.push(data); }); Here is the HTML code for the table: <tbody> ...

Issue with the iteration process while utilizing Async.waterfall in a for-loop

This snippet may seem simple, but it represents a real issue in my current code base. When attempting to log the index of a for-loop within an async.waterfall function, I am encountering a peculiar situation where the index value is 2 instead of expected ...

`No valid form submission when radio buttons used in AngularJS`

Within my form, I have a single input text field that is required (ng-required="true") and a group of radio buttons (each with ng-model="House.window" and ng-required="!House.window"). Interestingly, I've discovered that if I select a radio button fir ...

Adjust the property to be optional or required depending on the condition using a generic type

const controlConfig = >T extends 'input' | 'button'(config: Config<T>): Config<T> => config; interface Config<TYPE extends 'input' | 'button'> { type: TYPE; label: string; ...

"Learn how to efficiently incorporate data into data-binding in VUE JS with just a

In my data binding, there is a string that reads as follows: bc-men,bc-men-fashion,bc-men-underwear I want to create an input field where I can enter "bc-some-category", click "Add", and have it appended to the end of the list with a comma in front. Her ...

How can I dynamically update the status displayed in a DIV tag using PHP code?

I'm working on a web page where I am downloading data one by one in a continuous loop. Once each download is complete, I need to update the status displayed in a DIV tag on the webpage. The server connection and data download are handled by PHP code, ...

What causes the maximum update depth exceeded error in React when trying to set data to the context?

When building my React project, I implemented a context to share the selected currency across components. While the context functionality is working well, I encountered a small issue regarding setting a default currency. At the start of the web applicati ...

Using Angular's built-in dependency injection with the $resource factory allows for

Question regarding Dependency Injection on factory resource: As far as I know, the following example is the recommended approach for injecting dependencies in Angular1: angular.module('myApp').factory('Resource', Resource); Resource. ...

What is the best way to pass a dynamically generated component as the ng-content for another dynamically generated component?

Need help with creating a modal for our library using viewRef.createComponent. I want to generate Component A and embed Component B inside it, utilizing <ng-content> to fetch content from Component B. Component A serves as the modal template, while ...

I am currently in the process of conducting an automated test focused on the creation of a new Facebook account

I am currently facing a challenge while attempting an automated test on Facebook. The issue lies in clicking the "Create Account" button using FindElement by id, which is not functioning as expected. public void createAccount(String firstName, String lastN ...

Provide a unique <li> attribute for the JavaScript function to utilize

Is there a way to pass specific attributes from dropdown options to a javascript function? I have tried using .data() and .attr(), but the console keeps showing "undefined". Any suggestions on how to achieve this in a cleaner and simpler way would be gre ...

What is the best way to retrieve an object from every function?

I'm dealing with a <div> containing radio buttons: <div id='RB-01'> <span>Item_1</span><input type='radio' name='RB01' value='1'><br /> <span>Item_2</span><inp ...

Updating Object Properties in Vue.js 2.0 using Methods

I am facing an issue where I have an object with blank properties in my component's data. I want to update these properties using a method that is triggered by a click event on one of the listed elements. However, when I check the click event in the c ...

Discovering the wonders of React and Javascript through the powerful Map function

I've exhausted all my knowledge of map functions and syntax, but I keep encountering the error TypeError: Cannot read property 'action' of undefined. This issue seems to stem from this line: constructor(data=[]). Typically, I am only familia ...