Getting error: execution time limit exceeded with module

exports = function(arg){
  
  const bcryptjs = require("bcryptjs");
  
  return bcryptjs.hashSync("test", 10);
};

When I run it, I get the error after a certain amount of time. Same response when I use it in a webhook.
Anything I can do?

Hi @Jo_Rokegem ,

Thank you for contacting MongoDB Community Forums - My name is Josman and I am happy to assist you with this issue.

Currently, there are several constraints for running Realm functions under your Realm Application. Among the other constraints it seems that you are hitting:

  • Function runtime is limited to 90 seconds.

Anything I can do?

I reproduced your issue in my Realm App environment and unfortunately, we do not support all npm libraries. However, let me investigate if there is another library that you can use or if there is a workaround to overcome this situation.

Kind Regards,
Josman

1 Like

I tried the npm library bcrypt first, but it gave me these errors:

error: 
failed to execute source for 'node_modules/bcrypt/bcrypt.js': FunctionError: failed to execute source for 'node_modules/@mapbox/node-pre-gyp/lib/node-pre-gyp.js': FunctionError: failed to execute source for 'node_modules/npmlog/log.js': FunctionError: failed to execute source for 'node_modules/gauge/index.js': FunctionError: failed to execute source for 'node_modules/gauge/has-color.js': TypeError: Cannot access member 'platform' of undefined
	at isWin32 (node_modules/gauge/has-color.js:14:10(2))
	at node_modules/gauge/has-color.js:11:25(30)

	at require (native)
	at node_modules/gauge/index.js:17:24(50)

	at require (native)
	at node_modules/npmlog/log.js:15:21(42)

	at require (native)
	at node_modules/@mapbox/node-pre-gyp/lib/node-pre-gyp.js:31:19(76)

	at require (native)
	at node_modules/bcrypt/bcrypt.js:11:26(26)

maybe this helps

Hello @Jo_Rokegem

I tried the npm library bcrypt first, but it gave me these errors:

Yes, I reproduced the same errors as well. Even when trying a different version of the same library. Please, allow me some time to investigate this further and I will reply to you as soon as possible with my findings.

Thank you for your patience on the matter and sorry for the inconvenience this is causing to your application.

Kind Regards,
Josman

1 Like

Hello @Jo_Rokegem

Have you considered using the crypto node standard library instead? The crypto.scryptSync function and other hashing functions in that library will run significantly faster since they are not written in pure JavaScript like bcryptjs. The bcryptjs.hashSync function ends up being rate limited on our end since it uses pure JavaScript and it’s computationally expensive. I might have found some performance improvements that could be made within our rate limiter, but even when disabling it completely, it takes ~7 seconds to run your bycryptjs function.

At a glance, the “bcrypt” package doesn’t seem to be supported because we currently don’t support process.platform.

Your function using the standard node library, will be as follows:

exports = function(arg){
  const crypto = require('crypto');
  // Using the factory defaults.
  const key1 = crypto.scryptSync('test', 'salt', 10);
  console.log(key1.toString('hex'));  // '72f47a5f6....b96a9d7'
};

Please let me know if you have any additional questions or concerns regarding the details above.

Kind Regards,
Josman

2 Likes

So because there is no equivalent to bycrypt’s compare function do you just end up using scryptSync twice? For example if you were hashing a password the first would be to salt and hash the user’s password before it is stored in a collection. Then when you want to compare the two you would salt and hash the password submitted by the client and look for a match?

After experimenting with this I came up with some examples that can serve as starting points.

To generate a hash using scrypt:

exports = ({input}) => {
  
  const crypto = require('crypto');

  try {
    
/*
Note that the size argument in the randomBytes method doesn't 
necessarily need to match the key length argument in the scryptSync 
method. It just creates a longer salt. You might get by with just 16 
instead of 32. I'm not an expert on best practices so DYOR.
*/
    const salt = crypto.randomBytes(32).toString('hex');
    
    const hash = crypto.scryptSync(input, salt, 32).toString('hex');

/*
Here I'm concatenating the salt and hash but alternatively you could
you could just store the salt and hash separately in your database;
*/
    const concatenatedString = salt.concat("", hash);
   
    return concatenatedString;
    
  } catch(err){
    
    throw(err);
    
  }
  
};

To compare a hashed input to a stored hash value using scrypt:

exports = ({
  input,
  storedValue
}) => {
  
  const crypto = require('crypto');
    
  try {
    
/*
The length of the substring will depend on the length of the salt;
As already mentioned you could instead store the salts and hashes 
separately in your database;
*/
    const salt = storedValue.substring(0, 64);
    
    const hash = crypto.scryptSync(input, salt, 32).toString('hex');
    
    const concatenatedString = salt.concat("", hash);
    
    if(concatenatedString === storedValue){
      
      return true;
      
    } else {
      
      return false;
  
    }
    
  } catch (err) {
    
    throw(err);  
    
  }
  
};

I’ve also confirmed that regular old bcrypt works just fine in Google Cloud. Here is an example of how you could stick with bcrypt and still use it with Atlas by calling a Google Cloud Function. In Google Cloud use the following code to generate a hash using bcrypt:

const functions = require('@google-cloud/functions-framework');

const bcrypt = require('bcrypt');

const getHash = async({ input }) => {

  const hash = bcrypt.hashSync(input, 10);
  
  return hash;

};

functions.http('generateBcryptHash', async(req, res) => {
  
    try {
      
      const input = req.body.input;

      const hash = await getHash({ input });

      res.json(hash);
      
    } catch (err){

      res.json({err:{message:err.message}});

    }

  }

});

To call the Google Cloud Function in Atlas you could use:

exports = async({
 input
}) => {
      
    try {
      
      const response = await context.http.post({
        url: "https://us-central1-yourgoogleprojectname.net/generateBcryptHash",
        body:{
          input
        },
        encodeBodyAsJSON: true
      });
      
      const hash = JSON.parse(response.body.text());
       
      if(hash.err){
        
        throw new Error(hash.err.message);
        
      } else {
        
        return hash;
        
      }
      
    } catch(err){
      
      throw(err);
      
    }
  
};

Then in Google Cloud for the compare function:

const functions = require('@google-cloud/functions-framework');

const bcrypt = require('bcrypt');

const compare = ({ input, storedValue }) => {
  
  const result = bcrypt.compareSync(input, storedValue);
  
  return result;

};

functions.http('compareBcryptHash', async(req, res) => {

    try {
      
      const input = req.body.input;

      const storedValue = req.body.storedValue;

      const isMatch = await compare({ input, storedValue });

      res.json(isMatch);

    } catch (err){

      res.json({err:{message:err.message}});

    }

  }

});

Finally in Atlas call the Google Cloud Function to make a comparison:

exports = async function({
  input,
  storedValue
}){

  try {
    
    const response = await context.http.post({
      url: "https://us-central1-yourgoogleprojectname.cloudfunctions.net/compareBcryptHash",
      body:{
        input,
        storedValue
      },
      encodeBodyAsJSON: true
    });
  
    const isMatch = JSON.parse(response.body.text());
    
    if(isMatch.err){
      
      throw new Error(isMatch.err.message);
      
    } else {
      
      return isMatch;
      
    }
      
  } catch(err) {
    
    throw(err);
    
  }

};