Skip to content

Commit

Permalink
Adds v1.0.0 of module
Browse files Browse the repository at this point in the history
  • Loading branch information
cgarvis committed Oct 19, 2012
0 parents commit 86725e9
Show file tree
Hide file tree
Showing 8 changed files with 360 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
node_modules
19 changes: 19 additions & 0 deletions LICENSE
@@ -0,0 +1,19 @@
Copyright (c) 2012 Moveline Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
50 changes: 50 additions & 0 deletions README.md
@@ -0,0 +1,50 @@
# Rampart [![Build Status](https://secure.travis-ci.org/Moveline/rampart.png)](http://travis-ci.org/Moveline/rampart)

Authorization module with Connect/Express support

## Installation

```bash
$ npm install rampart
```

## Usage

```coffee-script
Auth = require './auth'
Rampart = require 'rampart'
express = require 'express'

class Ability extends Rampart.Ability
constructor: (user) ->
user = user || new User

if user.role is 'admin'
@can 'manage', User

else
@can 'manage', User, {_id: user.id}

app = express()
app.use Auth.session()
app.use Rampart.express(Ability)

app.get '/', (req, res, next) ->
res.send 401 unless req.user.isAllowed 'read', User

```

## Tests

```bash
$ npm test
```

## Authors [Christopher Garvis][0] & [Moveline][1]

[0]: http://christophergarvis.com
[1]: http://www.moveline.com

## License

MIT
110 changes: 110 additions & 0 deletions lib/rampart.js
@@ -0,0 +1,110 @@
_ = require('underscore');

var Ability = function Ability() {
this.rules = [];
};

Ability.prototype.can = function can(action, subject, conditions) {
this.rules.push(new Rule(true, action, subject, conditions));
};

Ability.prototype.cannot = function cannot(action, subject, conditions) {
this.rules.push(new Rule(false, action, subject, conditions));
};

/**
* @param {String}
* @param {Object}
* @returns boolean
*/
Ability.prototype.isAllowed = function isAllowed(action, subject) {
var rules = this.relevantRules(action, subject);
var match = _.detect(rules, function(rule) {
return rule.matchesConditions(action, subject);
});

return match ? match.base_behavior : false;
};

/**
* @see Ability::isAllowed
*/
Ability.prototype.isNotAllowed = function isNotAllowed() {
return !this.isAllowed.apply(this, arguments);
};

Ability.prototype.relevantRules = function relevantRules(action, subject) {
return this.rules.filter(function(rule) {
return rule.isRelevant(action, subject);
}, this);
};

var Rule = function(base_behavior, action, subject, conditions) {
var flatten = function flatten(items) {
if('array' === typeof items) {
return items.reduce(function(a,b) { return a.concact(b); });
}

return [items];
};

this.base_behavior = base_behavior;
this.actions = flatten(action);
this.subjects = flatten(subject);
this.conditions = conditions;
};

/**
* @param {String}
* @param {Object}
* @returns boolean
*/
Rule.prototype.isRelevant = function isRelevant(action, subject) {
return this.matchesAction(action) && this.matchesSubject(subject);
};

/**
* @param {String}
* @returns boolean
*/
Rule.prototype.matchesAction = function matchesAction(action) {
return this.actions.indexOf('manage') !== -1 ||
this.actions.indexOf(action) !== -1;
};

Rule.prototype.matchesConditions = function matchesConditions(action, subject) {
if(_.isUndefined(this.conditions) || _.isNull(this.conditions)) {
return true;
}

return _.reduce(this.conditions, function(memo, value, name) {
return memo && (subject[name] === value);
}, true);
};

/**
* @param {Object}
* @returns boolean
*/
Rule.prototype.matchesSubject = function matchesSubject(subject) {
return _.contains(this.subjects, subject) ||
this.subjects.some(function(value) { return subject instanceof value; });
};

var middleware = function(Ability) {
return function(req, res, next) {
var abilities = new Ability(req.user);

req.user.isAllowed = function() { return abilities.isAllowed.apply(abilities, arguments); };
req.user.isNotAllowed = function() { return abilities.isNotAllowed.apply(abilities, arguments); };
req.abilities = abilities;

next();
};
};

