Timi Ajiboye

Sails.js Access Management: Roles & Permissions

When you're building all kinds of applications, there's often a need restrict certain abilities to certain groups of users. A simple example of this is a simple blog:

  • You might want certain users to be able to only add comments and not be allowed to delete or edit anybody else's comments.
  • You could also want another group of users to be able to create posts but not be allowed to publish them.
  • Finally, it makes sense to have a group of users that can publish posts, and delete comments at will.

Creating a system like this in a Sails.js application or API is pretty simple using the sails-permissions module.

It works with sails-auth, which is a superb module that makes doing authentication easy. I wrote about that in a previous blog post.

sails-permissions overview

The way it works is simple, you create roles assign them to users.

In the blog example above, it'll make sense to give those groups of users the roles registered, contributors and editors respectively.

To determine what these groups of users (roles) can do, you'll have to assign permissions to them.

Below is a sort of schematic to make this clearer.

In reality, one can end up with a setup significantly more complex than the above, this is because with sails-permissions a user can have more than one role assigned to them.

By default, sails-permission creates two roles admin and registered.
The admin role can create, update, read and destroy all models in your application.
registered is the default role automatically assigned to any user that is created.

Usage

Installation & Set Up

1. Install

yarn

yarn add sails-permissions  

npm

npm install sails-permissions --save  

Make sure to install sails-auth if you haven't already.

2. Configure sailrc

Now you have to add this to your .sailsrc file

{
  "generators": {
    "modules": {
      "permissions-api": "sails-permissions/generator"
    }
  }
}

3. Run generator

sails generate permissions-api  

4. Set up admin user

To set up your admin user in production, you need to set these environment variables: ADMIN_USERNAME, ADMIN_EMAIL and ADMIN_PASSWORD

For development purposes you can set up your admin user by placing the following code in the exports of /config/local.js.

   permissions: {
     adminUsername: 'username',
     adminEmail: [email protected]',
     adminPassword: 'password'
   },

This is ideal for development because local.js isn't added to version control and hence your credentials cannot be compromised.

sails-permissions will check either the environment variables or the config variables before your application is lifted to create the admin user. If the user has been created, it will skip this step.

5. Policies

You need to paste the below code into your /config/policies.js file.

  '*': [
    'basicAuth',
    'passport',
    'sessionAuth',
    'ModelPolicy',
    'AuditPolicy',
    'OwnerPolicy',
    'PermissionPolicy',
    'RolePolicy',
    'CriteriaPolicy'
  ],

  AuthController: {
    '*': [ 'passport' ]
  }

Creating Roles and Permissions

Roles and Permissions can be created, updated & deleted the same way you treat all other records in a sails application. i.e. Role.create..., Permission.create...
You can even use REST Blueprint endpoints to create them and assign them to users as you wish.
Furthermore, the model has a very nifty PermissionService with nifty methods to make doing stuff like assigning Roles to user a bit easier.
You can read more about it in the sails-permissions documentation here.

There are things to note, however. For example; you can only create roles and permissions if you're currently authenticated as the admin user.

To make sure we don't skip anything important, we're going to look at the sails-permissions source code, so we can understand a bit more of what is going on with the Role and Permission models.

1. Role

This one is relatively simple, it has two attributes of importance when you're creating a role; name & active.
active is a boolean and can be set to false when you want to disable a role but not delete it.

2. Permission

This is a bit more complex. It's attributes are follows:

model

You have to associate a model to a Permission. sails-permissions actually creates a model called Model that automatically stores records for all the models in your application. Neat, isn't it? To get the instance of a model to associate with a permission you can do a query like Model.findOne({where: {name: 'Post'}}....

action

This is a string attribute that has to be create, update, read or delete. This is the action that the permission is allowing the user take on the model above.

relation

This is optional and it defaults to role.

  • You can set it to owner if you want the user to be able to take the action, on the model ONLY if he/she created it. So it makes sense to use this when you want to create a permission that allows a user to update only his own Posts.

  • You can set it to user if you want to grant a specific user this permission thereby skipping Roles entirely.

role

This is the role you want to associate this permission with. To get the instance of a Role to associate with a permission you can do a query like Role({where: {name: 'registered'}}....

user

If you chose to assign the permission to a specific user, this is the association whereby you specify which user record.

criteria

This is list of Criteria. If any of the criteria match the request, the action is allowed. If no criteria are specified, it is ignored altogether.

  • A Criteria has an attribute called where that takes a JSON value. A where clause uses waterline query syntax to determine if a permission is allowed. For example, you might want to let a user only edit an Issue if it is a public issue. You can create a Criteria for this Permission that will be something like this { where: { public: true } }.

  • Besides the where attribute that a Criteria has. There's another important one called blacklist. This takes an array of strings. For the blacklist, if the request action is update or create, and there is a blacklisted attribute in the request, the request will fail. If the request action is read, the blacklisted attributes will be filtered.

Below is an example using the PermissionService and all the knowledge above to create a Permission

PermissionService.grant({  
  role: 'collaborator', 
  model: 'Issue', 
  action: 'update', 
  criteria: { where: { public: true }, 
  blacklist: ['public'] } 
})

The above code creates a permission that allows a collaborator to only update an Issue where public: true. Furthermore, it doesn't let the collaborator update the public attribute of an Issue using the blacklist.

Overriding default Roles & Permissions

When your application starts, sails-permissions creates permissions for the default roles. It automatically creates permissions for the registered role that lets it create any model. This might be undesirable behaviour, so we have to override the file responsible for this.

You can check how the defaults permissions are created by reading the source code here

To override this, create a /config/fixtures/permission.js file and we can use lodash to pretty much replace the code in the sails-permissions source.

const _ = require('lodash');  
const _super = require('sails-permissions/dist/config/fixtures/permission');


module.exports = _.merge(_super, {  
  create(roles, models, admin, config){
     // do stuff
    ])
      .then(function (permissions) {
      // do stuff
      });
  }
})

It's the create method that gets called when sails-permissions wants to create/set defaults, so that's where most of your overriding should get done.

You have to have a good understanding of what is going on the source code to be able to override this functionality properly without ruining things entirely, so take time out to read & re-read till you get it.