diff --git a/README.md b/README.md index 35a6535..8a2ea59 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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: @@ -474,9 +476,9 @@ __Arguments__ -### 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). @@ -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) ``` --------------------------------------- diff --git a/lib/acl.js b/lib/acl.js index 9851717..606cfb9 100644 --- a/lib/acl.js +++ b/lib/acl.js @@ -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; @@ -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); @@ -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; } @@ -694,7 +714,12 @@ 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); @@ -702,13 +727,41 @@ Acl.prototype.middleware = function(numPathComponents, userId, actions){ 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); + }; }; /** diff --git a/lib/mongodb-backend.js b/lib/mongodb-backend.js index e3eb8de..127731d 100644 --- a/lib/mongodb-backend.js +++ b/lib/mongodb-backend.js @@ -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); }); @@ -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{ @@ -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); }); diff --git a/lib/redis-backend.js b/lib/redis-backend.js index c5b7705..69445c6 100644 --- a/lib/redis-backend.js +++ b/lib/redis-backend.js @@ -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; } } } diff --git a/package.json b/package.json index 496e6c6..17eb308 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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": {