module.exports = {
Ability: Ability,
connect: middleware,
express: middleware
};
40 changes: 40 additions & 0 deletions package.json
@@ -0,0 +1,40 @@
{
"name": "rampart",
"version": "1.0.0",
"description": "Authorization module for Node.js.",
"main": "./lib/rampart.js",
"dependencies":{
"underscore": "~1.4.0"
},
"devDependencies":{
"chai": "~1.3.0",
"coffee-script": "~1.3.3",
"express": "~3.0.0rc5",
"mocha": "~1.6.0",
"supertest": "~0.3.1"
},
"directories": {
"test": "test"
},
"scripts": {
"test": "./node_modules/mocha/bin/mocha --compilers coffee:coffee-script"
},
"keywords": [ "acl", "authorization", "access", "authorize", "connect", "express" ],
"author": {
"name": "Moveline Inc.",
"email": "info@moveline.com",
"url": "https://www.moveline.com"
},
"contributors": [
{
"name": "Christopher Garvis",
"email": "christopher.garvis@moveline.com",
"url": "https://github.com/cgarvis"
}
],
"repository": {
"type": "git",
"url": "https://github.com/Moveline/rampart.git"
},
"license": "MIT"
}
13 changes: 13 additions & 0 deletions test/middleware.coffee
@@ -0,0 +1,13 @@
app = require './server'
chai = require 'chai'
request = require 'supertest'

chai.should()

describe 'Rampart Middleware', ->
describe 'logged in', ->
it 'should return 200', (done) ->
request(app).get('/1').expect 200, done

it 'should return 401', (done) ->
request(app).get('/2').expect 401, done
98 changes: 98 additions & 0 deletions test/rampart.coffee
@@ -0,0 +1,98 @@
connect = require 'connect'
chai = require 'chai'

chai.should()

Rampart = require '../lib/rampart'
Ability = new Rampart.Ability()

class User

describe 'Rampart', ->
afterEach ->
Ability.rules = []

describe 'can', ->
it 'should create a rule', ->
Ability.can 'read', User
Ability.rules.should.have.length 1

describe 'cannot', ->
it 'should create a rule', ->
Ability.cannot 'read', User
Ability.rules.should.have.length 1

describe 'isAllowed', ->
describe 'class level', ->
beforeEach ->
Ability.can 'read', User

it 'should be true on `read`', ->
Ability.isAllowed('read', User).should.be.ok

it 'should be false on `write`', ->
Ability.isAllowed('write', User).should.not.be.ok

describe 'instance level', ->
beforeEach ->
Ability.can 'read', User

it 'should be true on `read`', ->
Ability.isAllowed('read', new User).should.be.ok

it 'should be true on `write`', ->
Ability.isAllowed('write', new User).should.not.be.ok

describe 'manage action', ->
beforeEach ->
Ability.can 'manage', User

it 'should be true on `read`', ->
Ability.isAllowed('read', User).should.be.ok

it 'should be true on `write`', ->
Ability.isAllowed('write', User).should.be.ok

it 'should be true on `create`', ->
Ability.isAllowed('create', User).should.be.ok

it 'should be true on `destroy`', ->
Ability.isAllowed('destory', User).should.be.ok

describe 'isNotAllowed', ->
describe 'class level', ->
beforeEach ->
Ability.can 'read', User

it 'should be false on `read`', ->
Ability.isNotAllowed('read', User).should.not.be.ok

it 'should be true on `write`', ->
Ability.isNotAllowed('write', User).should.be.ok

describe 'instance level', ->
beforeEach ->
Ability.can 'read', User

it 'should be false on `read`', ->
Ability.isNotAllowed('read', new User).should.not.be.ok

it 'should be true on `write`', ->
Ability.isNotAllowed('write', new User).should.be.ok

describe 'manage action', ->
beforeEach ->
Ability.can 'manage', User

it 'should be false on `read`', ->
Ability.isNotAllowed('read', User).should.not.be.ok

it 'should be false on `write`', ->
Ability.isNotAllowed('write', User).should.not.be.ok

it 'should be false on `create`', ->
Ability.isNotAllowed('create', User).should.not.be.ok

it 'should be false on `destroy`', ->
Ability.isNotAllowed('destory', User).should.not.be.ok

29 changes: 29 additions & 0 deletions test/server.coffee
@@ -0,0 +1,29 @@
Rampart = require '../'
express = require 'express'

class User
constructor: (id) ->
@id = parseInt id

class Article
constructor: (author) ->
@author = parseInt author

class Ability extends Rampart.Ability
constructor: (user) ->
super
@can 'read', Article, {author: user.id}

app = express()
app.use express.bodyParser()
app.use (req, res, next) ->
req.user = new User(1)
next()
app.use Rampart.express(Ability)
app.get '/:id', (req, res, next) ->
article = new Article(req.params.id)
unless req.user.isAllowed 'read', article
return res.send 401
res.send article

module.exports = app

0 comments on commit 86725e9

Please sign in to comment.