New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

connect-roles

Package Overview
Dependencies
Maintainers
1
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

connect-roles - npm Package Compare versions

Comparing version 1.0.1 to 2.0.0

181

index.js
"use strict";
var debug = require('debug')('connect-roles');
var pathToRegexp = require('path-to-regexp');
var functionList = [];
var failureHandler = function failureHandler(req, res, action) {
res.send(403);
res.send(403);
};
var defaultUser = {};
module.exports = function middleware(req, res, next) {
if (res.locals) attachHelpers(req, res.locals);
attachHelpers(req, req);
next();
var exports = module.exports = function middleware(req, res, next) {
if (res.locals) attachHelpers(req, res.locals);
attachHelpers(req, req);
next();
};
exports.use = use;
function use() {
if (arguments.length === 1) {
use1.apply(this, arguments);
} else if (arguments.length === 2) {
use2.apply(this, arguments);
} else if (arguments.length === 3) {
use3.apply(this, arguments);
} else {
throw new Error('use can have 1, 2 or 3 arguments, not ' + arguments.length);
}
}
function attachHelpers(req, obj) {
var oldUser = req.user;
obj.user = req.user || Object.create(defaultUser);
if(oldUser){
obj.user.isAuthenticated = true;
}else{
obj.user.isAuthenticated = false;
function use1(fn) {
if (typeof fn !== 'function') throw new Error('Expected fn to be of type function');
functionList.push(fn);
}
function use2(action, fn) {
if (typeof action !== 'string') throw new Error('Expected action to be of type string');
if (action[0] === '/') throw new Error('action can\'t start with `/`');
use1(function (req, act) {
if (act === action) {
return fn(req);
}
if(obj.user){
obj.user.is = tester(req,'is');
obj.user.can = tester(req,'can');
});
}
function use3(action, path, fn) {
if (typeof path !== 'string') throw new Error('Expected path to be of type string');
var keys = [];
var exp = pathToRegexp(path);
use2(action, function (req) {
var match;
if (match = exp.exec(req.path)) {
req = Object.create(req);
req.params = Object.create(req.params || {});
keys.forEach(function (key, i) {
req.params[key.name] = match[i+1];
});
return fn(req);
}
});
}
module.exports.log = false;
module.exports.can = routeTester('can');
module.exports.is = routeTester('is');
module.exports.isAuthenticated = isAuthenticated;
exports.can = routeTester('can');
exports.is = routeTester('is');
exports.isAuthenticated = isAuthenticated;
function isAuthenticated(req,res,next) {
if(arguments.length === 0){ return isAuthenticated; }
if (req.user && req.user.isAuthenticated === true){ next(); }
else if(req.user){ failureHandler(req, res, "isAuthenticated"); }
else { throw new Error("Request.user was null or undefined, include middleware"); }
if(arguments.length === 0){ return isAuthenticated; }
if (req.user && req.user.isAuthenticated === true){ next(); }
else if(req.user){ failureHandler(req, res, "isAuthenticated"); }
else { throw new Error("Request.user was null or undefined, include middleware"); }
};
module.exports.useAuthorisationStrategy = useAuthorizationStrategy;
function useAuthorizationStrategy(path, fn) {
if(typeof path === "function"){
fn = path;
}
functionList.push(function(user, action, stop){
if(typeof path === "string" && path !== action){
return null;
}
return fn.call(this, user, action, stop);
});
return this;
};
module.exports.setFailureHandler = setFailureHandler;
exports.setFailureHandler = setFailureHandler;
function setFailureHandler(fn) {
failureHandler = fn;
failureHandler = fn;
};
module.exports.setDefaultUser = setDefaultUser;
exports.setDefaultUser = setDefaultUser;
function setDefaultUser(user) {
defaultUser = user;
defaultUser = user;
};

@@ -65,42 +84,46 @@

function tester(req, verb){
return function(action){
var result = null,
vote;
var stop = false;
function stopNow(vote){
stop = true;
if (vote === false){
result = false;
} else if (vote === true) {
result = true;
}
}
for (var i = 0; i<functionList.length && !stop; i++){
var fn = functionList[i];
vote = fn.call(req, req.user, action, stopNow);
if(vote === false){
stop = true;
result = false;
} else if (vote === true){
result = true;
}
}
if(module.exports.log){
console.log('Check Permission: ' + (req.user.id||req.user.name||"user") +
"."+(verb||'can')+"('" + action + "') -> " + (result === true));
}
return (result === true);
return function(action){
var result = null, vote;
var stop = false;
for (var i = 0; i<functionList.length && !stop; i++){
var fn = functionList[i];
vote = fn(req, action);
if(vote === false){
stop = true;
result = false;
} else if (vote === true){
result = true;
}
}
debug('Check Permission: ' + (req.user.id||req.user.name||"user") +
"." + (verb || 'can') + "('" + action + "') -> " + (result === true));
return (result === true);
};
}
function routeTester(verb) {
return function (action){
return function (req, res, next) {
if(tester(req,verb)(action)){
next();
}else{
//Failed authentication.
failureHandler(req, res, action);
}
};
};
}
function routeTester(verb){
return function (action){
return function(req,res,next){
if(tester(req,verb)(action)){
next();
}else{
//Failed authentication.
failureHandler(req, res, action);
}
};
};
function attachHelpers(req, obj) {
var oldUser = req.user;
obj.user = req.user || Object.create(defaultUser);
if(oldUser){
obj.user.isAuthenticated = true;
}else{
obj.user.isAuthenticated = false;
}
if(obj.user){
obj.user.is = tester(req,'is');
obj.user.can = tester(req,'can');
}
}
{
"name": "connect-roles",
"description": "Provides dynamic roles based authentication for node.js connect and express servers.",
"version": "1.0.1",
"version": "2.0.0",
"homepage": "http://documentup.com/ForbesLindesay/connect-roles",

@@ -30,3 +30,7 @@ "repository": {

"everyauth"
]
],
"dependencies": {
"debug": "https://github.com/ForbesLindesay/debug/archive/master.tar.gz",
"path-to-regexp": "https://github.com/ForbesLindesay/path-to-regexp/archive/master.tar.gz"
}
}
[![Build Status](https://secure.travis-ci.org/ForbesLindesay/connect-roles.png?branch=master)](http://travis-ci.org/ForbesLindesay/connect-roles)
# Connect Roles
Connect roles is designed to work with connect or express. It is an authorization provider, not an authentication provider. It is designed to support context sensitive roles/abilities, through the use of middleware style authorization strategies. If you're looking for an authentication system I suggest you check out [passport.js](https://github.com/jaredhanson/passport)
Connect roles is designed to work with connect or express. It is an authorisation provider, not an authentication provider. It is designed to support context sensitive roles/abilities, through the use of middleware style authorisation strategies.
All code samples assume you have already used:
If you're looking for an authentication system I suggest you check out [passport.js](https://github.com/jaredhanson/passport)
```javascript
var app = require('express').createServer();//could also use connect
var user = require('connect-roles');
## Installation
app.use(/* Your authentication middleware goes here */);
app.use(user);//Load the connect-roles middleware here
```
$ npm install connect-roles
For an example of this in use, see server.js (which requires you install express)
## Usage
## Installation
```javascript
var authentication = require('your-authentication-module-here');
var user = require('connect-roles');
var express = require('express');
var app = express();
npm install connect-roles
app.use(authentication)
app.use(user);
## Authorization
//anonymous users can only access the home page
//returning false stops any more rules from being
//considered
app.use(function (req, action) {
if (!req.user.isAuthenticated) return action === 'access home page';
})
Connect Roles assumes that you have authentication middleware to set the user. It expects the user to be on the request object as `req.user`. It makes no assumptions about what this value contains. If this value is not used, it does not matter as the authentication strategies also have access to the request object itself
//moderator users can access private page, but
//they might not be the only one so we don't return
//false if the user isn't a moderator
app.use('access private page', function (req) {
if (req.user.role ==== 'moderator') {
return true;
}
})
## Defining authentication strategies
To define authentication strategies, call the useAuthorisationStrategy function:
@param [path] {string} The action/path/ability/role that this strategy applies to. The strategy will be ignored for all other roles/abilities. If it is not present, the strategy is used for all roles/abilities.
@param fn {function} The function to call to determine whether the user is authorized.
@param fn.this {object} The value of this inside the function is the current request, useful for dynamic authorization.
@param fn.user {object} The user found at req.user (also available as this.user), note that this could be null/undefined if the user is not authenticated.
@param fn.action {string} The action/role/ability etc. that we are checking permission for.
@param fn.stop {function} A function which can be called with or without the vote to make this the last strategy which is used (see anonymous example).
@param [fn.stop.vote] {boolean} The vote, true, false or null as below.
@param [fn.returns vote] {boolean} The function can optionally return a vote, if this is false, then access will be denied, if this is true and nothing returns false, access will be granted.
```javascript
user.useAuthorisationStrategy(function(user, action, stop){
//User logic here.
//admin users can access all pages
user.use(function (req) {
if (req.user.role === 'admin') {
return true;
}
});
//Or
user.useAuthorisationStrategy("create user", function(user, action, stop){
//User logic here.
//optionally controll the access denid page displayed
user.setFailureHandler(function (req, res, action){
var accept = req.headers.accept || '';
res.status(403);
if (~accept.indexOf('html')) {
res.render('access-denied', {action: action});
} else {
res.send('Access Denied - You don\'t have permission to: ' + action);
}
});
```
### Anonymous User
You should probably handle anonymous users first. This is important because it means you then won't have to handle anonymous users individually in every other function, providing you call stop.
If you have anything that an anonymous user is capeable of, you must then check before checking for "anonymous".
```javascript
user.useAuthorisationStrategy("register", function(user){
if(!user.isAuthenticated) return true;
app.get('/', user.can('access home page'), function (req, res) {
res.render('private');
});
app.get('/private', user.can('access private page'), function (req, res) {
res.render('private');
});
app.get('/admin', user.can('access admin page'), function (req, res) {
res.render('admin');
});
user.useAuthorisationStrategy(function(user, action, stop){
if(!user.isAuthenticated){
stop(action === "anonymous");
}
});
app.listen(3000);
```
### Roles
## API
If you have a user object which looks like `{id:10, roles:["RoleA", "RoleB"]}` you could use the following to provide roles checking.
### roles.use(fn(req, action))
```javascript
user.useAuthorisationStrategy(function(user, action){
if(user.isAuthenticated){//You can remove this if already checking for anonymous users
for(var i = 0; i < user.roles.length; i++){
if(user.roles[i] === action) return true;
}
}
});
```
Define and authorisation strategy which takes the current request and the action being performed. fn may return `true`, `false` or `undefined`/`null`
### Dynamic
If `true` is returned then no further strategies are considred, and the user is **granted** access.
This example is what makes this library special.
If `false` is returned, no further strategies are considered, and the user is **denied** access.
```javascript
user.useAuthorisationStrategy("edit user", function(user, action){
if(user.isAuthenticated){//You can remove this if already checking for anonymous users
if(this.params.userid){//`this` refers to the current request object
if(user.id === this.params.userid){
return true;
}
}
}
});
If `null`/`undefined` is returned, the next strategy is considerd. If it is the last strategy then access is **denied**.
//Then you can use the following in express
app.get('/user/:userid/edit', user.can("edit user"), function(req,res){
//Only called if the user is editing themselves, not other people.
});
```
### roles.use(action, fn(req))
## Inline authorization for connect or express
The strategy `fn` is only used when the action is equal to `action`. It has the same behaviour with regards to return values as `roles.use(fn(req, action))` (see above).
Providing you have supplied the middleware (see the first section of this guide) you can use the following functions.
It is equivallent to calling:
### req.isAuthenticated
```javascript
roles.use(function (req, act) {
if (act === action) {
return fn(req);
}
});
```
This is a property that is either true or false to tell you whether the user object is present.
**N.B.** The action must not start with a `/` character or it will call `roles.use(path, fn(req, action))`
### req.user.can, req.user.is
### roles.use(action, path, fn(req))
These functions are all the same, but be aware that methods of the form req.user.* will throw exceptions if user is null.
Path must be an express style route. It will then attach any parameters to `req.params`.
e.g.
```javascript
app.get("/canifly", function(req,res){
if(req.userCan("fly")) res.send("You can fly");
else res.send("You can't fly");
roles.use('edit user', '/user/:userID', function (req) {
if (req.params.userID === req.user.id) return true;
});
app.get("/logout", function(req,res){
//Note how we check authenticated first.
if(req.isAuthenticated && req.user.can("logout")){
logout();
}else{
throw "user can't log out";
}
});
```
### Inside view
Inside a view, you can use `user.isAuthenticated`, `user.is` and `user.can` exactly as you would inside the route handler (Except they aren't attached to the request handler). This is useful for making small UI adjustments, but probably shouldn't be used as the main part of security, I recommend you do that before sending stuff to the view. Just use this to hide buttons that would cause authorization errors.
Note that this authorisation strategy will only be used on routes that match `path`.
## Route middleware for express
It is equivallent to calling:
In express you can provide route middleware. This is perfect for authentication, especially with wildcards.
```javascript
var keys = [];
var exp = pathToRegexp(path);
roles.use(function (req, act) {
var match;
if (act === action && match = exp.exec(req.path)) {
req = Object.create(req);
req.params = Object.create(req.params || {});
keys.forEach(function (key, i) {
req.params[key.name] = match[i+1];
});
return fn(req);
}
});
```
### roles.can(action) and roles.is(action)
### Protect entire admin section in one line
`can` and `is` are synonyms everywhere they appear.
Simply put this before you have any other routes beginning /admin
You can use these as express route middleware:
```javascript
app.get("/admin*", user.is("admin"));
var user = roles;
app.get('/profile/:id', user.can('edit profile'), function (req, res) {
req.render('profile-edit', { id: req.params.id });
})
app.get('/admin', user.is('admin'), function (req, res) {
res.render('admin');
}
```
### Only let people edit themselves
### req.user.can(action) and req.user.is(action)
```javascript
user.useAuthorisationStrategy("edit user", function(user, action){
if(user.isAuthenticated){//You can remove this if already checking for anonymous users
if(this.params.userid){
if(user.id === this.params.userid){
return true;
}
}
}
});
`can` and `is` are synonyms everywhere they appear.
//Then you can use the following in express
app.get('/user/:userid/edit', user.can("edit user"), function(req,res){
//Only called if the user is editing themselves, not other people.
});
```
These functions return `true` or `false` depending on whether the user has access.
### Chain things
e.g.
```javascript
user.useAuthorisationStrategy("register", function(user){
if(!user.isAuthenticated) return true;
});
app.get('/', function (req, res) {
if (req.user.is('admin')) {
res.render('home/admin');
} else if (user.can('login')) {
res.render('home/login');
} else {
res.render('home');
}
})
```
user.useAuthorisationStrategy(function(user, action, stop){
if(!user.isAuthenticated){
stop(action === "anonymous");
}
});
### user.can(action) and user.is(action)
app.get("/register", user.is("anonymous"), user.can("register"), function(req,res){
//Only called if the user can register.
});
Inside the views of an express application you may use `user.can` and `user.is` which are equivallent to `req.user.can` and `req.user.is`
e.g.
```html
<% if (user.can('impersonate')) { %>
<button id="impersonate">Impersonate</button>
<% } %>
```
## Failure handler
**N.B.** not displaying a button doesn't mean someone can't do the thing that the button would do if clicked. The view is not where your security should go, but it is important for useability that you don't display buttons that will just result in 'access denied' where possible.
You can (and should) set the failure handler. This is called whenever a user fails authorization in route middleware.
### roles.setFailureHandler(fn(req, res, action))
It is set as follows:
You can (and should) set the failure handler. This is called whenever a user fails authorisation in route middleware.
Defaults to:
```javascript
user.setFailureHandler(function (req, res, action){
res.send(403);
res.send(403);
});
```
That, incidentally is the default implimentation. There is no "next" by design, to stop you accidentally calling it and allowing someone into a restricted part of your site. You are passed the action/role/ability which caused them to be denied access.
There is no "next" by design, to stop you accidentally calling it and allowing someone into a restricted part of your site. You are passed the action requested which caused them to be denied access.
### Redirect on failure
You could using this to redirect the user or render an error page:
You should probably consider using this to redirect the user, something like:
```javascript
user.setFailureHandler(function (req, res, action){
if(req.user){
res.redirect('/accessdenied?reason=' + action);
} else {
res.redirect('/login');
}
var accept = req.headers.accept || '';
res.status(403);
if(req.user.isAuthenticated){
if (~accept.indexOf('html')) {
res.render('access-denied', {action: action});
} else {
res.send('Access Denied - You don\'t have permission to: ' + action);
}
} else {
res.redirect('/login');
}
});
```
## Default User
## License
By default, the user middleware will set the user up to be `{}` and will then add the property `isAuthenticated = false`.
Roles will always add `isAuthenticated = false` but you can configure a default user object as follows.
```javascript
user.setDefaultUser({id:"anonymous"});
```
MIT
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc