Ready, Set, Build

Having theoretical knowledge about access control is nice, but unless put to use, we could have spent our time watching pictures of cute kittens instead. So let’s not stop there and let’s start building

The logic of a basic RBAC model is simple — you define a number of roles and each role has privileges assigned to it. When checking for access you check if the role has access and that’s it.

So our example from before can be summed in two tables:

We can achieve this model fairly easily in JavaScript — let’s create a model of the roles and a function to check them.

And now we have a very simple role system. Let’s give it a configurable and reusable form in the manner of a class.

This leaves us with a very simple module for defining and checking roles. Let’s not stop here — we will add hierarchy to the model so that we can manage roles more easily when adding new operations to the system.

This way there is no need to define rights to every operation for each role separately.

It’ll allow the user to represent a list of child roles, where to inherit permissions from.

And then we have to rewrite the access check functionality. In HRBAC model, the access checking begins with the current role, checks if it has access, if not then moves up to the parent and checks again. This happens until a permission is found or there are no more parents to check. So we can rewrite our checking functionality to use recursive logic:

Now we have roles, inheritance and a function to bring it together. Almost done, but not quite there yet. There are still real use cases that we haven’t accounted for. Let me give you an example based on a blogging platform where a writer can create a blog post and then open it up for editing — should the writer role also allow to rewrite every post in the system? Probably not. We need to first check if they are the owner of the post. But how can we write that into a reusable definition — functions? To answer this, let’s allow operations to define functions that need to pass.

So to extend our existing model of roles:

But now our check function also needs to be rewritten — we can no longer use indexOf either. Let’s create a function to normalise our input for better internal use:

And now we can use the map we created in our check function:

Awesome! We now have RBAC class that we can use to check our defined hierarchy model. Additionally, we can also define functions to do dynamic checks for specific access:

RBAC.can('writer', 'edit', {user: user, post: post});

We are still not done. Let’s not forget that we are dealing with Node.js so synchronous solutions are not the best way to go — we need async so that we can instantiate the class with information found in the database. Or we might want our access check to look something up from the file system, other API or somewhere else. Point is — we need it.

We can provide this in two ways — with promises or callbacks. Because we want to be supportive of both styles let’s implement both. However transformation from Promise to callback is much easier than vice versa so we’ll use Promises internally.

We’ll start our update with the check function. Let’s use the Q module to provide backwards compatibility. We can just wrap the contents of our function in a promise constructor:

We can then handle callbacks by optionally binding the handlers for our promise.

We can internally handle the resolve/reject events, by specifying a callback ourselves

And we can handle the inheritance by creating a new promise. One that resolves when any one of the child promises resolves — aka use Q.any.

After adding some type checks, our can function could look something like this:

Now we are almost done. The last thing we want to support is asynchronous loading of the definitions of roles. This means we have to handle the initialisation. The easiest way to accept a function as an input that can return the configuration object after obtaining it somewhere. To do this, let’s add a check in the beginning of init function and store the resolve state in a variable:

// If opts is a function execute for async loading if(typeof roles === 'function') {

this._init = Q.nfcall(roles)

.then(data => this.init(data));

return;

}

And add $this._inited = true before the return statement.

Now we can check at the beginning of the can function if we have managed to set up our roles and act accordingly:

// If not inited then wait until init finishes if(!this._inited) {

return this._init

.then(() => this.can(role, operation, params, cb));

}

And now we are done on the functionality part.