Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check multi permission by middleware

I already resolve this problem. I find Express.js role-based permissions middleware and use it. It's great and I will re-write my code!


I'm want to check multi permistion but it not working for me.

I create 3 middlwares to check Permistion: requiredAuth, checkAdmin and checkCompanyManager.

Model: User: {name, permistion, isBlocked, company: {id, userPermistion}}

requiredAuth funtion will check and find signedUser, and set it tores.locals.user

const checkAdmin = (req, res, next) => {
    let user = res.locals.user
    if (user.permission === 2) next()
    else res.json({errors: "Only admin can do this action"})
}

const checkCompanyManager = (req, res, next) => {
    let user = res.locals.user
    let companyId = req.body.companyId ? req.body.companyId : req.query.companyId
    if (user.company.id && user.company.id.equals(companyId)
        && user.company.userPermistion === 1) next()
    else res.json({errors: "Only company member can do this action"})
}

And last, I use all in router to check action block user (Only admin or company manager can block user)

router.post('/admin/block-by-ids',
    requiredAuth,
    checkAdmin || checkCompanyManager,
    userController.blockByIds
)

But it's not working, because if checkAdmin wrong, it's break and return json, not run checkCompanyManager I can solve this problem as follows:

router.post('/admin/block-by-ids',
    requiredAuth,
    (req, res, next) => {
        let user = res.locals.user
        let companyId = req.body.companyId
        if ((user.permission === 2) ||
            (user.company.id && user.company.id.equals(companyId) &&
                user.company.userPermistion === 1)) {
            next()
        } else next("Only admin or company manager can do this action")
    },
    userController.blockByIds
)

But it's not fun! I want only use middleware to check and do not want to write code again. How can I do this? I want an idea from you!

like image 295
Nguyen Van Tuan Avatar asked Oct 23 '25 14:10

Nguyen Van Tuan


1 Answers

The || operator does not do what you think it does. It returns the first truthy value:

var a = 1 || 2; // a is 1

What you need is an OR middleware. Something like:

function or (middleware1, middleware2) {
    return function (req, res, next) {
        var alreadyCalledNext = false;
        function resolve () {
            if (!alreadyCalledNext) {
                alreadyCalledNext = true;
                next();
            }
        }
        middleware1(req,res,resolve);
        middleware2(req,res,resolve);
    }        
}

router.post('/admin/block-by-ids',
    requiredAuth,
    or(checkAdmin, checkCompanyManager),
    userController.blockByIds
)

But the above implementation runs into another problem. Once you've sent res.json you cannot send another response. So if either checkAdmin or checkCompanyManager fails you need to stop them from sending res.json unless both fails. So you need to stub res and pass a fake res (just like what we did with next above):

function or (middleware1, middleware2) {
    return function (req, res, next) {
        var alreadyCalledNext = false;
        function resolve () {
            if (!alreadyCalledNext) {
                alreadyCalledNext = true;
                next();
            }
        }
        var jsonCount = 0;
        var fakeRes = {
            locals: res.locals,
            json: function (data) {
                jsonCount ++;
                if (jsonCount >= 2) { // both must fail for OR to fail
                    res.json(data);
                }
            }
        }

        middleware1(req,fakeRes,resolve);
        middleware2(req,fakeRes,resolve);
    }        
}

This should work.


IMHO the solution above feels over-engineered. I would personally make checkAdmin and checkCompanyManager regular functions returning boolean then wrap them in a checkPermissions middleware:

const isAdmin = (req,res) => {
    let user = res.locals.user
    return user.permission === 2
}

const isCompanyManager = (req,res) => {
    let user = res.locals.user
    let companyId = req.body.companyId ? req.body.companyId : req.query.companyId
    return user.company.id && user.company.id.equals(companyId) && user.company.userPermistion === 1
}

const checkPermissions = function (checks) {
    return (req, res, next) => {
        // Call next if any check passes:
        for (let i=0; i<checks.length; i++) {
            if (checks[i](req,res)) return next();
        }

        res.json({errors: "You don't have authorization to do this action"})
    }
}

router.post('/admin/block-by-ids',
    requiredAuth,
    checkPermissions([isAdmin, isCompanyManager]),
    userController.blockByIds
)
like image 143
slebetman Avatar answered Oct 25 '25 05:10

slebetman