Timi Ajiboye

Understanding Sails.js Policies (plus example)

Sails.js Policies are pretty simple tools for controlling “who can do what” in your application.
They let you allow or deny access to your controllers (and their actions) with nuance. Policies can be used to check if a request isAuthenticated before letting it through or if a User canEdit before updating another post. In fact, you can use (two) Policies to check if a request isAuthenticated AND if the User canEdit. They’re meant to be chained together if need be.

How to wield Policies

To utilize policies, there are two things that you need to do:

1. Write the Policy function:

At their core, Policies are Connect/Express middleware functions that run before your controllers. They’re really just simple functions that live in /api/policies. We’re going to look at a simple testPolicy.js to illustrate the important parts of a Policy function.

/**
 * testPolicy
 *
 * @description :: Policy to show how Policies are made
*/


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

  doSomething().then(doAnotherThing(result))
  .catch(function(err){
      return next(err); // 1
  });

  if (result.isNotWhatWeWant()){
      res.status(400).json("Not allowed") // 2
  } else if (result.isWhatWeWant()){
      next(); // 3
  }
}

There are three things to note about the above code:
1. If an error occurs while you're trying to perform an operation (like a Waterline Query), you can revert to the app's default error handler. To do this you call next(err), passing the error into it as an argument.
2. During the process of checking whatever you'd like to confirm about our request before letting it through, you might be faced with the undesired outcome. At this point, you simply just respond with a 400 (or a better fitting status code and message).
3. If the request conforms to the condition we're checking for, you just need to call next() to let the request through to the controller action or the next Policy.

2. Apply the policy to controller actions

There exists a file /config/policies.js. It is in this file you specify which policies will act as gatekeepers for controller actions using controller actions as keys and arrays of policies as values.

module.exports.policies = {  
    {
        SomeController: {
            action: 'testPolicy',
            anotherAction: ['isAuthorized', 'isAdmin']
        }, 

        AnotherController: {
         '*': 'isAuthorized',
         // Built in Policies

         edit: true,
         delete: false
        }
    }
}

In the above code:
- action of SomeController shall not be accessible unless the request conforms to the conditions in our testPolicy. - anotherAction of SomeController cannot be performed unless the request isAuthorized and isAdmin. - In AnotherController, the first declaration means that all('*') the actions would need to pass through isAuthorized unless otherwise stated. This means that default policy declarations like this one will not be applied to any controller action that is given an explicit mapping. - The other two actions in AnotherController make use of Built-in polices; true and false. true allows any request get to the mapped controller/action while false blocks all requests.

'*':true is the default policy for all controllers and actions. The official Sails.js documentation advises to set it to false to "prevent access to any logic you might have inadvertently exposed."

Note: Policies are executed from left-to-right. In the above code isAuthorized will be checked before isAdmin.

Example Application: Whitelist Request Hosts

We are going to build a simple Sails.js API that only allows requests from specific hosts.
One could posit that this can be done with CORS but CORS would not work for server-to-server requests as it is only applied in the browser.
A better way to do this (even in conjunction with CORS) would be to create a Policy that checks the requests host before letting the request through.

Let's get coding

In your API only Sails.js Application (created using sails new appname --no-frontend), create a file /api/policies/isAllowedHost.js.

module.exports = function(req, res, next){  
 var whitelist = ["www.somedomain.com", "anotherdomain.co"]

 if (whitelist.includes(req.host)){
     next();
 } else {
     res.status(400).json("Not allowed");
 }
}

And just like that our Policy is all done.

In /config/policies.js/,

module.export.policies = {  
    "*": "isAllowedHost"
}

That’s it. We’re done.

Note: If you’re adding more policy-action mappings, you have to add isAllowedHost to all of them. This is because (as stated earlier) Policy declarations don’t trickle down. If an action is explicitly listed, its policy list will override the default list.

module.export.policies = {  
    "*": "isAllowedHost",

    SomeController: {
        create: ["isAllowedHost", "canCreate"]
    }
}

That’s all for today folks.

O dabọ.