Skip to content

koa support #262

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Follow [manast](http://twitter.com/manast) for news and updates regarding this l
- Roles
- Hierarchies
- Resources
- Express middleware for protecting resources.
- Express/KOA middleware for protecting resources.
- Robust implementation with good unit test coverage.

## Installation
Expand Down Expand Up @@ -169,10 +169,12 @@ It will return an array of resource:[permissions] like this:
```


Finally, we provide a middleware for Express for easy protection of resources.
Finally, we provide a middleware for Express/KOA for easy protection of resources.

```javascript
acl.middleware()
acl.middleware(0, '', '' true) // KOA
acl.middleware(0, '', '' true, function (err, ctx, next) { }) // KOA
```

We can protect a resource like this:
Expand Down Expand Up @@ -474,9 +476,9 @@ __Arguments__

<a name="middleware" />

### middleware( [numPathComponents, userId, permissions] )
### middleware( [numPathComponents, userId, permissions, isKoa, koaErrorHandler] )

Middleware for express.
Middleware for express/koa.

To create a custom getter for userId, pass a function(req, res) which returns the userId when called (must not be async).

Expand All @@ -486,6 +488,8 @@ __Arguments__
numPathComponents {Number} number of components in the url to be considered part of the resource name.
userId {String|Number|Function} the user id for the acl system (defaults to req.session.userId)
permissions {String|Array} the permission(s) to check for (defaults to req.method.toLowerCase())
isKoa {Boolean} is used in KOA
koaErrorHandler {Function} error handler when is used in KOA, arguments: (err, ctx, next)
```

---------------------------------------
Expand Down
69 changes: 61 additions & 8 deletions lib/acl.js
Original file line number Diff line number Diff line change
Expand Up @@ -630,15 +630,17 @@ Acl.prototype.clean = function(callback){
*/

/**
Express Middleware
Express/KOA Middleware

*/
Acl.prototype.middleware = function(numPathComponents, userId, actions){
Acl.prototype.middleware = function(numPathComponents, userId, actions, isKoa, koaErrorHandler){
contract(arguments)
.params()
.params('number')
.params('number','string|number|function')
.params('number','string|number|function', 'string|array')
.params('number','string|number|function', 'string|array', 'boolean')
.params('number','string|number|function', 'string|array', 'boolean', 'function')
.end();

var acl = this;
Expand All @@ -652,12 +654,20 @@ Acl.prototype.middleware = function(numPathComponents, userId, actions){
this.constructor.prototype.__proto__ = Error.prototype;
}

return function(req, res, next){
var handler = function(req, res, next, reject){
var _userId = userId,
_actions = actions,
resource,
url;

var resolve,
error;
if (isKoa) {
resolve = next;
next = res;
res = undefined;
}

// call function to fetch userId
if(typeof userId === 'function'){
_userId = userId(req, res);
Expand All @@ -668,14 +678,24 @@ Acl.prototype.middleware = function(numPathComponents, userId, actions){
}else if((req.user) && (req.user.id)){
_userId = req.user.id;
}else{
next(new HttpError(401, 'User not authenticated'));
error = new HttpError(401, 'User not authenticated');
if (isKoa) {
reject(error);
} else {
next(error);
}
return;
}
}

// Issue #80 - Additional check
if (!_userId) {
next(new HttpError(401, 'User not authenticated'));
error = new HttpError(401, 'User not authenticated');
if (isKoa) {
reject(error);
} else {
next(error);
}
return;
}

Expand All @@ -694,21 +714,54 @@ Acl.prototype.middleware = function(numPathComponents, userId, actions){

acl.isAllowed(_userId, resource, _actions, function(err, allowed){
if (err){
next(new Error('Error checking permissions to access resource'));
error = new Error('Error checking permissions to access resource');
if (isKoa) {
reject(error);
} else {
next(error);
}
}else if(allowed === false){
if (acl.logger) {
acl.logger.debug('Not allowed '+_actions+' on '+resource+' by user '+_userId);
acl.allowedPermissions(_userId, resource, function(err, obj){
acl.logger.debug('Allowed permissions: '+util.inspect(obj));
});
}
next(new HttpError(403,'Insufficient permissions to access resource'));
error = new HttpError(403,'Insufficient permissions to access resource');
if (isKoa) {
reject(error);
} else {
next(error);
}
}else{
acl.logger?acl.logger.debug('Allowed '+_actions+' on '+resource+' by user '+_userId):null;
next();
if (isKoa) {
resolve();
} else {
next();
}
}
});
};
if (isKoa && Number(process.version.match(/^v(\d+\.\d+)/)[1]) < 7.6) {
throw new Error('Koa requires node v7.6.0 or higher for ES2015 and async function support.');
}
return isKoa ? eval('(async function (ctx, next) {\
try {\
await (new Promise(function (resolve, reject) {\
handler(ctx, next, resolve, reject);\
}));\
await next();\
} catch (err) {\
if (koaErrorHandler) {\
return await koaErrorHandler(err, ctx, next);\
}\
ctx.status = err.errorCode;\
ctx.body = err;\
}\
})') : function (req, res, next) {
handler(req, res, next);
};
};

/**
Expand Down
6 changes: 3 additions & 3 deletions lib/mongodb-backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ MongoDBBackend.prototype = {
values.forEach(function(value){doc[value]=true;});

// update document
collection.update(updateParams,{$set:doc},{safe:true,upsert:true},function(err){
collection.updateOne(updateParams,{$set:doc},{safe:true,upsert:true},function(err){
if(err instanceof Error) return cb(err);
cb(undefined);
});
Expand All @@ -135,7 +135,7 @@ MongoDBBackend.prototype = {
transaction.push(function(cb) {
self.db.collection(self.prefix + self.removeUnsupportedChar(collName), function(err,collection){
// Create index
collection.ensureIndex({_bucketname: 1, key: 1}, function(err){
collection.createIndex({_bucketname: 1, key: 1}, function(err){
if (err instanceof Error) {
return cb(err);
} else{
Expand Down Expand Up @@ -191,7 +191,7 @@ MongoDBBackend.prototype = {
values.forEach(function(value){doc[value]=true;});

// update document
collection.update(updateParams,{$unset:doc},{safe:true,upsert:true},function(err){
collection.updateOne(updateParams,{$unset:doc},{safe:true,upsert:true},function(err){
if(err instanceof Error) return cb(err);
cb(undefined);
});
Expand Down
4 changes: 2 additions & 2 deletions lib/redis-backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,10 @@ RedisBackend.prototype = {
var self = this;
if(Array.isArray(keys)){
return keys.map(function(key){
return self.prefix+'_'+bucket+'@'+key;
return self.prefix+'_'+bucket+':'+key;
});
}else{
return self.prefix+'_'+bucket+'@'+keys;
return self.prefix+'_'+bucket+':'+keys;
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "acl",
"version": "0.4.11",
"description": "An Access Control List module, based on Redis with Express middleware support",
"name": "acl-express_koa",
"version": "0.4.16",
"description": "An Access Control List module, based on Redis with Express/KOA middleware support",
"keywords": [
"middleware",
"acl",
Expand All @@ -18,7 +18,7 @@
"async": "^2.1.4",
"bluebird": "^3.0.2",
"lodash": "^4.17.3",
"mongodb": "^2.0.47",
"mongodb": "^3.5.7",
"redis": "^2.2.5"
},
"devDependencies": {
Expand Down