Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 26 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# CanThey
A customizeable, flexible authorization checker - usable with or without express.
A customizeable, flexible authorization checker, usable with or without express.

## Install
```
npm install canthey
```

## Authorization vs Authentication
Authentication is determining if a user is who they say they are. Authorization is determining whether or not said user is allowed to access / interact with the resource. This module assumes you've already handled authentication.
Authentication is determining if a user is who they say they are. Authorization is determining if an authenticated user is allowed to access/alter a specific resource. This module assumes you've already handled authentication.

## Function vs Express plugin
This can be used as a standalone function, seperate of Express. This is useful if you want to bake your own middleware or use it for non Express purposes.
CanThey can be used as a standalone function. This is useful if you want to bake your own middleware or use it outside of Express.

At its core, the function takes an ACL style string and a JSON object of permissions, and parses them against one another to see if they match.
At its core, the function takes an ACL style string and a JSON permissions object, and compares them for a match.

### Example ACL strings
```
Expand All @@ -22,7 +22,7 @@ products - music - albums - delete
products::toys::action_figures::edit
```

### Example permissions JSON:
### Example JSON permissions object
```js
{
"admins":
Expand All @@ -40,9 +40,11 @@ products::toys::action_figures::edit
}
```

In these examples, the first three ACL strings would pass, the fourth would not.
In the above example, the first three ACL strings would pass, the fourth would not.

The function is looking against the JSON for either a name match to the same or higher depth of the ACL string, a boolean true, or a "*" at the same or higher level. For example, with the above given permission JSON, the ACL string of "products:toys" blocking a route that should have "action_figures" in the ACL string will allow the user through.
The function looks through the permission object for a boolean true, or a string match or "*" at the same or higher level of the ACL string.

Be carefull with your permissions to ensure that you check them at the level you really want. For example, using the ACL string of "products:toys" with the above permissions object, attempting to protect a route that should require "action_figures" will pass, even though that isn't your intent. To get the granularity of check you want here, you need to use an ACL like "products:toys:action_figures".

# The CanThey Function

Expand All @@ -55,35 +57,35 @@ canThey(requiredACL, givenUserPermissions, opts); //returns true or false

## Parameters

* requiredACL - Required. The access level required, in a string of similar format to above..
* givenUserPermissions - Required. The JSON representation of the user's permissions OR an array of JSON permissions.
* if this is an array, an internal function called canTheyCombiner is used to create a singular permissions object from many permissions objects. A great example of this is if you used role based permisisons and a user had many roles.
* requiredACL - Required. The access level required, as a string with or without splitBy characters.
* givenUserPermissions - Required. A JSON permissions object or array of permissions objects.
* If array, canTheyCombiner merges objects into a single permissions object prior to ACL comparision. A use case for this is for role based permisisons where a user has many roles.
* opts - Optional. Each attribute in opts is optional, with a default value.
* splitBy - default: ":" - the string delimiter to split the ACL by.
* removeSpaces - default: true - whether or not to remove all spaces from the ACL.
* splitBy - default: ":" - The string delimiter to split the ACL by.
* removeSpaces - default: true - Whether or not to remove all spaces from the ACL.

# Express Middleware
There are two forms of Express Middleware available, as shown in the examples below.
```js
var Cte = require('canthey').Express
var canThey = new Cte(
{
onRoutecall: function, // to be explaiend later - this determines which middleware you're using
failureStatusCode: 403, //Defaults to 403 - what error code to send if they are not allowed access.
onRoutecall: function, // To be explaiend later - this determines which middleware you're using
failureStatusCode: 403, // Defaults to 403 - what error code to send if they are not allowed access.
permissionsAttribute: 'userACL',
/*
Defaults to userACL - Required when not using onRouteCall.
Determines what to request the user's permissions from in the request object.
Resulting code is req[this.permissionsAttribute] - we expect you to assign the
value in a prior middleware, probably when authenticating the user.
*/,
splitBy: ":", //Same as the canThey option - passed through.
removeSpaces: true //Same as the canThey option - passed through.
splitBy: ":", // Same as the canThey option - passed through.
removeSpaces: true // Same as the canThey option - passed through.
}
);
```

## Without onRoutecall
## Without onRouteCall

```js
var Cte = require('canthey').Express;
Expand All @@ -96,8 +98,8 @@ app.get('/admins/edit', canThey.do('admins:edit'), function(req, res){

## With onRouteCall

onRouteCall is a function fired off whenever the middleware is called to determine:
* the ACL string requirements for the route _ (Required) _
onRouteCall is a function fired when the middleware is called to allow dynamic determination of:
* the ACL string for the route (Required)
* the user's JSON permissions (Not required if you're using the request object as the other middleware).

```js
Expand All @@ -106,13 +108,13 @@ var canThey = new Cte(
{
onRouteCall: function(req, res, cb){
//Grab your ACL string
var fakeACL = 'admins:create';
var dynamicACL = 'admins:create';
//And, optionally, grab the user's permissions.
var fakeUserPermissions =
var dynamciUserPermissions =
{
admins: "*"
};
cb(null, fakeACL, fakeUserPermissions);
cb(null, dynamicACL, dynamicUserPermissions);
}
}
);
Expand All @@ -124,9 +126,9 @@ app.get('/admins/create', canThey.do, function(req, res){

Function with the following parameters:
* req - Request object, passed for your convenience.
* res - Response object, also passed for your convenience.
* res - Response object, passed for your convenience.
* cb - The callback is a function with the following expected responses:
* err - If populated, it will send the the failureStatusCode back to the user, but nothing else (silent fail).
* err - If populated, it will respond with the failureStatusCode, but nothing else (silent fail).
* routeACL - Required - the route's ACL string.
* permissions - Optional - if you are attaching permissions to the request object, you can ignore this. Otherwise, it is the resulting permissions for the user.

Expand Down
7 changes: 5 additions & 2 deletions lib/canTheyCombiner.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ var merge = require('controlled-merge'),
returns a javascript object
*/
function(permissions){
if(!Array.isArray(permissions)) throw new Error('CanThey Combiner can only an array of javascript objects');

var msg = 'CanThey Combiner can only accept an array of javascript objects';

if(!Array.isArray(permissions)) throw new Error(msg);

permissions.forEach(function(permission){
if(typeof permission != 'object') throw new Error('CanThey Combiner can only accept an array of javascript objects');
if(typeof permission != 'object') throw new Error(msg);
});

return merge(
Expand Down
2 changes: 1 addition & 1 deletion lib/canTheyExpress.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module.exports = function(){
var self = this;

if(arguments.length != 1 && !self.onRouteCall){
throw new Error('CanThey.do can only be treated like middleware (req, res, next) passed if onRouteCall is set.');
throw new Error('CanThey.do can only be treated like middleware (req, res, next passed) when onRouteCall is set.');
}

if(self.onRouteCall){
Expand Down
2 changes: 1 addition & 1 deletion test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ describe('CanThey - express, onRouteCall is used', function(){
}).to.throw();
});

it('should return 401 if userACL is undefined', function(){
it('should return 403 if userACL is undefined', function(){
req.routeACL = null;
canThey.do(req, res, next);

Expand Down