0

I'm attempting to write a simple validation function that takes a mongo_id and checks to see if it exists. I am able to go this far, but I'm not able to pass that result back to the route that calls the function.

Here is that function which is based on an example that is floating around... It does work.

validateUserId = function(userId) {

  var MongoClient = require('mongodb').MongoClient;
  var assert = require('assert');

  var options = {
    mongos: {
      ssl: false,
      sslValidate: false,
    }
  }
  isValid = false;
  MongoClient.connect(vars["MONGO_URL"], options, function(err, db) {
    assert.equal(null, err);
    var q = db.collection("users").find({
      _id: userId
    }, {
      _id: 1
    }).toArray(function(err, record) {
      assert.equal(null, err);
      record.forEach(function(r) {
        console.log(r);
        if (r._id == userId) {
          isValid = true;
        }
      });
      db.close();
      return isValid;
    })
  });
  return isValid;

};

This function does not return anything.

How do I properly modify this code to return a true/false value based on the result of the query?

The idea is to not have to put this code into every route where the validation needs to occur and simply call validateUserId() before performing other tasks (which does not require access or connection to the mongodb.)

Ex:

app.get("/performVerifiedAction",function(req,res){
 if(validateUserId(req.query['userId'])){
  res.send("You may pass");
 }else{
  res.send("Can't figure out who you are");
 }
 return true;
});
redcap3000
  • 928
  • 1
  • 8
  • 15

2 Answers2

0

validUserId is really an asynchronous function.

By convention in Node.js async functions receive a callback in their last argument, see How to write asynchronous functions for Node.js

Following Node.js style callbacks, the callback argument is a function that receives the result of the async operation in its second argument.

// callback is a function that gets isValid as its second arguments
// by convention the first argument of these callback function is any error 
// that may occur in this async operation.
validateUserId = function(userId, callback) {

  var MongoClient = require('mongodb').MongoClient;
  var assert = require('assert');

  var options = {
    mongos: {
      ssl: false,
      sslValidate: false,
    }
  }
  isValid = false;
  MongoClient.connect(vars["MONGO_URL"], options, function(err, db) {
    assert.equal(null, err);
    var q = db.collection("users").find({
      _id: userId
    }, {
      _id: 1
    }).toArray(function(err, record) {
      assert.equal(null, err);
      record.forEach(function(r) {
        console.log(r);
        if (r._id == userId) {
          isValid = true;
        }
      });
      db.close();
      return callback(null, isValid); // instead of return isValid;
    })
  });
  return callback(null, isValid); // instead of return isValid;

};

This is how you can use validUserId async function:

app.get("/performVerifiedAction",function(req,res){
  validateUserId(req.query['userId'], function(error, isValid) {
    if(isValid) {
      res.send("You may pass");
    } else {
      res.send("Can't figure out who you are");  
    }
  });
});

Please note that you can improve the above code by a lot by handling the errors (passing the errs to the callback and returning from the function). For example:

  MongoClient.connect(vars["MONGO_URL"], options, function(err, db) {
    assert.equal(null, err);
    // ...

Can be improved to:

  MongoClient.connect(vars["MONGO_URL"], options, function(err, db) {
    if(!!err) { 
      return callback(err); 
    }
    //...
Community
  • 1
  • 1
homam
  • 1,945
  • 1
  • 19
  • 26
  • Tried this out and got "Error: Can't set headers after they are sent." – redcap3000 Mar 18 '16 at 23:06
  • @redcap3000 the problem was that I forgot to exit from the async function after callbacks. It should be fine now (I added `return callback(null, isValid);`) – homam Mar 18 '16 at 23:12
  • It immediately sends back that its invalid... then when it receives the response I get : Error: Can't set headers after they are sent and the process exits. – redcap3000 Mar 18 '16 at 23:28
0

Node.js approach to calling external services is asynchronous. That means that calling MongoDb functions does not block the program execution, so your validateUserId function will always return false because the MongoDb find callback will be called much later and will not modify already returned value.

To use this validation on many routes you can modify this method as Express middleware as follows:

//Avoid connecting to MongoDb on every request, make the connection persistant

var MongoClient = require('mongodb').MongoClient;
var options = {
  mongos: {
    ssl: false,
    sslValidate: false,
  }
};

var mongo = {
  db: null
};

MongoClient.connect(vars["MONGO_URL"], options, function(err, db) {
  if (err) {
    console.error("Error connecting to MongoDB:", err);
    process.exit(1);
  }
  mongo.db = db;
});

// Create a middleware method that will be called in request processing chain before your handler

var validateUserId = function(req, res, next) {
  var userId = req.query['userId'];
  
  // Use findOne to validate user id. It is faster and simplier
  mongo.db.collection("users").findOne({_id: userId}, {_id: 1}, function(err, record) {
    if (err) {
      return res.status(500).send("Error getting user");
    }
    if (!record) {
      return res.status(403).send("Can't figure out who you are");
    }
    next();
  });
};

Then you can use this middleware in the following way:

app.get("/performVerifiedAction", validateUserId, function(req,res){
 res.send("You may pass");
});
Constantine Poltyrev
  • 1,015
  • 10
  • 12
  • I tried this code both inside and outside of the appRouter context: received these errors : TypeError: Cannot call method 'collection' of null .. and app.get("/performVerifiedAction", validateUserId, function(req,res){ ^ ReferenceError: validateUserId is not defined – redcap3000 Mar 18 '16 at 23:22