Search code examples
node.jsldapauthorizationmiddleware

Nodejs with LDAP. How to authorize user in multiple groups having same privileges


I'm using LDAP-authentication in my web page to link the user logging in to the proper groups he has in Active Directory. I want to use these groups to perform authorization when the user makes requests and have been succesfull until I noticed that when the user is part of multiple groups that have the privileges for the same resource I'm running in to a "Error: Can't set headers after they are sent.".

The error is caused by the function that the middleware calls to check if the group should have access to the resource. Because this is called multiple times when multiple groups have the same privilege it ends up calling next() more than once.

How should I change things to be able to have a user in multiple groups that have access to the same resources? I should somehow be able to return a value that blocks any further calls to the authorization function, but then I'd have to create the response in the middleware and not in the function.

All of this might be caused by my lack of understanding how the middleware works, so if there are some logical errors in my implementation I'm happy to get some feedback.

Here is the middleware that is responsible of the authorization:

app.use(function(reg, res, next){
        var checkSpesificGroup = false;
        var bearerToken;
        console.log(reg.body);
        var bearerHeader = reg.headers.authorization;
        var forbidden=true
        for(käyttäjänOikeus in käyttäjänOikeudet){
                if(käyttäjänOikeudet[käyttäjänOikeus].indexOf(reg.url) > -1){
                        forbidden = false;
                        checkYourPrivilege(käyttäjänOikeus, bearerHeader, res, reg, next);
                }
        }


        if(public.indexOf(reg.url) > -1){
                console.log('public')
                forbidden = false;
                bearerToken = decodeToken(bearerHeader);
                if(bearerToken !== false){
                        decoded = verifyToken(bearerToken);
                        if(decoded !== false){
                                if (checkIfExpired(decoded.expires)){
                                        console.log('checkifexpired success');
                                        res.setHeader('Tokenexpired',false);
                                        next();
                                }else{
                                        console.log('checkifexpired failed');
                                        res.setHeader('Tokenexpired',true);
                                        next();
                                }
                        }else{
                                console.log('decoding failed');
                                res.setHeader('Tokenexpired',false);
                                next();
                        }
                }else{
                        console.log('no bearer token');
                        res.setHeader('Tokenexpired',false);
                        next();
                }
        }
        if(forbidden){
                console.log('forbidden')
                return res.status(403).send({
                        success: false,
                        message: "Forbidden"
                });
        }
});

Here is the function the middleware calls to perform authorization for resources that are not public:

function checkYourPrivilege(checkSpesificGroup, bearerHeader, res, reg, next){
        bearerToken = decodeToken(bearerHeader);
        if (bearerToken !== false) {
                decoded = verifyToken(bearerToken)
                if (decoded !== false){
                        if (checkIfExpired(decoded.expires)){
                                if(checkSpesificGroup){
                                        if(decoded.oikeudet[checkSpesificGroup] == true){
                                                console.log("Tokenisi vanhentuu" + Math.abs(decoded.expires - Math.floor(Date.now())/100$
                                                reg.decoded = decoded;
                                                res.setHeader('Tokenexpired',false);
                                                next()
                                        }else{
                                                return;
                                        }
                                }else{
                                        console.log("Tokenisi vanhentuu" + Math.abs(decoded.expires - Math.floor(Date.now())/1000) + "pä$
                                        reg.decoded = decoded;
                                        res.setHeader('Tokenexpired',false);
                                        next();
                                }
                        }else{
                                console.log("Token Expired");
                                return res.status(403).send({
                                          success: false,
                                          tokenstatus: "expired",
                                          message: "Token expired"
                                });
                        }
                }else{
                        return res.status(403).send({ success:false, message: 'Invalid token'});
                }
        } else {
                console.log('ei tokenia');
                return res.status(403).send({
                        success: false,
                        message: 'No token provided.'
                });
        }
};

Here is the array that contains the group information and the list that contains public resources:

var käyttäjänOikeudet =({
"JasentietoSecurity":[],
"JasenSurvivalkit": ['/haut.html','/haku'],
"JasenKV": ['/haut.html','/haku'],
"JasenJasenpalvelu": ['/haut.html','/haku'],
"JasenAsukastoimisto": ['/haut.html','/haku'],
"JasenMaksutiedot":['/varaushaku'],
"JasenSystem": ['/haut.html','/haku','/pk.html','/update','/loki.html', '/raportointi.html','/maksuhaku','/tulosraportti.html']
});

var public = ['/','/login.html','/core.js','/authenticate','/koti.html', '/styles.css','/favicon.ico']

Solution

  • I managed to get this working by taking the authorization function out of the for loop and calling it only once after the loop was done.

    I also needed to return a value from the authorization function instead of calling next() inside.

    Here is the changes I made to the middleware:

        var tarkistuslista = []; //Created a list to contain the groups the user is in
        for(käyttäjänOikeus in käyttäjänOikeudet){
                if(käyttäjänOikeudet[käyttäjänOikeus].indexOf(reg.url) > -1){
                        tarkistuslista.push(käyttäjänOikeus); //Instead of calling the authorization function, this part pushes the group in the list instead
                }
        }
        //Added the following if statement
        if (tarkistuslista.length > 0){
                privilege = checkYourPrivilege(tarkistuslista, bearerHeader)
                if(privilege == true){
                        next();
                }else{
                        return res.status(403).send(privilege);
                }
        }
    

    I also needed to do some changes to the authorization function:

    function checkYourPrivilege(tarkistuslista, bearerHeader){
            bearerToken = decodeToken(bearerHeader);
            if (bearerToken !== false) {
                    decoded = verifyToken(bearerToken)
                    if (decoded !== false){
                            if (checkIfExpired(decoded.expires)){
                                    n = 1;
                                    privilege = false;
                                    for(i in tarkistuslista){
                                            if(decoded.ryhmäoikeudet[tarkistuslista[i]]){
                                                    privilege = true
                                                    console.log("Tokenisi vanhentuu" + Math.abs(decoded.expires - Mat$
                                                    return true;
                                                    break;
                                            }else{
                                                    if(n == tarkistuslista.length && privilege == false){
                                                            response = ({
                                                              success: false,
                                                              message: "Forbidden"
                                                            });
                                                            return response;
                                                    }
                                                    n +=1;
                                            }
                                    };
                            }else{
                                    console.log("Token Expired");
                                    response = ({
                                              success: false,
                                              tokenstatus: "expired",
                                              message: "Token expired"
                                    });
                                    return response;
                            }
                    }else{
                            response = ({ success:false, message: 'Invalid token'});
                            return response;
                    }
            } else {
                    response = ({
                            success: false,
                            message: 'No token provided.'
                    });
                    return response;
            }
    };