npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

simple-rbac

v2.0.0

Published

Module to get RBAC (Role-based access control) in a NodeJS app, using Mongoose.

Downloads

17

Readme

simple-rbac - introduction

Simple RBAC (Role-based access control) for mongoose apps

We recommend using simple-rbac with this modules too

  • simple-rbac-utils: Basic functions to check for permissions setted in RBAC, both for server-side and client-side (through templating engines, like Dust.js)

Requirements

Promises available!

  • Thanks to Bluebird, simple-rbac can transform all it's async methods into promises. To use them, you'll have all the public methods with the suffix Async and promisified (read this to check how it's done)

Also works with these templating engines

What is RBAC?

Role-based access control (RBAC) is a method of regulating access based on the roles of individual users within an enterprise/app. In this context, access is the ability of an individual user to perform a specific task, such as view, create, or modify information or get aditional tools in an application. Roles are defined according to job competency, authority, responsibility or any other established condition.

Module description

simple-rbac gives you the base code to implement a Role-based access control using mongoose, allowing you to use it together with your own user's collection.

It's really simple to use and has tools to integrate it with a templating engine as Dust.js (more engines comming soon! :) ), in case you want to render your app based on the role-based access controll settings, and tools to make role-based decisions server-side.

Installation

npm install simple-rbac --save

Usage

Initialization

You will need to initializate RBAC. There are a few options to do that.

1) Initializate RBAC by default:

var simpleRbac = require('simple-rbac').rbac;
var rbac = simpleRbac.init();

"By default" means that RBAC will add the 3 collections to save the information with default names and basic schemas for each of them:

  • Permissions collection: 'rbac_permissions'
  • Roles collection: 'rbac_roles'
  • Users collection: 'rbac_users'

2) Initializate RBAC with your own collections

var simpleRbac = require('simple-rbac').rbac;
var rbac = simpleRbac.init({
	permission: 'my_rbac_permissions_collection',
	role: 'my_rbac_roles_collection',
	user: 'my_rbac_users_collection'
});

3) Initializate RBAC extending the collections schemas

Example 1: customizing collection names and extending user's schema

var simpleRbac = require('simple-rbac').rbac;
var rbac = simpleRbac.init({
	permission: 'my_rbac_permissions_collection',
	role: 'my_rbac_roles_collection',
	user: 'my_rbac_users_collection'
}, {
	extendSchemas: {
		user: {
			user_info: {
				type: mongoose.Schema.Types.ObjectId,
				ref: 'user_infos'
			}
		}
	}
});

Example 2: extending user's schema and permission's schema

var simpleRbac = require('simple-rbac').rbac;
var rbac = simpleRbac.init(null, {
	extendSchemas: {
		user: {
			email: {
				type: String,
				required: true
			},
			phone: {
				type: Number,
				required: true
			}
		},
		permission: {
			dateAdded: {
				type: Date,
				default: Date.now
			}
		}
	}
});

simple-rbac API

After initializating RBAC, we have methods to access the permissions, roles and users.

Permissions

rbac.Permissions.add(permission, callback);

Tries to add a new permission to the database and then return it back. If the permission exists, it just returns the existent one

  • @param {object} permission: Data that defines the permission. permission.permission and permission.operation are required
  • @param {Function} callback: Callback that returns an error or the permission object

rbac.Permissions.remove(parameters, callback);

Tries to remove all the permissions with certain parameters. Returns the status of the removal and the number of elements removed

  • @param {object} parameters: Parameters to find the permissions to be removed. parameters.permission is required
  • @param {Function} callback: Callback that returns an error or the status of the removal and the number of elements removed

rbac.Permissions.get(parameters, callback, opts);

Tries to get a permissions with certain parameters.

  • @param {object} parameters: Parameters to find the permissions.
  • @param {Function} callback: Callback that returns an error or founded permission. Returns null if nothing was found.
  • @param {object} opts: Aditional options
    • {object} opts.select: Allows to send an object with the field to be selected
    • {boolean} opts.lean: Makes the mongo query "lean" (mongoose)

