authorize-mw

0.0.1 • Public • Published

Authorize middleware

Authorization middleware for Node.js and the Javascript platform in general

See wiki for a guide on design and architecture.

Most important sections:

Installation

The index.js file exposes the following:

  AuthorizeMw :  requires.mw 'authorize-mw'
  Authorizer :   requires.lib 'authorizer'
  Ability :      requires.lib 'ability'
  Allower :      requires.lib 'allower'
  Permit :       requires.lib 'permit'
  permit-for:    requires.permit 'permit-for'

So you can simply do:

authorize = require('authorize-mw');

Then require the parts you need as demonstrated in the usage examples below.

Permit      = authorize.Permit;
permit-for  = authorize.permit-for;

Browser usage

Try browserify

browserify index.js -o authorize-mw.js

Usage

The following is a complete example, using LiveScript syntax for a clearer picture.

authorize = require 'authorize-mw'

Middleware  = require 'middleware' .Middleware
AuthorizeMw = authorize.AuthorizeMw
Permit      = authorize.Permit
permit-for  = authorize.permit-for

class Book extends Base
  (obj) ->
    super ...

book = new Book title: title

class GuestUser extends User
  (obj) ->
    super ...

  role: 'guest'

guest-user   = new GuestUser name: 'unknown'

GuestPermit = class GuestPermit extends Permit
  (name, desc) ->
    super ...

  match: (access) ->
    @matches(access).user role: 'guest'

guest-permit = permit-for(GuestPermit, 'guest books',
  rules:
    ctx:
      area:
        guest: ->
          @ucan 'publish', 'Paper'
        admin: ->
          @ucannot 'publish', 'Paper'

    read: ->
      @ucan 'read' 'Book'
    write: ->
      @ucan 'write' 'Book'
    default: ->
      @ucan 'read' 'any'
)

basic-authorize-mws = new AuthorizeMw current-user: guest-user

auth-middleware = new Middleware 'model' data: books.hello
auth-middleware.use authorize: basic-authorize-mws

read-books-request =
  action      :   'read'
  collection  :   'books'

allowed = auth-middleware.run read-books-request

Same in Javascript:

var authorize, Middleware, AuthorizeMw, Permit,
    permitFor, Book, book, GuestUser, guestUser,
    GuestPermit, guestPermit, basicAuthorizeMws,
    authMiddleware, readBooksRequest, allowed;

authorize = require('authorize-mw');

Middleware = require('middleware').Middleware;
AuthorizeMw = authorize.AuthorizeMw;
Permit = authorize.Permit;

permitFor = authorize.permitFor;

Book = require 'models/book'
book = new Book({
  title: title
});

GuestUser = require 'models/users/guest'
guestUser = new GuestUser({
  name: 'unknown'
});

GuestPermit = lo.extend(Permit, {
  prototype: {
    match: function(access){
      return this.matches(access).user({role: 'guest'});
    }
  }
});

guestPermit = permitFor(GuestPermit, 'guest books', {
  rules: {
    ctx: {
      area: {
        guest: function(){
          return this.ucan('publish', 'Paper');
        },
        admin: function(){
          return this.ucannot('publish', 'Paper');
        }
      }
    },
    read: function(){
      return this.ucan('read', 'Book');
    },
    write: function(){
      return this.ucan('write', 'Book');
    },
    'default': function(){
      return this.ucan('read', 'any');
    }
  }
});

basicAuthorizeMws = new AuthorizeMw({
  currentUser: guestUser
});

authMiddleware = new Middleware('model', {
  data: books.hello
});

authMiddleware.use({
  authorize: basicAuthorizeMws
});

readBooksRequest = {
  action: 'read',
  collection: 'books'
};

allowed = authMiddleware.run(readBooksRequest);

You can also run with the user as part of the run context

publish-book-request =
  user     :   guest-user
  action   :   'publish'
  data     :   book

allowed = auth-middleware.run publish-book-request

or you can use model instead of collection

publish-book-request =
  user     :   guest-user
  action   :   'publish'
  model    :   `book`

Without middleware

Simple example using a user Ability directly, without the middleware layer...

authorize = require 'authorize-mw'

Ability     = authorize.Ability

user = (name) ->
  new User name

book = (title) ->
  new Book title

ability = (user) ->
  new Ability user

a-book = book 'some book'
current-user = user 'kris'

if ability(current-user).allowed-for action: 'read', subject: a-book
  # do the read book action
  ...

Same in Javascript:

var authorize, Ability, user, book, ability, aBook, currentUser;

authorize = require('authorize-mw');
Ability = authorize.Ability;

user = function(name){
  return new User(name);
};

book = function(title){
  return new Book(title);
};

ability = function(user){
  return new Ability(user);
};

aBook = book('some book');

currentUser = user('kris');

if (ability(currentUser).allowedFor({
  action: 'read',
  subject: aBook`
})) {
  // do the action
}

Current status

All tests are passing :)

Note to self: rule-applier needs more tests...

To facilitate testing, each class implements Debugger which allows using debug-on! on the class or instance level to track what goes on inside.

Use xdescribe, describe.skip and describe.only to select which tests to execute.

Caching via fingerprinting

A caching strategy should be implemented, so rules are not evaluated each time for the same action request (action, subject, object, context) AccReq, instead a cached previous result for that AccReq should be retrieved.

If any of these is an object, a .hash function should be attempted called on that object to get the "fingerprint" If no hash function on object, fingerprinting is done via JSON stringify.

Then each of these fingerprints should be concatenated into one access request fingerprint. If the same AccReq fingerprint comes in again, the cached permit rules should be used for better performance!

Please feel free to contribute a solution to this major performance enhancement.

Design

Why LiveScript?

Since it is faster/easier to develop the basic functionality. We can always later refactor the code to use something else.

Why classes and not prototypical inheritance?

See reasoning for Livescript ;) Just easier to implement using classes. I started off using basic Javascript constructor functions but since abandoned this approach because it was slowing me down. Just provides a nice encapsulation/abstraction layer.

Feel free to fork this project and provide a version without classes if that is a MUST for you...

Testing

Run mocha on all files in test folder

Just run all test like this:

$ mocha

To execute individual test, do like this:

$ mocha test/authorize-mw/permit_test.js

Test coverage

The library istanbul is used for code coverage.

See code-coverage-with-mocha for use with mocha.

npm install -g istanbul
istanbul cover _mocha -- -R spec
open coverage/lcov-report/index.html

$ instanbul cover _mocha

To measure coverage of individual test:

$ instanbul cover _mocha test/authorize-mw/permit_test.js

Contribution

Please help improve this project, suggest improvements, add better tests etc. ;)

Licence

MIT License Copyright 2014-2015 Kristian Mandrup

Package Sidebar

Install

npm i authorize-mw

Weekly Downloads

2

Version

0.0.1

License

none

Last publish

Collaborators

  • kmandrup