Search code examples
node.jsrestapi-designpassport.js

nodejs passport - use same routes in api but return different sets of data based on permissions


Not sure of a clean way to go about his. Let's say I have this endpoint:

GET /api/Books/

For the user on the webservice, this will return only the user's resources. It might look a little something like this:

exports.getBooks = function(req, res) {
  // find all books for user
  BookModel.find({ userId: req.user._id }, function(err, books) {
    if (err)
      res.send(err);

    res.json(books);
  });
};

The web service using the api needs the user to be logged in first. I can achieve this using a basic passport strategy to ensure authentication. But let's say I have an admin account that needs to see ALL books ever recorded. What's more is that the admin account and user accounts have completely different properties so assigning a Boolean for permissions is not enough. Using the same endpoint:

GET /api/Books

I see no reason to write another endpoint to achieve this. However the difference would look like this:

exports.getBooks = function(req, res) {
   // find all books in the database
   BookModel.find({}, function(err, books) {
     if (err)
       res.send(err);

     res.json(books);
   });
 };

However I cannot come up with a clean way to achieve this while also using the passport middlewear as it is intended like so:

router.route('/books')
  .post(authController.isAuthenticated, bookController.postBooks)
  .get(authController.isAuthenticated, bookController.getBooks);

The function isAuthenticated will will only verify whether or not the user requesting resources has permission and does not change the way the controller behaves. I'm open to ideas.

ANSWER

The user @ZeroCho suggested to check user properties in req.user object to determine what should be sent back. This was more simple than I expected. In my implementation for passport.BasicAuth strategy, I check which table has a matching doc. Once the user is found in the common user or Admin user table all you do is add a property in the isMatch return object.

   // Basic strategy for users
   passport.use('basic', new BasicStrategy(
      function(email, password, done) {
       verifyUserPassword(email, password,
    function(err, isMatch) {
      if(err) { return done(err); }

      // Password did not match
      if(!isMatch) { return done(null, false); }

      // Success
      var userInfo = {
        email: email,
        isAdmin: isMatch.isAdmin || false,
        businessID: isMatch.businessID || false
      };

      return done(null, userInfo);
    });
  })
    );

Then you can check if .isAdmin or .businessID is valid in your requests.


Solution

  • Just separate your controller by if statement

    exports.getBooks = function(req, res) {
      if (req.user.isAdmin) { // or some other code to check user is admin
        // find all books in the database
        BookModel.find({}, function(err, books) {
          if (err)
            res.send(err);
          res.json(books);
        });
      } else {
        BookModel.find({ userId: req.user._id }, function(err, books) {
          if (err)
            res.send(err);
          res.json(books);
        });
      }
    };