rbac.Permissions.getAll(parameters, callback, opts);

Tries to get all the permissions that match a filter.

  • @param {object} filter: Filter to find the permissions.
  • @param {Function} callback: Callback that returns an error or founded permission. Returns null if nothing was found.
  • @param {object} opts: Aditional options
    • {object} opts.select: Allows to send an object with the field to be selected
    • {boolean} opts.lean: Makes the mongo query "lean" (mongoose)

Permission's methods

After you get a permission using the API (for example, when you do rbac.Permission.add or rbac.Permission.get), you get an object with the permission itself.

This new object is a mongoose object, which means that you can operate with it using any mongoose method on it. Besides that, simple-rbac has a method to simplify your work.

So, if you do this:

rbac.Permission.get({
	permission: 'accountSettings',
    operation: 'read'
    }, function(err, permission) {
    	console.log(permission);
});

you get all the permission's information, and can use a permission's method:

permission.edit(newData, callback);

Edits a permission properties

  • @param {Object} newData: Object with the new properties for the permission
  • @param {Function} callback: Callback. Returns the callback of the mongoose's save() function

Roles

rbac.Roles.add(role, callback);

Tries to add a new role to the database and then return back that new role object. If the role exists, it just returns the existent one

  • @param {object} role: Data that defines the role. role.name is required
  • @param {Function} callback: Callback that returns an error or the role object

rbac.Roles.remove(roleName, callback);

Tries to remove a role matching roleName parameter. Returns the status of the removal and the number of elements removed

  • @param {object} parameters: Name of the role to be deleted
  • @param {Function} callback: Callback that returns an error or the status of the removal and the number of elements removed

rbac.Roles.get(roleName, callback, opts);

Tries to get a role by roleName

  • @param {string} roleName: Role Name
  • @param {Function} callback: Callback that returns an error or founded role. Returns null if nothing was found.
  • @param {object} opts: Aditional options
    • {object} opts.select: Allows to send an object with the field to be selected
    • {boolean} opts.lean: Makes the mongo query "lean" (mongoose)

rbac.Roles.getById(roleId, callback, opts);

Tries to get a role by id

  • @param {string} roleId: Role Id
  • @param {Function} callback: Callback that returns an error or founded role. Returns null if nothing was found.
  • @param {object} opts: Aditional options
    • {object} opts.select: Allows to send an object with the field to be selected
    • {boolean} opts.lean: Makes the mongo query "lean" (mongoose)

rbac.Roles.getAll(filter, callback, opts);

Tries to get all the roles that match a filter

  • @param {object} filter: Filter to find the roles
  • @param {Function} callback: Callback that returns an error or founded role
  • @param {object} opts: Aditional options
    • {object} opts.select: Allows to send an object with the field to be selected
    • {boolean} opts.lean: Makes the mongo query "lean" (mongoose)

Role's methods

After you get a role using the API (for example, when you do rbac.Role.add or rbac.Role.get), you get an object with the role itself.

This new object is a mongoose object, which means that you can operate with it using any mongoose method on it. Besides that, simple-rbac has methods to simplify your work.

So, if you do this:

rbac.Role.get({
	name: 'manager'
    }, function(err, role) {
    	console.log(role);
});

you get all the role's information, and can use any role's method:

role.edit(newData, callback);

Edits a role properties

  • @param {Object} newData: Object with the new properties
  • @param {Function} callback: Callback. Returns the callback of the mongoose's save() function

role.addInheritedRoles(rolesArray, callback);

Add all the roles listed in the rolesArray param

  • @param {Array} rolesArray: Array filled with role names
  • @param {Function} callback: Callback. Returns the callback of the mongoose's save() function

role.removeInheritedRoles(rolesArray, callback);

Removes all the inheritRoles listed in the permissionsArray param

  • @param {Array} rolesArray: Array filled with roles names
  • @param {Function} callback: Callback. Returns the callback of the mongoose's save() function

