Nodejs/mongodb- Checking if a user has Admin privileges (token based auth)

Viewed 11310

In my express/mongoose app I'm defining verifyOrdinaryUser function to check if a user is authenticated on a server. Which works well, however I've defined verifyAdmin function below to check if a user has admin privileges also (I'm using passport-local-mongoose module to define user Schemas). As you can see user's token is checked in verifyOrdinaryUser() function, it will load a new property named decoded to the request object which I'm trying to reuse in verifyAdmin, and that's when I'm getting the following error in postman.

{
  "message": "Cannot read property '_doc' of undefined",
  "error": {}
}

The following is the

var User = require('../models/user');
var jwt = require('jsonwebtoken'); 
var config = require('../config.js');

exports.getToken = function (user) {
    return jwt.sign(user, config.secretKey, {
        expiresIn: 3600
    });
};

exports.verifyOrdinaryUser = function (req, res, next) {
    // check header or url parameters or post parameters for token
    var token = req.body.token || req.query.token || req.headers['x-access-token'];

    // decode token
    if (token) {
        // verifies secret and checks exp
        jwt.verify(token, config.secretKey, function (err, decoded) {
            if (err) {
                var err = new Error('You are not authenticated!');
                err.status = 401;
                return next(err);
            } else {
                // if everything is good, save to request for use in other routes
                req.decoded = decoded;
                next();
            }
        });
    } else {
        // if there is no token
        // return an error
        var err = new Error('No token provided!');
        err.status = 403;
        return next(err);
    }
};

exports.verifyAdmin = function(req,res,next){
    if(req.decoded._doc.admin !== true)  {
        return next(err);
    }else {
        return next();
    }
};

I'm sure I've messed up something in the verifyAdmin function. Middleware order looks right to me Suggestions are welcome

thank you

EDIT: Middleware goes here from app.js

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());

// passport config
var User = require('./models/user');
app.use(passport.initialize());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
app.use('/users', users);
app.use('/dishes',dishRouter);
app.use('/promotions',promoRouter);
app.use('/leadership',leaderRouter);
16 Answers

If anyone was using the recommended signature of:

exports.jwtPassport = passport.use(new JwtStrategy(opts,
    (jwt_payload, done) => {
        console.log("JWT payload: ", jwt_payload);
        User.findOne({_id: jwt_payload._id}, (err, user) => {
            if (err) {
                return done(err, false);
            }
            else if (user) {
                return done(null, user);
            }
            else {
                return done(null, false);
            }
        });
    }));

exports. verifyOrdinaryUser = passport.authenticate('jwt', {session: false});

then, you might notice that the 'user' object is returned from the strategy. Thus, by setting:

exports.verifyAdmin = function(params, err, next) {
    if (params.user.admin){
      return next();
    } else {
      var err = new Error('Only administrators are authorized to perform this operation.');
      err.status = 403;
      return next(err);
    }
};

After which the following authorization signature in the route should work:

...
.get(Verify.verifyOrdinaryUser, Verify.verifyAdmin, function(req,res,next){...}

Though, as Emmanuel P. mentioned, there is a cleaner way.

The issue you are facing is due to the fact there is no _doc under decoded, the right call should be as per the following,

req.decoded.data.admin

To make sure of what I'm saying, just add console.log(req.decoded) somewhere in your verifyAdminfunction and you should find an output similar to the following on your console

MAC:passport username$ npm start
...
Connected correctly to server
{ data: 
   { _id: '59e011e9209e2613c5492b1d',
     salt: '...',
     hash: '...',
     username: 'username',
     __v: 0,
     admin: false },
  iat: 1508012930,
  exp: 1508016530 }

GET /dishes 401 18.242 ms - 70

Hence, your code shall be as per the following,

exports.verifyAdmin = function (req, res, next) {

    if (req.decoded.data.admin == true) { //NOTICE THE CHANGE "_doc --> data"
        next();
    } else {
        // if the user is not admin
        // return an error
        var err = new Error('You are not authorized to perform this operation!');
        err.status = 403;
        return next(err);
    }
};

Have fun ;)

exports.verifyAdmin = ((req,res,next) =>{
        const name = req.body.username
        console.log(name)
        User.findOne({username: name},(err,user) => { 
            if(err) {
                next(err)
            }
            else if(!user) {
                next(new Error("user not found"))
            }
            else {
                if(!user.admin) {
                    next(new Error("you are not an admin"))
                }
                else {
                    next()
                }
            }                         
        });
    });

it worked in my case. You can use this function after verifying the user as a normal user.

Related