/daddy

Simple, powerful and fun permission checks for Javascript

Primary LanguageJavaScriptMIT LicenseMIT

Daddy

Simple, powerful and fun permission checks for Javascript.

NPM Version Coverage Status GitHub license

Installation

$ npm install daddy

Usage

ECMAScript 6 version is the default:

import Daddy from 'daddy';

For ECMAScript 5 compatibility, use the distributed commonJS build.

var Daddy = require('daddy/es5');

Daddy uses the native ES6 Promise spec by default. You can use any Promises/A+ compliant implementation though, by setting it with setPromiseImplementation();

var d       = new Daddy(),
    Promise = require('bluebird');

d.setPromiseImplementation(Promise);
// now daddy uses bluebird when returning async results from daddy.when

A common use case:

function getCurrentUser() {
  return {
    name: 'Teemu',
    role: 'admin'
  };
}

function ensureAdmin(user) {
  return (user.role === 'admin')
    ? true
    : 'Must be admin';
}

//
// Daddy instance, not mad
var dad = new Daddy();

//
// set current user as the default parameter for checks
dad.defineParamsGetter(getCurrentUser);

//
// The permission to do stuff requires admin
dad.permission('doStuff', ensureAdmin);

dad.check('doStuff').granted === true;
dad.check('doUnknownAction').granted === true; // because dad is not mad

And preferably do checks async:

  
dad.when('illegalAction').catch(function(rejection) {
  // rejection.granted === false;
  // rejection.reason === 'Whatever was returned from the failing handler';
});

API

new Daddy(mad)

Param Type Description
mad Boolean If set to true, will deny all unknown permissions.

Daddy.permission(pattern *, handler *, ...)

Param Type Description
pattern `String RegExp`
handler function A handler function that gets called on Daddy.check. Supports multiple handlers.

Use RegExp for useful catch-many permissions or namespacing:

// for example, allow only admins to do remove actions. 
// Eg. 'removePost','removeComment'.
daddy.permission(/^remove/, ensureAdmin);

// or
// require a registered user to crud comments. 
// Eg. 'Comment','viewComment','createComment'...
daddy.permission(/Comment$/, ensureRegistered);

Daddy.defineParamsGetter(fn *)

Param Type
fn Function

Register a param getter function in a daddy instance. It set, will head its' return value in params passed to check. This is especially useful for user authorization when you know for sure that the current user is always needed in checks.

Daddy.check(name *, param, ...)

Param Type Description
name String The permission name to be matched against.
param any Optional parameters to be passed to permission layer handlers.

Performs a synchronous check returning a result object:

{
  granted: Boolean, // did it pass?
  reason: any // the value returned from the failing handler
}

Calls each handler in each satisfying permission layer, passing in the result of paramsGetter, if set, and passed param(s) as arguments. Short circuits as soon as any of the handlers return false.

Layer lookups are cached, so subsequent calls with the same name are quaranteed to be as fast as accessing an array by a known index.

Daddy.check(name *, param, ...)

Identical to Daddy.check but:

Performs an asynchronous check returning a promise that resolves or rejects with the result object.

Daddy uses ECMAScript 6 Promises by default. You can change the implementation with:

Daddy.setPromiseImplementation(Promise)

For example:

var d       = new Daddy(),
    Promise = require('bluebird');

d.setPromiseImplementation(Promise);
// now daddy uses bluebird when returning async results from daddy.when

More examples

You may want to namespace permissions:

var daddy = new Daddy();

daddy
  .permission(/^core:/           , ensureRegistered)
  .permission(/^core:utils:/     , ensureDeveloper)
  .permission(/^core:utils:api/  , ensureAdmin);

Or go crazy:

var player  = { name: 'Teemu', type: 'player' },
    enemy   = { name: 'T Rex', type: 'dinosaur' },
    // dad is mad
    permissionManager = new Daddy(true);

//
// Only dinosaurs are allowed to eat anything
permissionManager.permission(/^eat/, function(x) {
  return (x.type === 'dinosaur')
    ? true
    : 'Only dinosaurs are allowed to eat anything';
});

//
// But for some reason cars can only be interacted with by a player
permissionManager.permission(/Car$/, function(x) {
  return (x.type === 'player')
    ? true
    : 'Only players can interact with cars';
});

permissionManager.check('eatBuilding', enemy).granted === true;
permissionManager.check('eatStuff', enemy).granted === true

permissionManager.check('eatCar', enemy);
// -> { granted: false, reason: 'Only players can interact with cars' }

permissionManager.check('eatCar', player); // eat false -> false
// -> { granted: false, reason: 'Only dinosaurs are allowed to eat anything' }
// 
permissionManager.check('driveCar', player).granted === true;

permissionManager.check('idle', player);
// dad is mad
// -> { granted: false, reason: 'No permission layers matched.'}

Contributions

Clone and install deps

$ npm install

Build

$ npm run build

Test

$ npm run test

License

MIT