role.addPermissions(permissionsArray, callback);

Add all the permissions listed in the permissionsArray param

  • @param {Array} permissionsArray: Array filled with permissionsIds
  • @param {Function} callback: Callback. Returns the callback of the mongoose's save() function

role.removePermissions(permissionsArray, callback);

Removes all the permissions listed in the permissionsArray param

  • @param {Array} permissionsArray: Array filled with permissionsIds
  • @param {Function} callback: Callback. Returns the callback of the mongoose's save() function

role.getPermissions(populate, callback);

Returns all the permissions of a role

  • @param {Boolean} populate: If true, populates the permission IDs with their full data
  • @param {Function} callback: Callback. Parameters: callback(error, permissions);

Users

rbac.Users.add(user, callback);

Tries to add a new user to the database and then return back that new user object. If the user exists, it just returns the existent one and returns 'true' as third parameter of the callback

  • @param {object} user: Data that defines the user. user.permission and permission.operation are required
  • @param {Function} callback: Callback that returns an error or the user

rbac.Users.remove(userName, callback);

Tries to remove a user matching roleName parameter. Returns the status of the removal and the number of elements removed

  • @param {string} userName: Name of the user to be deleted
  • @param {Function} callback: Callback that returns an error or the status of the removal and the number of elements removed

rbac.Users.get(userName, callback, opts);

Tries to get a user with it's username

  • @param {object} userName: Username
  • @param {Function} callback: Callback that returns an error or founded user. Returns null if nothing was found.
  • @param {object} opts: Aditional options
    • {object} opts.select: Allows to send an object with the field to be selected
    • {boolean} opts.lean: Makes the mongo query "lean" (mongoose)

rbac.Users.getAll(filter, callback, opts);

Tries to get all the users that match a filter

  • @param {object} filter: Filter
  • @param {Function} callback: Callback that returns an error or founded users
  • @param {object} opts: Aditional options
    • {object} opts.select: Allows to send an object with the field to be selected
    • {boolean} opts.lean: Makes the mongo query "lean" (mongoose)

rbac.Users.getById(userId, callback, opts);

Tries to get a user with it's ID

  • @param {string} userId: Username
  • @param {Function} callback: Callback that returns an error or founded user. Returns null if nothing was found.
  • @param {object} opts: Aditional options
    • {object} opts.select: Allows to send an object with the field to be selected
    • {boolean} opts.lean: Makes the mongo query "lean" (mongoose)
    • {boolean} opts.toObject: Before returning the user, it converts it to an object, merging the effectivePermissions and effectiveRoles in the user's permissions and roles parameters

rbac.Users.getFull(userName|userId, callback);

Tries to get all the user data, using it's ID or it's userName.

  • @param {object} userName: Username
  • @param {Function} callback: Callback that returns an error or founded user. Returns null if nothing was found.

User's methods

After you get a user using the API (for example, when you do rbac.User.add or rbac.User.getById), you get an object with the user itself.

This new object is a mongoose object, which means that you can operate with it using any mongoose method on it. Besides that, simple-rbac has methods to simplify your work.

So, if you do this:

rbac.User.get({
	userName: 'bart.simpson'
    }, function(err, user) {
    	console.log(user);
});

you get all the user's information, and can use any user's method:

user.edit(newData, callback);

Edits a user properties

  • @param {Object} newData: Object with the new properties
  • @param {Function} callback: Callback. Returns the callback of the mongoose's save() function

user.addRoles(rolesArray, callback);

Add all the roles listed in the rolesArray param

  • @param {Array} rolesArray: Array filled with roleIds
  • @param {Function} callback: Callback. Returns the callback of the mongoose's save() function

user.removeRoles(rolesArray, callback);

Removes all the roles listed in the rolesArray param

  • @param {Array} rolesArray: Array filled with roleIds
  • @param {Function} callback: Callback. Returns the callback of the mongoose's save() function

user.getRoles(populate, callback);

Returns all the user of a user

  • @param {Boolean} populate: If true, populates the role IDs with their full data
  • @param {Function} callback: Callback

user.getPermissions(populate, callback);

Returns all the permissions of a user.

  • @param {Boolean} populate: If true, populates the permission IDs with their full data
  • @param {Function} callback: Callback

Importing base data example

A nice thing about this module is that it allows you to import the RBAC structure from a JS Object, allowing you to easily set your basic structure to start using the module without a UI.

Doing this is really simple, you just need to prepare the bulk that you want to import, set the roles inheritance (if exists), and presto!

'use strict';

/**
 * Dependencies
 */
var async = require('async');

/**
 * Init the rbac module
 */
var simpleRbac = require('simple-rbac').rbac;
var rbac = simpleRbac.init();

/**
 * Prepare the bulk to import the data
 */

var managerInheritedRoles = ['salesman'];
var adminInheritedRoles = ['manager'];

var bulks = {
	permission: [{
		permission: 'accountSettings',
		operation: 'read',
		displayName: 'See settings',
		description: 'Allows the user to see the settings'
	}, {
		permission: 'accountSettings',
		operation: 'update',
		displayName: 'Update settings',
		description: 'Allows the possibility to update the settings'
	}, {
		permission: 'user',
		operation: 'create',
		displayName: 'Create new user',
		description: 'Allows the possibility to create a new user'
	}, {
		permission: 'user',
		operation: 'read',
		displayName: 'See users',
		description: 'Allows the possibility to list all the users and see each user data'
	}, {
		permission: 'user',
		operation: 'update',
		displayName: 'Edit user',
		description: 'Allows the possibility to update a user\'s information'
	}, {
		permission: 'user',
		operation: 'delete',
		displayName: 'Delete user',
		description: 'Allows the possibility to remove a user'
	}],
	role: [
	{
		name: 'salesman',
		displayName: 'Salesman',
		permissions: ['accountSettings.read', 'accountSettings.update']
	}, {
		name: 'manager',
		displayName: 'Manager',
		inheritRoles: managerInheritedRoles,
		permissions: ['user.read']
	}, {
		name: 'admin',
		displayName: 'Admin',
		inheritRoles: adminInheritedRoles,
		permissions: ['user']
	}],
	user: [
	{
		userName: 'bart.simpson',
		roles: ['salesman']
	}, {
		userName: 'homer.simpson',
		roles: ['manager']
	}, {
		userName: 'max.powel',
		roles: ['admin']
	}]
};



console.log('Importing the new users structure...');
// Import all the data presented in the bulks obj
rbac.importData(bulks.permission, bulks.role, bulks.user, function(importErr, response) {
	if (importErr) {
		console.error(importErr);
	}

	// Add the inherited roles
	async.parallel([
		function(done) {
			rbac.Role.get('manager', function(err, role) {
				if (err) {
					console.error(err);
				} else {
					// Attach the inherited roles to the new created role 'manager'
					role.addInheritedRoles(managerInheritedRoles, done);
				}
			});
		},
		function(done) {
			rbac.Role.get('admin', function(err, role) {
				if (err) {
					console.error(err);
				} else {
					// Attach the inherited roles to the new created role 'admin'
					role.addInheritedRoles(adminInheritedRoles, done);
				}
			});
		}
	], function(addInheritRolesErr) {
		if (addInheritRolesErr) {
			console.error(addInheritRolesErr);
		} else {
			console.info('New users structure imported successfully!');
		}
	});
});

TODO

  • Define the collection indexes automatically
  • Make something to graph automatically the user-roles-permissions assignments (Example: http://sigmajs.org/)
  • Change all the methods to work with names instead of ids in the parameters (add role, add permissions, etc)
  • Make permissions and roles inheritance recursive (it only works 1 level deep now)
  • Avoid population of the main object (user/role) when using method getRoles or getPermissions