diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d63ab9a..eed613e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node: ['12', '14', '16'] + node: ['16', '18', '20'] steps: - uses: actions/checkout@v2 @@ -30,8 +30,8 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node: ['12', '14', '16'] - redis-version: [4, 5, 6] + node: ['16', '18', '20'] + redis-version: [5, 6, 7] steps: - uses: actions/checkout@v2 @@ -57,8 +57,8 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node: ['12', '14', '16'] - mongodb-version: [4.0, 5.0] + node: ['16', '18', '20'] + mongodb-version: [6, 7] steps: - uses: actions/checkout@v2 @@ -74,9 +74,9 @@ jobs: - run: npm install - - run: npm install mongodb@4 + - run: npm install mongodb@6 - run: npm run test_mongo - - run: npm install mongodb@3 + - run: npm install mongodb@5 - run: npm run test_mongo test_mongo_single: @@ -85,8 +85,8 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node: ['12', '14', '16'] - mongodb-version: [4.0, 5.0] + node: ['16', '18', '20'] + mongodb-version: [6, 7] steps: - uses: actions/checkout@v2 @@ -102,7 +102,7 @@ jobs: - run: npm install - - run: npm install mongodb@4 + - run: npm install mongodb@6 - run: npm run test_mongo_single - - run: npm install mongodb@3 + - run: npm install mongodb@5 - run: npm run test_mongo_single diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..b6a7d89 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +16 diff --git a/README.md b/README.md index 1a64605..e20cf02 100644 --- a/README.md +++ b/README.md @@ -16,43 +16,37 @@ A Redis, MongoDB and In-Memory based backends are provided built-in in the modul ### Breaking changes comparing to the original `acl` -* The backend constructors take options object instead of multiple argument. +- The backend constructors take options object instead of multiple argument. Original `acl`: + ```js -new ACL.mongodbBackend(db, prefix, useSingle, useRawCollectionNames) -new ACL.redisBackend(redis, prefix) +new ACL.mongodbBackend(db, prefix, useSingle, useRawCollectionNames); +new ACL.redisBackend(redis, prefix); ``` New `acl2`: + ```js new ACL.mongodbBackend({ client, db, prefix = "acl_", useSingle, useRawCollectionNames }) new ACL.redisBackend({ redis, prefix = "acl_" }) ``` -* The new default `"acl_"` prefix for both Redis and MongoDB. - -* The `mongodb` dependency upgraded from v2 to the latest v3 - v4. - -* Both `mongodb` and `redis` dependencies moved to `devDependencies`. You have to install them to your project separately. - -* The minimal supported nodejs version was `0.10`, but became the current LTS `12`. +- The new default `"acl_"` prefix for both Redis and MongoDB. -* The first published version of `acl2` is `1.0` to be more SemVer compliant. +- Maintained and modern code infrastructure. ### Other notable changes comparing to the original `acl` -* ES6 -* ESLint -* Prettier -* Internally use more promises, fewer callbacks for better stack traces -* Upgraded all possible dependencies -* Made unit test debuggable, split them by backend type -* MongoDB backend accepts either `client` or `db` [objects](https://github.com/mongodb/node-mongodb-native/blob/3.0/CHANGES_3.0.0.md) -* Removed all possible warnings -* Run CI tests using multiple MongoDB versions. - -### +- ES6 +- ESLint +- Prettier +- Promises only, no callbacks +- Upgraded all possible dependencies +- Made unit test debuggable, split them by backend type +- MongoDB backend accepts either `client` or `db` [objects](https://github.com/mongodb/node-mongodb-native/blob/3.0/CHANGES_3.0.0.md) +- Removed all possible warnings +- Run CI tests using multiple MongoDB versions. ## Features @@ -71,7 +65,7 @@ Using npm: npm install acl2 ``` -Optionally: +Optionally: ```shell script npm install mongodb @@ -104,10 +98,10 @@ npm install redis Create your acl module by requiring it and instantiating it with a valid backend instance: ```javascript -var ACL = require("acl2"); +const ACL = require("acl2"); // Using Redis backend -acl = new ACL(new ACL.redisBackend({ client: redisClient })); +acl = new ACL(new ACL.redisBackend({ redis: redisClient })); // Or Using the memory backend acl = new ACL(new ACL.memoryBackend()); @@ -116,50 +110,49 @@ acl = new ACL(new ACL.memoryBackend()); acl = new ACL(new ACL.mongodbBackend({ client: mongoClient })); ``` -See below for full list of backend constructor arguments. +See below for full list of backend constructor arguments. -All the following functions return a promise or optionally take a callback with -an err parameter as last parameter. We omit them in the examples for simplicity. +All the following functions return a promise. Create roles implicitly by giving them permissions: ```javascript // guest is allowed to view blogs -acl.allow("guest", "blogs", "view"); +await acl.allow("guest", "blogs", "view"); // allow function accepts arrays as any parameter -acl.allow("member", "blogs", ["edit", "view", "delete"]); +await acl.allow("member", "blogs", ["edit", "view", "delete"]); ``` Users are likewise created implicitly by assigning them roles: ```javascript -acl.addUserRoles("joed", "guest"); +await acl.addUserRoles("joed", "guest"); ``` Hierarchies of roles can be created by assigning parents to roles: ```javascript -acl.addRoleParents("baz", ["foo", "bar"]); +await acl.addRoleParents("baz", ["foo", "bar"]); ``` Note that the order in which you call all the functions is irrelevant (you can add parents first and assign permissions to roles later) ```javascript -acl.allow("foo", ["blogs", "forums", "news"], ["view", "delete"]); +await acl.allow("foo", ["blogs", "forums", "news"], ["view", "delete"]); ``` Use the wildcard to give all permissions: ```javascript -acl.allow("admin", ["blogs", "forums"], "*"); +await acl.allow("admin", ["blogs", "forums"], "*"); ``` Sometimes is necessary to set permissions on many different roles and resources. This would lead to unnecessary nested callbacks for handling errors. Instead use the following: ```javascript -acl.allow([ +await acl.allow([ { roles: ["guest", "member"], allows: [ @@ -180,17 +173,16 @@ acl.allow([ You can check if a user has permissions to access a given resource with _isAllowed_: ```javascript -acl.isAllowed("joed", "blogs", "view", function (err, res) { - if (res) { - console.log("User joed is allowed to view blogs"); - } -}); +const res = await acl.isAllowed("joed", "blogs", "view"); +if (res) { + console.log("User joed is allowed to view blogs"); +} ``` Of course arrays are also accepted in this function: ```javascript -acl.isAllowed("jsmith", "blogs", ["edit", "view", "delete"]); +await acl.isAllowed("jsmith", "blogs", ["edit", "view", "delete"]); ``` Note that all permissions must be fulfilled in order to get _true_. @@ -198,12 +190,8 @@ Note that all permissions must be fulfilled in order to get _true_. Sometimes is necessary to know what permissions a given user has over certain resources: ```javascript -acl.allowedPermissions("james", ["blogs", "forums"], function ( - err, - permissions -) { - console.log(permissions); -}); +const permissions = await acl.allowedPermissions("james", ["blogs", "forums"]); +console.log(permissions); ``` It will return an array of resource:[permissions] like this: @@ -227,7 +215,7 @@ app.put('/blogs/:id', acl.middleware(), function(req, res, next){…} The middleware will protect the resource named by _req.url_, pick the user from _req.session.userId_ and check the permission for _req.method_, so the above would be equivalent to something like this: ```javascript -acl.isAllowed(req.session.userId, "/blogs/12345", "put"); +await acl.isAllowed(req.session.userId, "/blogs/12345", "put"); ``` The middleware accepts 3 optional arguments, that are useful in some situations. For example, sometimes we @@ -249,85 +237,80 @@ app.put('/blogs/:id/comments/:commentId', acl.middleware(3, 'joed', 'post'), fun -### addUserRoles( userId, roles, function(err) ) +### addUserRoles( userId, roles ) Adds roles to a given user id. **Arguments** ```javascript - userId {String|Number} User id. + userId {String} User id. roles {String|Array} Role(s) to add to the user id. - callback {Function} Callback called when finished. ``` --- -### removeUserRoles( userId, roles, function(err) ) +### removeUserRoles( userId, roles ) Remove roles from a given user. **Arguments** ```javascript - userId {String|Number} User id. + userId {String} User id. roles {String|Array} Role(s) to remove to the user id. - callback {Function} Callback called when finished. ``` --- -### userRoles( userId, function(err, roles) ) +### userRoles( userId ) Return all the roles from a given user. **Arguments** ```javascript - userId {String|Number} User id. - callback {Function} Callback called when finished. + userId {String} User id. ``` --- -### roleUsers( rolename, function(err, users) ) +### roleUsers( roleName ) Return all users who has a given role. **Arguments** ```javascript - rolename {String|Number} User id. - callback {Function} Callback called when finished. + roleName {String} User id. ``` --- -### hasRole( userId, rolename, function(err, hasRole) ) +### hasRole( userId, rolroleNameename ) Return boolean whether user has the role **Arguments** ```javascript - userId {String|Number} User id. - rolename {String|Number} role name. - callback {Function} Callback called when finished. + userId {String} User id. + roleName {String} role name. ``` --- -### addRoleParents( role, parents, function(err) ) +### addRoleParents( role, parents ) Adds a parent or parent list to role. @@ -336,14 +319,13 @@ Adds a parent or parent list to role. ```javascript role {String} Child role. parents {String|Array} Parent role(s) to be added. - callback {Function} Callback called when finished. ``` --- -### removeRoleParents( role, parents, function(err) ) +### removeRoleParents( role, parents ) Removes a parent or parent list from role. @@ -354,14 +336,13 @@ If `parents` is not specified, removes all parents. ```javascript role {String} Child role. parents {String|Array} Parent role(s) to be removed [optional]. - callback {Function} Callback called when finished [optional]. ``` --- -### removeRole( role, function(err) ) +### removeRole( role ) Removes a role from the system. @@ -369,13 +350,13 @@ Removes a role from the system. ```javascript role {String} Role to be removed - callback {Function} Callback called when finished. ``` --- -### removeResource( resource, function(err) ) + +### removeResource( resource ) Removes a resource from the system @@ -383,14 +364,13 @@ Removes a resource from the system ```javascript resource {String} Resource to be removed - callback {Function} Callback called when finished. ``` --- -### allow( roles, resources, permissions, function(err) ) +### allow( roles, resources, permissions ) Adds the given permissions to the given roles over the given resources. @@ -400,25 +380,22 @@ Adds the given permissions to the given roles over the given resources. roles {String|Array} role(s) to add permissions to. resources {String|Array} resource(s) to add permisisons to. permissions {String|Array} permission(s) to add to the roles over the resources. - callback {Function} Callback called when finished. ``` -### allow( permissionsArray, function(err) ) +### allow( permissionsArray ) **Arguments** ```javascript permissionsArray {Array} Array with objects expressing what permissions to give. [{roles:{String|Array}, allows:[{resources:{String|Array}, permissions:{String|Array}]] - - callback {Function} Callback called when finished. ``` --- -### removeAllow( role, resources, permissions, function(err) ) +### removeAllow( role, resources ) Remove permissions from the given roles owned by the given role. @@ -430,14 +407,13 @@ Note: we loose atomicity when removing empty role_resources. role {String} resources {String|Array} permissions {String|Array} - callback {Function} ``` --- -### allowedPermissions( userId, resources, function(err, obj) ) +### allowedPermissions( userId, resources ) Returns all the allowable permissions a given user have to access the given resources. @@ -448,16 +424,15 @@ resource name to a list of permissions for that resource. **Arguments** ```javascript - userId {String|Number} User id. + userId {String} User id. resources {String|Array} resource(s) to ask permissions for. - callback {Function} Callback called when finished. ``` --- -### isAllowed( userId, resource, permissions, function(err, allowed) ) +### isAllowed( userId, resource, permissions ) Checks if the given user is allowed to access the resource for the given permissions (note: it must fulfill all the permissions). @@ -465,17 +440,16 @@ permissions (note: it must fulfill all the permissions). **Arguments** ```javascript - userId {String|Number} User id. + userId {String} User id. resource {String} resource to ask permissions for. permissions {String|Array} asked permissions. - callback {Function} Callback called with the result. ``` --- -### areAnyRolesAllowed( roles, resource, permissions, function(err, allowed) ) +### areAnyRolesAllowed( roles, resource, permissions ) Returns true if any of the given roles have the right permissions. @@ -485,14 +459,13 @@ Returns true if any of the given roles have the right permissions. roles {String|Array} Role(s) to check the permissions for. resource {String} resource to ask permissions for. permissions {String|Array} asked permissions. - callback {Function} Callback called with the result. ``` --- -### whatResources(role, function(err, {resourceName: [permissions]}) +### whatResources(role) : {resourceName: [permissions]} Returns what resources a given role has permissions over. @@ -500,10 +473,9 @@ Returns what resources a given role has permissions over. ```javascript role {String|Array} Roles - callback {Function} Callback called with the result. ``` -whatResources(role, permissions, function(err, resources) ) +whatResources(role, permissions) : resources Returns what resources a role has the given permissions over. @@ -512,7 +484,6 @@ Returns what resources a role has the given permissions over. ```javascript role {String|Array} Roles permissions {String|Array} Permissions - callback {Function} Callback called with the result. ``` --- @@ -529,7 +500,7 @@ To create a custom getter for userId, pass a function(req, res) which returns th ```javascript 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) + userId {String} 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()) ``` @@ -544,7 +515,7 @@ Creates a MongoDB backend instance. **Arguments** ```javascript - client {Object} MongoClient instance. If missing, the `db` will be used. + client {Object} MongoClient instance. If missing, the `db` will be used. db {Object} Database instance. If missing, the `client` will be used. prefix {String} Optional collection prefix. Default is "acl_". useSingle {Boolean} Create one collection for all resources (defaults to false) @@ -553,7 +524,9 @@ Creates a MongoDB backend instance. Example: ```javascript -const client = await require("mongodb").connect("mongodb://127.0.0.1:27017/acl_test"); +const client = await require("mongodb").connect( + "mongodb://127.0.0.1:27017/acl_test" +); const ACL = require("acl2"); const acl = new ACL(new ACL.mongodbBackend({ client, useSingle: true })); ``` @@ -565,16 +538,18 @@ Creates a Redis backend instance. **Arguments** ```javascript - client {Object} Redis client instance. + client {Object} Redis client instance. prefix {String} Optional prefix. Default is "acl_". ``` Example: ```javascript -var client = require("redis").createClient(6379, "127.0.0.1", { no_ready_check: true }); +const client = await require("redis").createClient(6379, "127.0.0.1").connect(); const ACL = require("acl2"); -const acl = new ACL(new acl.redisBackend({ client, prefix: "my_acl_prefix_" })); +const acl = new ACL( + new acl.redisBackend({ redis: client, prefix: "my_acl_prefix_" }) +); ``` ## Tests @@ -592,4 +567,4 @@ npm run test_memory npm run test_redis npm run test_mongo npm run test_mongo_single -``` +``` diff --git a/lib/acl.js b/lib/acl.js index 753f60f..3a422c0 100644 --- a/lib/acl.js +++ b/lib/acl.js @@ -1,4 +1,4 @@ -/** +/* ACL System inspired on Zend_ACL. All functions accept strings, objects or arrays unless specified otherwise. @@ -29,16 +29,13 @@ a resource, then he can get exclusive write operation on the locked resource. This lock should expire if the resource has not been accessed in some time. */ -"use strict"; - -var _ = require("lodash"), +const _ = require("lodash"), util = require("util"), - bluebird = require("bluebird"), contract = require("./contract"); contract.debug = true; -var Acl = function (backend, logger, options) { +const Acl = function (backend, logger, options) { contract(arguments).params("object").params("object", "object").params("object", "object", "object").end(); options = _.extend( @@ -58,302 +55,236 @@ var Acl = function (backend, logger, options) { this.logger = logger; this.backend = backend; this.options = options; - - // Promisify async methods - backend.endAsync = bluebird.promisify(backend.end); - backend.getAsync = bluebird.promisify(backend.get); - backend.cleanAsync = bluebird.promisify(backend.clean); - backend.unionAsync = bluebird.promisify(backend.union); - if (backend.unions) { - backend.unionsAsync = bluebird.promisify(backend.unions); - } }; /** - addUserRoles( userId, roles, function(err) ) + addUserRoles( userId, roles ) Adds roles to a given user id. - @param {String|Number} User id. - @param {String|Array} Role(s) to add to the user id. - @param {Function} Callback called when finished. + @param {String} userId + @param {String|Array} roles to add to the user id. @return {Promise} Promise resolved when finished */ -Acl.prototype.addUserRoles = function (userId, roles, cb) { - contract(arguments) - .params("string|number", "string|array", "function") - .params("string|number", "string|array") - .end(); +Acl.prototype.addUserRoles = async function (userId, roles) { + contract(arguments).params("string", "string|array").end(); - var transaction = this.backend.begin(); - this.backend.add(transaction, this.options.buckets.meta, "users", userId); - this.backend.add(transaction, this.options.buckets.users, userId, roles); + const transaction = await this.backend.begin(); + await this.backend.add(transaction, this.options.buckets.meta, "users", userId); + await this.backend.add(transaction, this.options.buckets.users, userId, roles); if (Array.isArray(roles)) { for (const role of roles) { - this.backend.add(transaction, this.options.buckets.roles, role, userId); + await this.backend.add(transaction, this.options.buckets.roles, role, userId); } } else { - this.backend.add(transaction, this.options.buckets.roles, roles, userId); + await this.backend.add(transaction, this.options.buckets.roles, roles, userId); } - return this.backend.endAsync(transaction).nodeify(cb); + return await this.backend.end(transaction); }; /** - removeUserRoles( userId, roles, function(err) ) + removeUserRoles( userId, roles ) Remove roles from a given user. - @param {String|Number} User id. - @param {String|Array} Role(s) to remove to the user id. - @param {Function} Callback called when finished. + @param {String} userId + @param {String|Array} roles to remove to the user id. @return {Promise} Promise resolved when finished */ -Acl.prototype.removeUserRoles = function (userId, roles, cb) { - contract(arguments) - .params("string|number", "string|array", "function") - .params("string|number", "string|array") - .end(); +Acl.prototype.removeUserRoles = async function (userId, roles) { + contract(arguments).params("string", "string|array").end(); - var transaction = this.backend.begin(); - this.backend.remove(transaction, this.options.buckets.users, userId, roles); + const transaction = await this.backend.begin(); + await this.backend.remove(transaction, this.options.buckets.users, userId, roles); if (Array.isArray(roles)) { for (const role of roles) { - this.backend.remove(transaction, this.options.buckets.roles, role, userId); + await this.backend.remove(transaction, this.options.buckets.roles, role, userId); } } else { - this.backend.remove(transaction, this.options.buckets.roles, roles, userId); + await this.backend.remove(transaction, this.options.buckets.roles, roles, userId); } - return this.backend.endAsync(transaction).nodeify(cb); + return await this.backend.end(transaction); }; /** - userRoles( userId, function(err, roles) ) + userRoles( userId ) Return all the roles from a given user. - @param {String|Number} User id. - @param {Function} Callback called when finished. + @param {String} userId @return {Promise} Promise resolved with an array of user roles */ -Acl.prototype.userRoles = function (userId, cb) { - return this.backend.getAsync(this.options.buckets.users, userId).nodeify(cb); +Acl.prototype.userRoles = async function (userId) { + return await this.backend.get(this.options.buckets.users, userId); }; /** - roleUsers( roleName, function(err, users) ) + roleUsers( roleName ) : users Return all users who has a given role. - @param {String|Number} rolename. - @param {Function} Callback called when finished. + @param {String} roleName @return {Promise} Promise resolved with an array of users */ -Acl.prototype.roleUsers = function (roleName, cb) { - return this.backend.getAsync(this.options.buckets.roles, roleName).nodeify(cb); +Acl.prototype.roleUsers = function (roleName) { + return this.backend.get(this.options.buckets.roles, roleName); }; /** - hasRole( userId, rolename, function(err, is_in_role) ) + hasRole( userId, roleName ) : is_in_role Return boolean whether user is in the role - @param {String|Number} User id. - @param {String|Number} rolename. - @param {Function} Callback called when finished. + @param {String} userId + @param {String} roleName @return {Promise} Promise resolved with boolean of whether user is in role */ -Acl.prototype.hasRole = function (userId, rolename, cb) { - return this.userRoles(userId) - .then((roles) => roles.indexOf(rolename) != -1) - .nodeify(cb); +Acl.prototype.hasRole = async function (userId, roleName) { + let roles = await this.userRoles(userId); + return roles.indexOf(roleName) !== -1; }; /** - addRoleParents( role, parents, function(err) ) + addRoleParents( role, parents ) Adds a parent or parent list to role. - @param {String} Child role. - @param {String|Array} Parent role(s) to be added. - @param {Function} Callback called when finished. + @param {String} role Child role. + @param {String|Array} parents Parent role(s) to be added. @return {Promise} Promise resolved when finished */ -Acl.prototype.addRoleParents = function (role, parents, cb) { - contract(arguments) - .params("string|number", "string|array", "function") - .params("string|number", "string|array") - .end(); +Acl.prototype.addRoleParents = async function (role, parents) { + contract(arguments).params("string", "string|array").end(); - var transaction = this.backend.begin(); - this.backend.add(transaction, this.options.buckets.meta, "roles", role); - this.backend.add(transaction, this.options.buckets.parents, role, parents); - return this.backend.endAsync(transaction).nodeify(cb); + const transaction = await this.backend.begin(); + await this.backend.add(transaction, this.options.buckets.meta, "roles", role); + await this.backend.add(transaction, this.options.buckets.parents, role, parents); + return await this.backend.end(transaction); }; /** - removeRoleParents( role, parents, function(err) ) + removeRoleParents( role, parents ) Removes a parent or parent list from role. If `parents` is not specified, removes all parents. - @param {String} Child role. - @param {String|Array} Parent role(s) to be removed [optional]. - @param {Function} Callback called when finished [optional]. + @param {String} role Child role. + @param {String|Array} parents Parent role(s) to be removed [optional]. @return {Promise} Promise resolved when finished. */ -Acl.prototype.removeRoleParents = function (role, parents, cb) { - contract(arguments) - .params("string", "string|array", "function") - .params("string", "string|array") - .params("string", "function") - .params("string") - .end(); - - if (!cb && _.isFunction(parents)) { - cb = parents; - parents = null; - } +Acl.prototype.removeRoleParents = async function (role, parents) { + contract(arguments).params("string", "string|array").params("string").end(); - var transaction = this.backend.begin(); + const transaction = await this.backend.begin(); if (parents) { - this.backend.remove(transaction, this.options.buckets.parents, role, parents); + await this.backend.remove(transaction, this.options.buckets.parents, role, parents); } else { - this.backend.del(transaction, this.options.buckets.parents, role); + await this.backend.del(transaction, this.options.buckets.parents, role); } - return this.backend.endAsync(transaction).nodeify(cb); + return await this.backend.end(transaction); }; /** - removeRole( role, function(err) ) + removeRole( role ) Removes a role from the system. - @param {String} Role to be removed - @param {Function} Callback called when finished. + @param {String} role Role to be removed */ -Acl.prototype.removeRole = function (role, cb) { - contract(arguments).params("string", "function").params("string").end(); +Acl.prototype.removeRole = async function (role) { + contract(arguments).params("string").end(); // Note that this is not fully transactional. - return this.backend - .getAsync(this.options.buckets.resources, role) - .then((resources) => { - var transaction = this.backend.begin(); - - for (const resource of resources) { - var bucket = allowsBucket(resource); - this.backend.del(transaction, bucket, role); - } - - this.backend.del(transaction, this.options.buckets.resources, role); - this.backend.del(transaction, this.options.buckets.parents, role); - this.backend.del(transaction, this.options.buckets.roles, role); - this.backend.remove(transaction, this.options.buckets.meta, "roles", role); - - // `users` collection keeps the removed role - // because we don't know what users have `role` assigned. - return this.backend.endAsync(transaction); - }) - .nodeify(cb); + let resources = await this.backend.get(this.options.buckets.resources, role); + const transaction = await this.backend.begin(); + for (const resource of resources) { + const bucket = allowsBucket(resource); + await this.backend.del(transaction, bucket, role); + } + await this.backend.del(transaction, this.options.buckets.resources, role); + await this.backend.del(transaction, this.options.buckets.parents, role); + await this.backend.del(transaction, this.options.buckets.roles, role); + await this.backend.remove(transaction, this.options.buckets.meta, "roles", role); + return await this.backend.end(transaction); }; /** - removeResource( resource, function(err) ) + removeResource( resource ) Removes a resource from the system - @param {String} Resource to be removed - @param {Function} Callback called when finished. + @param {String} resource Resource to be removed @return {Promise} Promise resolved when finished */ -Acl.prototype.removeResource = function (resource, cb) { - contract(arguments).params("string", "function").params("string").end(); - - return this.backend - .getAsync(this.options.buckets.meta, "roles") - .then((roles) => { - var transaction = this.backend.begin(); - this.backend.del(transaction, allowsBucket(resource), roles); - for (const role of roles) { - this.backend.remove(transaction, this.options.buckets.resources, role, resource); - } - return this.backend.endAsync(transaction); - }) - .nodeify(cb); +Acl.prototype.removeResource = async function (resource) { + contract(arguments).params("string").end(); + + let roles = await this.backend.get(this.options.buckets.meta, "roles"); + const transaction = await this.backend.begin(); + await this.backend.del(transaction, allowsBucket(resource), roles); + for (const role of roles) { + await this.backend.remove(transaction, this.options.buckets.resources, role, resource); + } + return await this.backend.end(transaction); }; /** - allow( roles, resources, permissions, function(err) ) + allow( roles, resources, permissions ) Adds the given permissions to the given roles over the given resources. - @param {String|Array} role(s) to add permissions to. - @param {String|Array} resource(s) to add permisisons to. - @param {String|Array} permission(s) to add to the roles over the resources. - @param {Function} Callback called when finished. + @param {String|Array} roles role(s) to add permissions to. + @param {String|Array} resources resource(s) to add permisisons to. + @param {String|Array} permissions permission(s) to add to the roles over the resources. - allow( permissionsArray, function(err) ) + allow( permissionsArray ) - @param {Array} Array with objects expressing what permissions to give. + @param {Array} roles Array with objects expressing what permissions to give. [{roles:{String|Array}, allows:[{resources:{String|Array}, permissions:{String|Array}]] - @param {Function} Callback called when finished. @return {Promise} Promise resolved when finished */ -Acl.prototype.allow = function (roles, resources, permissions, cb) { - contract(arguments) - .params("string|array", "string|array", "string|array", "function") - .params("string|array", "string|array", "string|array") - .params("array", "function") - .params("array") - .end(); +Acl.prototype.allow = async function (roles, resources, permissions) { + contract(arguments).params("string|array", "string|array", "string|array").params("array").end(); - if (arguments.length === 1 || (arguments.length === 2 && _.isObject(roles) && _.isFunction(resources))) { - return this._allowEx(roles).nodeify(resources); + if (arguments.length === 1 || (arguments.length === 2 && _.isObject(roles))) { + return await this._allowEx(roles); } else { roles = makeArray(roles); resources = makeArray(resources); - var transaction = this.backend.begin(); + const transaction = await this.backend.begin(); - this.backend.add(transaction, this.options.buckets.meta, "roles", roles); + await this.backend.add(transaction, this.options.buckets.meta, "roles", roles); for (const resource of resources) { for (const role of roles) { - this.backend.add(transaction, allowsBucket(resource), role, permissions); + await this.backend.add(transaction, allowsBucket(resource), role, permissions); } } for (const role of roles) { - this.backend.add(transaction, this.options.buckets.resources, role, resources); + await this.backend.add(transaction, this.options.buckets.resources, role, resources); } - return this.backend.endAsync(transaction).nodeify(cb); + return await this.backend.end(transaction); } }; -Acl.prototype.removeAllow = function (role, resources, permissions, cb) { - contract(arguments) - .params("string", "string|array", "string|array", "function") - .params("string", "string|array", "string|array") - .params("string", "string|array", "function") - .params("string", "string|array") - .end(); +Acl.prototype.removeAllow = function (role, resources, permissions) { + contract(arguments).params("string", "string|array", "string|array").params("string", "string|array").end(); resources = makeArray(resources); - if (cb || (permissions && !_.isFunction(permissions))) { + if (permissions) { permissions = makeArray(permissions); - } else { - cb = permissions; - permissions = null; } - return this.removePermissions(role, resources, permissions, cb); + return this.removePermissions(role, resources, permissions); }; /** @@ -363,46 +294,41 @@ Acl.prototype.removeAllow = function (role, resources, permissions, cb) { Note: we loose atomicity when removing empty role_resources. - @param {String} - @param {String|Array} - @param {String|Array} + @param {String} role + @param {String|Array} resources + @param {String|Array} permissions */ -Acl.prototype.removePermissions = function (role, resources, permissions, cb) { - var transaction = this.backend.begin(); +Acl.prototype.removePermissions = async function (role, resources, permissions) { + const transaction = await this.backend.begin(); for (const resource of resources) { - var bucket = allowsBucket(resource); + const bucket = allowsBucket(resource); if (permissions) { - this.backend.remove(transaction, bucket, role, permissions); + await this.backend.remove(transaction, bucket, role, permissions); } else { - this.backend.del(transaction, bucket, role); - this.backend.remove(transaction, this.options.buckets.resources, role, resource); + await this.backend.del(transaction, bucket, role); + await this.backend.remove(transaction, this.options.buckets.resources, role, resource); } } // Remove resource from role if no rights for that role exists. // Not fully atomic... - return this.backend - .endAsync(transaction) - .then(() => { - var transaction = this.backend.begin(); - return bluebird - .all( - resources.map((resource) => { - var bucket = allowsBucket(resource); - return this.backend.getAsync(bucket, role).then((permissions) => { - if (permissions.length == 0) { - this.backend.remove(transaction, this.options.buckets.resources, role, resource); - } - }); - }) - ) - .then(() => this.backend.endAsync(transaction)); + await this.backend.end(transaction); + + const transaction2 = await this.backend.begin(); + await Promise.all( + resources.map(async (resource) => { + const bucket = allowsBucket(resource); + let permissions1 = await this.backend.get(bucket, role); + if (permissions1.length === 0) { + await this.backend.remove(transaction2, this.options.buckets.resources, role, resource); + } }) - .nodeify(cb); + ); + return await this.backend.end(transaction2); }; /** - allowedPermissions( userId, resources, function(err, obj) ) + allowedPermissions( userId, resources ) : obj Returns all the allowable permissions a given user have to access the given resources. @@ -410,42 +336,32 @@ Acl.prototype.removePermissions = function (role, resources, permissions, cb) { It returns an array of objects where every object maps a resource name to a list of permissions for that resource. - @param {String|Number} User id. - @param {String|Array} resource(s) to ask permissions for. - @param {Function} Callback called when finished. + @param {String} userId + @param {String|Array} resources resource(s) to ask permissions for. */ -Acl.prototype.allowedPermissions = function (userId, resources, cb) { - if (!userId) return cb(null, {}); +Acl.prototype.allowedPermissions = async function (userId, resources) { + if (!userId) return {}; - contract(arguments) - .params("string|number", "string|array", "function") - .params("string|number", "string|array") - .end(); + contract(arguments).params("string", "string|array").end(); - if (this.backend.unionsAsync) { - return this.optimizedAllowedPermissions(userId, resources, cb); + if (this.backend.unions) { + return this.optimizedAllowedPermissions(userId, resources); } resources = makeArray(resources); - return this.userRoles(userId) - .then((roles) => { - var result = {}; - return bluebird - .all( - resources.map((resource) => - this._resourcePermissions(roles, resource).then((permissions) => { - result[resource] = permissions; - }) - ) - ) - .then(() => result); + const roles = await this.userRoles(userId); + const result = {}; + await Promise.all( + resources.map(async (resource) => { + result[resource] = await this._resourcePermissions(roles, resource); }) - .nodeify(cb); + ); + return result; }; /** - optimizedAllowedPermissions( userId, resources, function(err, obj) ) + optimizedAllowedPermissions( userId, resources ): obj Returns all the allowable permissions a given user have to access the given resources. @@ -455,182 +371,122 @@ Acl.prototype.allowedPermissions = function (userId, resources, cb) { This is the same as allowedPermissions, it just takes advantage of the unions function if available to reduce the number of backend queries. - @param {String|Number} User id. - @param {String|Array} resource(s) to ask permissions for. - @param {Function} Callback called when finished. + @param {String} userId + @param {String|Array} resources resource(s) to ask permissions for. */ -Acl.prototype.optimizedAllowedPermissions = function (userId, resources, cb) { - if (!userId) { - return cb(null, {}); - } +Acl.prototype.optimizedAllowedPermissions = async function (userId, resources) { + if (!userId) return {}; - contract(arguments) - .params("string|number", "string|array", "function|undefined") - .params("string|number", "string|array") - .end(); + contract(arguments).params("string", "string|array").end(); resources = makeArray(resources); + let response; + const roles = await this._allUserRoles(userId); + const buckets = resources.map(allowsBucket); + if (roles.length === 0) { + const emptyResult = {}; + for (const bucket of buckets) { + emptyResult[bucket] = []; + } + response = emptyResult; + } else { + response = await this.backend.unions(buckets, roles); + } - return this._allUserRoles(userId) - .then((roles) => { - var buckets = resources.map(allowsBucket); - if (roles.length === 0) { - var emptyResult = {}; - for (const bucket of buckets) { - emptyResult[bucket] = []; - } - return bluebird.resolve(emptyResult); - } - - return this.backend.unionsAsync(buckets, roles); - }) - .then((response) => { - var result = {}; - for (const bucket of Object.keys(response)) { - result[keyFromAllowsBucket(bucket)] = response[bucket]; - } + const result = {}; + for (const bucket of Object.keys(response)) { + result[keyFromAllowsBucket(bucket)] = response[bucket]; + } - return result; - }) - .nodeify(cb); + return result; }; /** - isAllowed( userId, resource, permissions, function(err, allowed) ) + isAllowed( userId, resource, permissions ) Checks if the given user is allowed to access the resource for the given permissions (note: it must fulfill all the permissions). - @param {String|Number} User id. - @param {String|Array} resource(s) to ask permissions for. - @param {String|Array} asked permissions. - @param {Function} Callback called wish the result. + @param {String} userId + @param {String|Array} resource resource(s) to ask permissions for. + @param {String|Array} permissions asked permissions. */ -Acl.prototype.isAllowed = function (userId, resource, permissions, cb) { - contract(arguments) - .params("string|number", "string", "string|array", "function") - .params("string|number", "string", "string|array") - .end(); +Acl.prototype.isAllowed = async function (userId, resource, permissions) { + contract(arguments).params("string", "string", "string|array").end(); - return this.backend - .getAsync(this.options.buckets.users, userId) - .then((roles) => { - if (roles.length) { - return this.areAnyRolesAllowed(roles, resource, permissions); - } else { - return false; - } - }) - .nodeify(cb); + let roles = await this.backend.get(this.options.buckets.users, userId); + if (roles.length) { + return this.areAnyRolesAllowed(roles, resource, permissions); + } else { + return false; + } }; /** - areAnyRolesAllowed( roles, resource, permissions, function(err, allowed) ) + areAnyRolesAllowed( roles, resource, permissions ) : allowed Returns true if any of the given roles have the right permissions. - @param {String|Array} Role(s) to check the permissions for. - @param {String} resource(s) to ask permissions for. - @param {String|Array} asked permissions. - @param {Function} Callback called with the result. + @param {String|Array} roles to check the permissions for. + @param {String} resource resource(s) to ask permissions for. + @param {String|Array} permissions asked permissions. */ -Acl.prototype.areAnyRolesAllowed = function (roles, resource, permissions, cb) { - contract(arguments) - .params("string|array", "string", "string|array", "function") - .params("string|array", "string", "string|array") - .end(); +Acl.prototype.areAnyRolesAllowed = async function (roles, resource, permissions) { + contract(arguments).params("string|array", "string", "string|array").end(); roles = makeArray(roles); permissions = makeArray(permissions); if (roles.length === 0) { - return bluebird.resolve(false).nodeify(cb); + return false; } else { - return this._checkPermissions(roles, resource, permissions).nodeify(cb); + return await this._checkPermissions(roles, resource, permissions); } }; /** - whatResources(role, function(err, {resourceName: [permissions]}) + whatResources(role) : {resourceName: [permissions]} Returns what resources a given role or roles have permissions over. - whatResources(role, permissions, function(err, resources) ) + whatResources(role, permissions) : resources Returns what resources a role has the given permissions over. - @param {String|Array} Roles - @param {String|Array} Permissions - @param {Function} Callback called wish the result. + @param {String|Array} roles + @param {String|Array} permissions */ -Acl.prototype.whatResources = function (roles, permissions, cb) { - contract(arguments) - .params("string|array") - .params("string|array", "string|array") - .params("string|array", "function") - .params("string|array", "string|array", "function") - .end(); +Acl.prototype.whatResources = function (roles, permissions) { + contract(arguments).params("string|array").params("string|array", "string|array").end(); roles = makeArray(roles); - if (_.isFunction(permissions)) { - cb = permissions; - permissions = undefined; - } else if (permissions) { - permissions = makeArray(permissions); - } - - return this.permittedResources(roles, permissions, cb); -}; + permissions = !permissions ? undefined : makeArray(permissions); -Acl.prototype.permittedResources = function (roles, permissions, cb) { - var result = _.isUndefined(permissions) ? {} : []; - return this._rolesResources(roles) - .then((resources) => - bluebird - .all( - resources.map((resource) => - this._resourcePermissions(roles, resource).then((p) => { - if (permissions) { - var commonPermissions = _.intersection(permissions, p); - if (commonPermissions.length > 0) { - result.push(resource); - } - } else { - result[resource] = p; - } - }) - ) - ) - .then(() => result) - ) - .nodeify(cb); + return this.permittedResources(roles, permissions); }; -/** - clean () - - Cleans all the keys with the given prefix from redis. +Acl.prototype.permittedResources = async function (roles, permissions) { + const result = _.isUndefined(permissions) ? {} : []; + let resources = await this._rolesResources(roles); + await Promise.all( + resources.map(async (resource) => { + let p = await this._resourcePermissions(roles, resource); + if (permissions) { + const commonPermissions = _.intersection(permissions, p); + if (commonPermissions.length > 0) { + result.push(resource); + } + } else { + result[resource] = p; + } + }) + ); - Note: this operation is not reversible!. -*/ -/* -Acl.prototype.clean = function(callback){ - var acl = this; - this.redis.keys(this.prefix+'*', function(err, keys){ - if(keys.length){ - acl.redis.del(keys, function(err){ - callback(err); - }); - }else{ - callback(); - } - }); + return result; }; -*/ /** Express Middleware - */ Acl.prototype.middleware = function (numPathComponents, userId, actions) { contract(arguments) @@ -640,7 +496,7 @@ Acl.prototype.middleware = function (numPathComponents, userId, actions) { .params("number", "string|number|function", "string|array") .end(); - var acl = this; + const acl = this; function HttpError(errorCode, msg) { this.errorCode = errorCode; @@ -652,7 +508,7 @@ Acl.prototype.middleware = function (numPathComponents, userId, actions) { } return function (req, res, next) { - var _userId = userId, + let _userId = userId, _actions = actions, resource, url; @@ -694,22 +550,23 @@ Acl.prototype.middleware = function (numPathComponents, userId, actions) { acl.logger ? acl.logger.debug("Requesting " + _actions + " on " + resource + " by user " + _userId) : null; - acl.isAllowed(_userId, resource, _actions, (err, allowed) => { - if (err) { - next(new Error("Error checking permissions to access resource")); - } else if (allowed === false) { - if (acl.logger) { - acl.logger.debug("Not allowed " + _actions + " on " + resource + " by user " + _userId); - acl.allowedPermissions(_userId, resource, (err, obj) => { + acl.isAllowed(_userId, resource, _actions) + .then(async (allowed) => { + if (allowed === false) { + if (acl.logger) { + acl.logger.debug("Not allowed " + _actions + " on " + resource + " by user " + _userId); + const obj = acl.allowedPermissions(_userId, resource); acl.logger.debug("Allowed permissions: " + util.inspect(obj)); - }); + } + next(new HttpError(403, "Insufficient permissions to access resource")); + } else { + acl.logger + ? acl.logger.debug("Allowed " + _actions + " on " + resource + " by user " + _userId) + : null; + next(); } - next(new HttpError(403, "Insufficient permissions to access resource")); - } else { - acl.logger ? acl.logger.debug("Allowed " + _actions + " on " + resource + " by user " + _userId) : null; - next(); - } - }); + }) + .catch(() => next(new Error("Error checking permissions to access resource"))); }; }; @@ -719,7 +576,7 @@ Acl.prototype.middleware = function (numPathComponents, userId, actions) { @param {String} [contentType] (html|json) defaults to plain text */ Acl.prototype.middleware.errorHandler = function (contentType) { - var method = "end"; + let method = "end"; if (contentType) { switch (contentType) { @@ -750,9 +607,9 @@ Acl.prototype.middleware.errorHandler = function (contentType) { Acl.prototype._allowEx = function (objs) { objs = makeArray(objs); - var demuxed = []; + const demuxed = []; for (const obj of objs) { - var roles = obj.roles; + const roles = obj.roles; for (const allow of obj.allows) { demuxed.push({ roles: roles, @@ -762,137 +619,98 @@ Acl.prototype._allowEx = function (objs) { } } - return bluebird.reduce(demuxed, (values, obj) => this.allow(obj.roles, obj.resources, obj.permissions), null); + return Promise.all(demuxed.map((obj) => this.allow(obj.roles, obj.resources, obj.permissions))); }; // // Returns the parents of the given roles // Acl.prototype._rolesParents = function (roles) { - return this.backend.unionAsync(this.options.buckets.parents, roles); + return this.backend.union(this.options.buckets.parents, roles); }; // // Return all roles in the hierarchy including the given roles. // -/* -Acl.prototype._allRoles = function(roleNames, cb){ - var _this = this, roles; - - _this._rolesParents(roleNames, function(err, parents){ - roles = _.union(roleNames, parents); - async.whilst( - function (){ - return parents.length >0; - }, - function (cb) { - _this._rolesParents(parents, function(err, result){ - if(!err){ - roles = _.union(roles, parents); - parents = result; - } - cb(err); - }); - }, - function(err){ - cb(err, roles); - } - ); - }); -}; -*/ -// -// Return all roles in the hierarchy including the given roles. -// -Acl.prototype._allRoles = function (roleNames) { - return this._rolesParents(roleNames).then((parents) => { - if (parents.length > 0) { - return this._allRoles(parents).then((parentRoles) => _.union(roleNames, parentRoles)); - } else { - return roleNames; - } - }); +Acl.prototype._allRoles = async function (roleNames) { + let parents = await this._rolesParents(roleNames); + if (parents.length > 0) { + let parentRoles = await this._allRoles(parents); + return _.union(roleNames, parentRoles); + } + + return roleNames; }; // // Return all roles in the hierarchy of the given user. // -Acl.prototype._allUserRoles = function (userId) { - return this.userRoles(userId).then((roles) => { - if (roles && roles.length > 0) { - return this._allRoles(roles); - } else { - return []; - } - }); +Acl.prototype._allUserRoles = async function (userId) { + let roles = await this.userRoles(userId); + if (roles && roles.length > 0) { + return this._allRoles(roles); + } + + return []; }; // // Returns an array with resources for the given roles. // -Acl.prototype._rolesResources = function (roles) { +Acl.prototype._rolesResources = async function (roles) { roles = makeArray(roles); - return this._allRoles(roles).then((allRoles) => { - var result = []; - - // check if bluebird.map simplifies this code - return bluebird - .all( - allRoles.map((role) => - this.backend.getAsync(this.options.buckets.resources, role).then((resources) => { - result = result.concat(resources); - }) - ) - ) - .then(() => result); - }); + let allRoles = await this._allRoles(roles); + let result = []; + await Promise.all( + allRoles.map((role) => + this.backend.get(this.options.buckets.resources, role).then((resources) => { + result = result.concat(resources); + }) + ) + ); + return result; }; // // Returns the permissions for the given resource and set of roles // -Acl.prototype._resourcePermissions = function (roles, resource) { +Acl.prototype._resourcePermissions = async function (roles, resource) { if (roles.length === 0) { - return bluebird.resolve([]); - } else { - return this.backend.unionAsync(allowsBucket(resource), roles).then((resourcePermissions) => - this._rolesParents(roles).then((parents) => { - if (parents && parents.length) { - return this._resourcePermissions(parents, resource).then((morePermissions) => - _.union(resourcePermissions, morePermissions) - ); - } else { - return resourcePermissions; - } - }) - ); + return []; } + + const resourcePermissions = await this.backend.union(allowsBucket(resource), roles); + + const parents = await this._rolesParents(roles); + if (parents && parents.length) { + const morePermissions = await this._resourcePermissions(parents, resource); + return _.union(resourcePermissions, morePermissions); + } + + return resourcePermissions; }; // // NOTE: This function will not handle circular dependencies and result in a crash. // -Acl.prototype._checkPermissions = function (roles, resource, permissions) { - return this.backend.unionAsync(allowsBucket(resource), roles).then((resourcePermissions) => { - if (resourcePermissions.indexOf("*") !== -1) { - return true; - } else { - permissions = permissions.filter((p) => resourcePermissions.indexOf(p) === -1); +Acl.prototype._checkPermissions = async function (roles, resource, permissions) { + let resourcePermissions = await this.backend.union(allowsBucket(resource), roles); + if (resourcePermissions.indexOf("*") !== -1) { + return true; + } - if (permissions.length === 0) { - return true; - } else { - return this.backend.unionAsync(this.options.buckets.parents, roles).then((parents) => { - if (parents && parents.length) { - return this._checkPermissions(parents, resource, permissions); - } else { - return false; - } - }); - } - } - }); + permissions = permissions.filter((p) => resourcePermissions.indexOf(p) === -1); + if (permissions.length === 0) { + return true; + } + + let parents = await this.backend.union(this.options.buckets.parents, roles); + if (parents && parents.length) { + return await this._checkPermissions(parents, resource, permissions); + } + + return false; }; //----------------------------------------------------------------------------- diff --git a/lib/backend.js b/lib/backend.js index 25674d4..e6740ad 100644 --- a/lib/backend.js +++ b/lib/backend.js @@ -6,72 +6,69 @@ Implement this API for providing a backend for the acl module. */ -var contract = require("./contract"); +const contract = require("./contract"); -var Backend = { +const Backend = { /** Begins a transaction. */ - begin() { + async begin() { // returns a transaction object }, /** Ends a transaction (and executes it) */ - end(transaction, cb) { - contract(arguments).params("object", "function").end(); + async end(transaction) { // Execute transaction }, /** Cleans the whole storage. */ - clean(cb) { - contract(arguments).params("function").end(); - }, + async clean() {}, /** Gets the contents at the bucket's key. */ - get(bucket, key, cb) { - contract(arguments).params("string", "string|number", "function").end(); + async get(bucket, key) { + contract(arguments).params("string", "string").end(); }, /** Gets the union of contents of the specified keys in each of the specified buckets and returns a mapping of bucket to union. */ - unions(bucket, keys, cb) { - contract(arguments).params("array", "array", "function").end(); + async unions(bucket, keys) { + contract(arguments).params("array", "array").end(); }, /** Returns the union of the values in the given keys. */ - union(bucket, keys, cb) { - contract(arguments).params("string", "array", "function").end(); + async union(bucket, keys) { + contract(arguments).params("string", "array").end(); }, /** Adds values to a given key inside a bucket. */ - add(transaction, bucket, key, values) { - contract(arguments).params("object", "string", "string|number", "string|array|number").end(); + async add(transaction, bucket, key, values) { + contract(arguments).params("object", "string", "string", "string|array").end(); }, /** Delete the given key(s) at the bucket */ - del(transaction, bucket, keys) { + async del(transaction, bucket, keys) { contract(arguments).params("object", "string", "string|array").end(); }, /** Removes values from a given key inside a bucket. */ - remove(transaction, bucket, key, values) { - contract(arguments).params("object", "string", "string|number", "string|array|number").end(); + async remove(transaction, bucket, key, values) { + contract(arguments).params("object", "string", "string", "string|array`").end(); }, }; diff --git a/lib/contract.js b/lib/contract.js index 7b74a6d..f6a6439 100644 --- a/lib/contract.js +++ b/lib/contract.js @@ -1,4 +1,4 @@ -/** +/* Design by Contract module (c) OptimalBits 2011. Roadmap: @@ -13,17 +13,16 @@ .end() */ -"use strict"; -var noop = {}; -var _ = require("lodash"); +const noop = {}; +const _ = require("lodash"); noop.params = function () { return this; }; noop.end = function () {}; -var contract = function (args) { +const contract = function (args) { if (contract.debug === true) { contract.fulfilled = false; contract.args = _.toArray(args); @@ -50,12 +49,12 @@ contract.end = function () { } }; -var typeOf = function (obj) { +const typeOf = function (obj) { return Array.isArray(obj) ? "array" : typeof obj; }; -var checkParams = function (args, contract) { - var fulfilled, types, type, i, j; +const checkParams = function (args, contract) { + let fulfilled, types, type, i, j; if (args.length !== contract.length) { return false; @@ -84,13 +83,13 @@ var checkParams = function (args, contract) { } }; -var printParamsError = function (args, checkedParams) { - var msg = "Parameter mismatch.\nInput:\n( ", +const printParamsError = function (args, checkedParams) { + let msg = "Parameter mismatch.\nInput:\n( ", type, i; _.each(args, function (input, key) { type = typeOf(input); - if (key != 0) { + if (key !== 0) { msg += ", "; } msg += input + ": " + type; @@ -105,10 +104,10 @@ var printParamsError = function (args, checkedParams) { console.log(msg); }; -var argsToString = function (args) { - var res = ""; +const argsToString = function (args) { + let res = ""; _.each(args, function (arg, key) { - if (key != 0) { + if (key !== 0) { res += ", "; } res += arg; diff --git a/lib/memory-backend.js b/lib/memory-backend.js index bb83839..e1109e4 100644 --- a/lib/memory-backend.js +++ b/lib/memory-backend.js @@ -1,11 +1,10 @@ -/** +/* Memory Backend. In-memory implementation of the storage. */ -"use strict"; -var contract = require("./contract"), +const contract = require("./contract"), _ = require("lodash"); function MemoryBackend() { @@ -13,9 +12,7 @@ function MemoryBackend() { } MemoryBackend.prototype = { - close(cb) { - cb(); - }, + async close() {}, /** Begins a transaction. @@ -28,45 +25,40 @@ MemoryBackend.prototype = { /** Ends a transaction (and executes it) */ - end(transaction, cb) { - contract(arguments).params("array", "function").end(); - + async end(transaction) { // Execute transaction - for (var i = 0, len = transaction.length; i < len; i++) { - transaction[i](); + for (let i = 0, len = transaction.length; i < len; i++) { + await transaction[i](); } - cb(); }, /** Cleans the whole storage. */ - clean(cb) { - contract(arguments).params("function").end(); + async clean() { this._buckets = {}; - cb(); }, /** Gets the contents at the bucket's key. */ - get(bucket, key, cb) { - contract(arguments).params("string", "string|number", "function").end(); + async get(bucket, key) { + contract(arguments).params("string", "string").end(); if (this._buckets[bucket]) { - cb(null, this._buckets[bucket][key] || []); + return this._buckets[bucket][key] || []; } else { - cb(null, []); + return []; } }, /** Gets the union of the keys in each of the specified buckets */ - unions(buckets, keys, cb) { - contract(arguments).params("array", "array", "function").end(); + async unions(buckets, keys) { + contract(arguments).params("array", "array").end(); - var results = {}; + const results = {}; for (const bucket of buckets) { if (this._buckets[bucket]) { @@ -76,17 +68,16 @@ MemoryBackend.prototype = { } } - cb(null, results); + return results; }, /** Returns the union of the values in the given keys. */ - union(bucket, keys, cb) { - contract(arguments).params("string", "array", "function").end(); + async union(bucket, keys) { + contract(arguments).params("string", "array").end(); - var match; - var re; + let match, re; if (!this._buckets[bucket]) { Object.keys(this._buckets).some(function (b) { re = new RegExp("^" + b + "$"); @@ -97,15 +88,15 @@ MemoryBackend.prototype = { } if (this._buckets[bucket]) { - var keyArrays = []; - for (var i = 0, len = keys.length; i < len; i++) { + const keyArrays = []; + for (let i = 0, len = keys.length; i < len; i++) { if (this._buckets[bucket][keys[i]]) { keyArrays.push.apply(keyArrays, this._buckets[bucket][keys[i]]); } } - cb(undefined, _.union(keyArrays)); + return _.union(keyArrays); } else { - cb(undefined, []); + return []; } }, @@ -113,7 +104,7 @@ MemoryBackend.prototype = { Adds values to a given key inside a bucket. */ add(transaction, bucket, key, values) { - contract(arguments).params("array", "string", "string|number", "string|array|number").end(); + contract(arguments).params("array", "string", "string", "string|array").end(); values = makeArray(values); @@ -139,7 +130,7 @@ MemoryBackend.prototype = { transaction.push(() => { if (this._buckets[bucket]) { - for (var i = 0, len = keys.length; i < len; i++) { + for (let i = 0, len = keys.length; i < len; i++) { delete this._buckets[bucket][keys[i]]; } } @@ -150,12 +141,12 @@ MemoryBackend.prototype = { Removes values from a given key inside a bucket. */ remove(transaction, bucket, key, values) { - contract(arguments).params("array", "string", "string|number", "string|array|number").end(); + contract(arguments).params("array", "string", "string", "string|array").end(); values = makeArray(values); transaction.push(() => { - var old; - if (this._buckets[bucket] && (old = this._buckets[bucket][key])) { + if (this._buckets[bucket] && this._buckets[bucket][key]) { + let old = this._buckets[bucket][key]; this._buckets[bucket][key] = _.difference(old, values); } }); diff --git a/lib/mongodb-backend.js b/lib/mongodb-backend.js index 5440dda..2d43eb5 100644 --- a/lib/mongodb-backend.js +++ b/lib/mongodb-backend.js @@ -1,16 +1,14 @@ -/** +/* MongoDB Backend. Implementation of the storage backend using MongoDB */ -"use strict"; -var contract = require("./contract"); -var async = require("async"); -var _ = require("lodash"); +const contract = require("./contract"); +const _ = require("lodash"); // Name of the collection where meta and allowsXXX are stored. // If prefix is specified, it will be prepended to this name, like acl_resources -var aclCollectionName = "resources"; +const aclCollectionName = "resources"; function MongoDBBackend({ client, db, prefix, useSingle, useRawCollectionNames }) { this.client = client; @@ -21,15 +19,14 @@ function MongoDBBackend({ client, db, prefix, useSingle, useRawCollectionNames } } MongoDBBackend.prototype = { - close(cb) { - if (this.client) this.client.close(cb); - else cb(); + async close() { + if (this.client) await this.client.close(); }, /** Begins a transaction. */ - begin() { + async begin() { // returns a transaction object(just an array of functions will do here.) return []; }, @@ -37,173 +34,142 @@ MongoDBBackend.prototype = { /** Ends a transaction (and executes it) */ - end(transaction, cb) { - contract(arguments).params("array", "function").end(); - async.series(transaction, function (err) { - cb(err instanceof Error ? err : undefined); - }); + async end(transaction) { + const promises = transaction.map((fn) => fn()); + await Promise.all(promises); }, /** Cleans the whole storage. */ - clean(cb) { - contract(arguments).params("function").end(); - this.db.collections(function (err, collections) { - if (err instanceof Error) return cb(err); - async.forEach( - collections, - function (coll, innercb) { - coll.drop(function () { - innercb(); - }); // ignores errors - }, - cb - ); - }); + async clean() { + const collections = await this.db.collections(); + const promises = collections.map((coll) => coll.drop()); + await Promise.all(promises); }, /** Gets the contents at the bucket's key. */ - get(bucket, key, cb) { - contract(arguments).params("string", "string|number", "function").end(); + async get(bucket, key) { key = encodeText(key); - var searchParams = this.useSingle ? { _bucketname: bucket, key: key } : { key: key }; - var collName = this.useSingle ? aclCollectionName : bucket; + const searchParams = this.useSingle ? { _bucketname: bucket, key: key } : { key: key }; + const collName = this.useSingle ? aclCollectionName : bucket; let collection = this.db.collection(this.prefix + this.removeUnsupportedChar(collName)); - if (!collection) return cb(new Error(`Cannot find the collection ${collName}`)); + if (!collection) throw new Error(`Cannot find the collection ${collName}`); // Excluding bucket field from search result - collection.findOne(searchParams, { projection: { _bucketname: 0 } }, function (err, doc) { - if (err) return cb(err); - if (!_.isObject(doc)) return cb(undefined, []); - doc = fixKeys(doc); - cb(undefined, _.without(_.keys(doc), "key", "_id", "_bucketname")); - }); + let doc = await collection.findOne(searchParams, { projection: { _bucketname: 0 } }); + if (!_.isObject(doc)) return []; + doc = fixKeys(doc); + return _.without(_.keys(doc), "key", "_id", "_bucketname"); }, /** Returns the union of the values in the given keys. */ - union(bucket, keys, cb) { - contract(arguments).params("string", "array", "function").end(); + async union(bucket, keys) { + contract(arguments).params("string", "array").end(); + keys = encodeAll(keys); - var searchParams = this.useSingle ? { _bucketname: bucket, key: { $in: keys } } : { key: { $in: keys } }; - var collName = this.useSingle ? aclCollectionName : bucket; + const searchParams = this.useSingle ? { _bucketname: bucket, key: { $in: keys } } : { key: { $in: keys } }; + const collName = this.useSingle ? aclCollectionName : bucket; let collection = this.db.collection(this.prefix + this.removeUnsupportedChar(collName)); - if (!collection) return cb(new Error(`Cannot find the collection ${collName}`)); + if (!collection) throw new Error(`Cannot find the collection ${collName}`); - collection.find(searchParams, { projection: { _bucketname: 0 } }).toArray(function (err, docs) { - if (err instanceof Error) return cb(err); - if (!docs.length) return cb(undefined, []); + let docs = await collection.find(searchParams, { projection: { _bucketname: 0 } }).toArray(); + if (!docs.length) return []; - var keyArrays = []; - docs = fixAllKeys(docs); - for (const doc of docs) { - keyArrays.push(...Object.keys(doc)); - } - cb(undefined, _.without(_.union(keyArrays), "key", "_id", "_bucketname")); - }); + const keyArrays = []; + docs = fixAllKeys(docs); + for (const doc of docs) { + keyArrays.push(...Object.keys(doc)); + } + return _.without(_.union(keyArrays), "key", "_id", "_bucketname"); }, /** Adds values to a given key inside a bucket. */ - add(transaction, bucket, key, values) { - contract(arguments).params("array", "string", "string|number", "string|array|number").end(); + async add(transaction, bucket, key, values) { + contract(arguments).params("array", "string", "string", "string|array").end(); - if (key == "key") throw new Error("Key name 'key' is not allowed."); + if (key === "key") throw new Error("Key name 'key' is not allowed."); key = encodeText(key); - var collectionIndex = this.useSingle ? { _bucketname: 1, key: 1 } : { key: 1 }; - var updateParams = this.useSingle ? { _bucketname: bucket, key: key } : { key: key }; - var collName = this.useSingle ? aclCollectionName : bucket; - transaction.push((cb) => { + const collectionIndex = this.useSingle ? { _bucketname: 1, key: 1 } : { key: 1 }; + const updateParams = this.useSingle ? { _bucketname: bucket, key: key } : { key: key }; + const collName = this.useSingle ? aclCollectionName : bucket; + transaction.push(async () => { values = makeArray(values); let collection = this.db.collection(this.prefix + this.removeUnsupportedChar(collName)); - if (!collection) return cb(new Error(`Cannot find the collection ${collName}`)); + if (!collection) throw new Error(`Cannot find the collection ${collName}`); // build doc from array values - var doc = {}; + const doc = {}; for (const value of values) { doc[value] = true; } // update documents - collection.updateMany(updateParams, { $set: doc }, { safe: true, upsert: true }, (err) => { - if (err instanceof Error) return cb(err); - cb(undefined); - }); + await collection.updateMany(updateParams, { $set: doc }, { safe: true, upsert: true }); }); - transaction.push((cb) => { + transaction.push(async () => { let collection = this.db.collection(this.prefix + this.removeUnsupportedChar(collName)); - if (!collection) return cb(new Error(`Cannot find the collection ${collName}`)); + if (!collection) throw new Error(`Cannot find the collection ${collName}`); - collection.createIndex(collectionIndex, (err) => { - if (err instanceof Error) { - return cb(err); - } else { - cb(undefined); - } - }); + await collection.createIndex(collectionIndex); }); }, /** Delete the given key(s) at the bucket */ - del(transaction, bucket, keys) { + async del(transaction, bucket, keys) { contract(arguments).params("array", "string", "string|array").end(); keys = makeArray(keys); - var updateParams = this.useSingle ? { _bucketname: bucket, key: { $in: keys } } : { key: { $in: keys } }; - var collName = this.useSingle ? aclCollectionName : bucket; + const updateParams = this.useSingle ? { _bucketname: bucket, key: { $in: keys } } : { key: { $in: keys } }; + const collName = this.useSingle ? aclCollectionName : bucket; - transaction.push((cb) => { + transaction.push(async () => { let collection = this.db.collection(this.prefix + this.removeUnsupportedChar(collName)); - if (!collection) return cb(new Error(`Cannot find the collection ${collName}`)); + if (!collection) throw new Error(`Cannot find the collection ${collName}`); - collection.deleteMany(updateParams, { safe: true }, (err) => { - if (err instanceof Error) return cb(err); - cb(undefined); - }); + await collection.deleteMany(updateParams, { safe: true }); }); }, /** Removes values from a given key inside a bucket. */ - remove(transaction, bucket, key, values) { - contract(arguments).params("array", "string", "string|number", "string|array|number").end(); + async remove(transaction, bucket, key, values) { + contract(arguments).params("array", "string", "string", "string|array").end(); key = encodeText(key); - var updateParams = this.useSingle ? { _bucketname: bucket, key: key } : { key: key }; - var collName = this.useSingle ? aclCollectionName : bucket; + const updateParams = this.useSingle ? { _bucketname: bucket, key: key } : { key: key }; + const collName = this.useSingle ? aclCollectionName : bucket; values = makeArray(values); - transaction.push((cb) => { + transaction.push(async () => { let collection = this.db.collection(this.prefix + this.removeUnsupportedChar(collName)); - if (!collection) return cb(new Error(`Cannot find the collection ${collName}`)); + if (!collection) throw new Error(`Cannot find the collection ${collName}`); // build doc from array values - var doc = {}; + const doc = {}; for (const value of values) { doc[value] = true; } // update documents - collection.updateMany(updateParams, { $unset: doc }, { safe: true, upsert: true }, (err) => { - if (err instanceof Error) return cb(err); - cb(undefined); - }); + await collection.updateMany(updateParams, { $unset: doc }, { safe: true, upsert: true }); }); }, @@ -217,7 +183,7 @@ MongoDBBackend.prototype = { }; function encodeText(text) { - if (typeof text == "string" || text instanceof String) { + if (typeof text === "string" || text instanceof String) { text = encodeURIComponent(text); text = text.replace(/\./g, "%2E"); } @@ -225,7 +191,7 @@ function encodeText(text) { } function decodeText(text) { - if (typeof text == "string" || text instanceof String) { + if (typeof text === "string" || text instanceof String) { text = decodeURIComponent(text); } return text; diff --git a/lib/redis-backend.js b/lib/redis-backend.js index 7e7a99d..2f73881 100644 --- a/lib/redis-backend.js +++ b/lib/redis-backend.js @@ -1,11 +1,10 @@ -/** +/* Redis Backend. Implementation of the storage backend using Redis */ -"use strict"; -var contract = require("./contract"); +const contract = require("./contract"); function noop() {} @@ -15,139 +14,126 @@ function RedisBackend({ redis, prefix }) { } RedisBackend.prototype = { - close(cb) { - this.redis.end(); - cb(); + async close() { + await this.redis.quit(); }, /** Begins a transaction */ - begin() { - return this.redis.multi(); + async begin() { + return await this.redis.multi(); }, /** Ends a transaction (and executes it) */ - end(transaction, cb) { - contract(arguments).params("object", "function").end(); - transaction.exec(function () { - cb(); - }); + async end(transaction) { + await transaction.exec(); }, /** Cleans the whole storage. */ - clean(cb) { - contract(arguments).params("function").end(); - this.redis.keys(this.prefix + "*", (err, keys) => { - if (keys.length) { - this.redis.del(keys, function () { - cb(); - }); - } else { - cb(); - } - }); + async clean() { + const keys = await this.redis.keys(this.prefix + "*"); + if (keys && keys.length) this.redis.del(keys); }, /** Gets the contents at the bucket's key. */ - get(bucket, key, cb) { - contract(arguments).params("string", "string|number", "function").end(); + async get(bucket, key) { + contract(arguments).params("string", "string").end(); key = this.bucketKey(bucket, key); - this.redis.smembers(key, cb); + return await this.redis.sMembers(key); }, /** Gets an object mapping each passed bucket to the union of the specified keys inside that bucket. */ - unions(buckets, keys, cb) { - contract(arguments).params("array", "array", "function").end(); + async unions(buckets, keys) { + contract(arguments).params("array", "array").end(); - var redisKeys = {}; - var batch = this.redis.batch(); + const redisKeys = {}; + const multi = this.redis.multi(); for (const bucket of buckets) { redisKeys[bucket] = this.bucketKey(bucket, keys); - batch.sunion(redisKeys[bucket], noop); + multi.sUnion(redisKeys[bucket], noop); } - batch.exec(function (err, replies) { - if (!Array.isArray(replies)) { - return {}; - } + const replies = await multi.exec(); + if (!Array.isArray(replies)) { + return {}; + } - var result = {}; - for (let index = 0; index < replies.length; index++) { - let reply = replies[index]; - if (reply instanceof Error) cb(reply); + const result = {}; + for (let index = 0; index < replies.length; index++) { + let reply = replies[index]; + if (reply instanceof Error) throw reply; - result[buckets[index]] = reply; - } - cb(err, result); - }); + result[buckets[index]] = reply; + } + return result; }, /** Returns the union of the values in the given keys. */ - union(bucket, keys, cb) { - contract(arguments).params("string", "array", "function").end(); + async union(bucket, keys) { + contract(arguments).params("string", "array").end(); keys = this.bucketKey(bucket, keys); - this.redis.sunion(keys, cb); + return await this.redis.sUnion(keys); }, /** Adds values to a given key inside a bucket. */ - add(transaction, bucket, key, values) { - contract(arguments).params("object", "string", "string|number", "string|array|number").end(); + async add(transaction, bucket, key, values) { + contract(arguments).params("object", "string", "string", "string|array").end(); key = this.bucketKey(bucket, key); if (Array.isArray(values)) { for (const value of values) { - transaction.sadd(key, value); + transaction.sAdd(key, value); } } else { - transaction.sadd(key, values); + transaction.sAdd(key, values); } }, /** Delete the given key(s) at the bucket */ - del(transaction, bucket, keys) { + async del(transaction, bucket, keys) { contract(arguments).params("object", "string", "string|array").end(); keys = Array.isArray(keys) ? keys : [keys]; keys = keys.map((key) => this.bucketKey(bucket, key)); - transaction.del(keys); + await transaction.del(keys); }, /** Removes values from a given key inside a bucket. */ - remove(transaction, bucket, key, values) { - contract(arguments).params("object", "string", "string|number", "string|array|number").end(); + async remove(transaction, bucket, key, values) { + contract(arguments).params("object", "string", "string", "string|array").end(); key = this.bucketKey(bucket, key); if (Array.isArray(values)) { for (const value of values) { - transaction.srem(key, value); + transaction.sRem(key, value); } } else { - transaction.srem(key, values); + transaction.sRem(key, values); } }, diff --git a/package.json b/package.json index 5026241..e04b4cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "acl2", - "version": "3.0.1", + "version": "4.0.0", "description": "An Access Control List module based on memory, Redis, or MongoDB with Express middleware support", "keywords": [ "middleware", @@ -9,28 +9,26 @@ "node" ], "repository": "git://github.com/flash-oss/node_acl.git", - "author": "Manuel Astudillo ", + "author": "https://github.com/koresar", "homepage": "https://github.com/flash-oss/node_acl", "license": "MIT", "engines": { - "node": ">= 12.9" + "node": ">= 16" }, "main": "./index.js", "dependencies": { - "async": "^3.2.3", - "bluebird": "^3.0.2", "lodash": "^4.17.3" }, "devDependencies": { - "chai": "^4.2.0", - "eslint": "^7.7.0", - "mocha": "^8.1.1", - "mongodb": "3 - 4", + "eslint": "^8.56.0", + "mocha": "^10.2.0", + "mongodb": "5 - 6", "nyc": "^15.1.0", "prettier": "^2.1.0", - "redis": "^2.2.5" + "redis": "^4.6.12" }, "scripts": { + "lint": "eslint ./", "test": "npm run test_memory && npm run test_redis && npm run test_mongo && npm run test_mongo_single", "test_memory": "ACL_BACKEND=memory mocha", "test_redis": "ACL_BACKEND=redis mocha", @@ -44,7 +42,7 @@ ], "eslintConfig": { "parserOptions": { - "ecmaVersion": 2019 + "ecmaVersion": 2023 }, "env": { "es6": true, diff --git a/test/backendtests.js b/test/backendtests.js index 8f46e18..75a3cb2 100644 --- a/test/backendtests.js +++ b/test/backendtests.js @@ -1,78 +1,66 @@ -var chai = require("chai"); -var expect = chai.expect; +const assert = require("node:assert/strict"); -var testData = { +const testData = { key1: ["1", "2", "3"], key2: ["3", "2", "4"], key3: ["3", "4", "5"], }; -var buckets = ["bucket1", "bucket2"]; +const buckets = ["bucket1", "bucket2"]; describe("unions", function () { let backend; - before(function (done) { - require("./create-backend")() - .then((b) => { - backend = b; + before(async function () { + backend = await require("./create-backend")(); + if (!backend.unions) { + this.skip(); + } - if (!backend.unions) { - this.skip(); - } - - backend.clean(function () { - var transaction = backend.begin(); - for (const key of Object.keys(testData)) { - for (const bucket of buckets) { - backend.add(transaction, bucket, key, testData[key]); - } - } - backend.end(transaction, done); - }); - }) - .catch(done); + await backend.clean(); + const transaction = await backend.begin(); + for (const key of Object.keys(testData)) { + for (const bucket of buckets) { + await backend.add(transaction, bucket, key, testData[key]); + } + } + await backend.end(transaction); }); - after(function (done) { - if (!backend) return done(); - backend.clean((err) => { - if (err) return done(err); - backend.close(done); - }); + after(async function () { + if (!backend) return; + await backend.clean(); + await backend.close(); }); - it("should respond with an appropriate map", function (done) { - var expected = { + it("should respond with an appropriate map", async function () { + const expected = { bucket1: ["1", "2", "3", "4", "5"], bucket2: ["1", "2", "3", "4", "5"], }; - backend.unions(buckets, Object.keys(testData), function (err, result) { - expect(err).to.be.null; - expect(result).to.be.eql(expected); - done(); - }); + + const result = await backend.unions(buckets, Object.keys(testData)); + + assert.deepEqual(result, expected); }); - it("should get only the specified keys", function (done) { - var expected = { + it("should get only the specified keys", async function () { + const expected = { bucket1: ["1", "2", "3"], bucket2: ["1", "2", "3"], }; - backend.unions(buckets, ["key1"], function (err, result) { - expect(err).to.be.null; - expect(result).to.be.eql(expected); - done(); - }); + + const result = await backend.unions(buckets, ["key1"]); + + assert.deepEqual(result, expected); }); - it("should only get the specified buckets", function (done) { - var expected = { + it("should only get the specified buckets", async function () { + const expected = { bucket1: ["1", "2", "3"], }; - backend.unions(["bucket1"], ["key1"], function (err, result) { - expect(err).to.be.null; - expect(result).to.be.eql(expected); - done(); - }); + + const result = await backend.unions(["bucket1"], ["key1"]); + + assert.deepEqual(result, expected); }); }); diff --git a/test/create-backend.js b/test/create-backend.js index 72d9b2d..8e63c42 100644 --- a/test/create-backend.js +++ b/test/create-backend.js @@ -12,16 +12,14 @@ module.exports = async function createBackend(backendType) { password: null, }; - const redis = require("redis").createClient(options.port, options.host, { - no_ready_check: true, - }); + const redis = await require("redis").createClient(options.port, options.host).connect(); return new Acl.redisBackend({ redis }); } if (backendType === "mongo") { const { MongoClient } = await require("mongodb"); - const client = await MongoClient.connect("mongodb://localhost:27017/acl_test?useUnifiedTopology=true"); + const client = await MongoClient.connect("mongodb://localhost:27017/acl_test"); await client.db("acl_test").dropDatabase(); return new Acl.mongodbBackend({ client, prefix: "acl_" }); @@ -29,11 +27,11 @@ module.exports = async function createBackend(backendType) { if (backendType === "mongo_single") { const { MongoClient } = await require("mongodb"); - const client = await MongoClient.connect("mongodb://localhost:27017/acl_test?useUnifiedTopology=true"); + const client = await MongoClient.connect("mongodb://localhost:27017/acl_test"); await client.db("acl_test").dropDatabase(); return new Acl.mongodbBackend({ client, prefix: "acl_", useSingle: true }); } - throw new Error("Please assign ACL_BACKEND env var to one of: memory, redis, mongo, mongo_single"); + throw new Error("Please assign ACL_BACKEND env const to one of: memory, redis, mongo, mongo_single"); }; diff --git a/test/tests.js b/test/tests.js index fb5076e..2ff37b1 100644 --- a/test/tests.js +++ b/test/tests.js @@ -1,6 +1,6 @@ -var Acl = require("../"), - assert = require("chai").assert, - expect = require("chai").expect; +const Acl = require("../"); +const assert = require("node:assert/strict"); +const _ = require("lodash"); describe("acl", () => { let backend; @@ -9,28 +9,26 @@ describe("acl", () => { backend = await require("./create-backend")(); }); - after(function (done) { - if (!backend) return done(); - backend.clean((err) => { - if (err) return done(err); - backend.close(done); - }); + after(async function () { + if (!backend) return; + await backend.clean(); + await backend.close(); }); describe("constructor", function () { it("should use default `buckets` names", function () { - var acl = new Acl(backend); + const acl = new Acl(backend); - expect(acl.options.buckets.meta).to.equal("meta"); - expect(acl.options.buckets.parents).to.equal("parents"); - expect(acl.options.buckets.permissions).to.equal("permissions"); - expect(acl.options.buckets.resources).to.equal("resources"); - expect(acl.options.buckets.roles).to.equal("roles"); - expect(acl.options.buckets.users).to.equal("users"); + assert.equal(acl.options.buckets.meta, "meta"); + assert.equal(acl.options.buckets.parents, "parents"); + assert.equal(acl.options.buckets.permissions, "permissions"); + assert.equal(acl.options.buckets.resources, "resources"); + assert.equal(acl.options.buckets.roles, "roles"); + assert.equal(acl.options.buckets.users, "users"); }); it("should use given `buckets` names", function () { - var acl = new Acl(backend, null, { + const acl = new Acl(backend, null, { buckets: { meta: "Meta", parents: "Parents", @@ -41,1158 +39,822 @@ describe("acl", () => { }, }); - expect(acl.options.buckets.meta).to.equal("Meta"); - expect(acl.options.buckets.parents).to.equal("Parents"); - expect(acl.options.buckets.permissions).to.equal("Permissions"); - expect(acl.options.buckets.resources).to.equal("Resources"); - expect(acl.options.buckets.roles).to.equal("Roles"); - expect(acl.options.buckets.users).to.equal("Users"); + assert.equal(acl.options.buckets.meta, "Meta"); + assert.equal(acl.options.buckets.parents, "Parents"); + assert.equal(acl.options.buckets.permissions, "Permissions"); + assert.equal(acl.options.buckets.resources, "Resources"); + assert.equal(acl.options.buckets.roles, "Roles"); + assert.equal(acl.options.buckets.users, "Users"); }); }); describe("allow", function () { this.timeout(10000); - it("guest to view blogs", function (done) { - var acl = new Acl(backend); + it("guest to view blogs", async function () { + const acl = new Acl(backend); - acl.allow("guest", "blogs", "view", function (err) { - assert.ifError(err); - done(); - }); + await acl.allow("guest", "blogs", "view"); }); - it("guest to view forums", function (done) { - var acl = new Acl(backend); + it("guest to view forums", async function () { + const acl = new Acl(backend); - acl.allow("guest", "forums", "view", function (err) { - assert.ifError(err); - done(); - }); + await acl.allow("guest", "forums", "view"); }); - it("member to view/edit/delete blogs", function (done) { - var acl = new Acl(backend); + it("member to view/edit/delete blogs", async function () { + const acl = new Acl(backend); - acl.allow("member", "blogs", ["edit", "view", "delete"], function (err) { - assert.ifError(err); - done(); - }); + await acl.allow("member", "blogs", ["edit", "view", "delete"]); }); }); describe("Add user roles", function () { - it("joed => guest, jsmith => member, harry => admin, test@test.com => member", function (done) { - var acl = new Acl(backend); - - acl.addUserRoles("joed", "guest", function (err) { - assert.ifError(err); + it("joed => guest, jsmith => member, harry => admin, test@test.com => member", async function () { + const acl = new Acl(backend); - acl.addUserRoles("jsmith", "member", function (err) { - assert.ifError(err); - - acl.addUserRoles("harry", "admin", function (err) { - assert.ifError(err); - - acl.addUserRoles("test@test.com", "member", function (err) { - assert.ifError(err); - done(); - }); - }); - }); - }); + await acl.addUserRoles("joed", "guest"); + await acl.addUserRoles("jsmith", "member"); + await acl.addUserRoles("harry", "admin"); + await acl.addUserRoles("test@test.com", "member"); }); - it("0 => guest, 1 => member, 2 => admin", function (done) { - var acl = new Acl(backend); - - acl.addUserRoles(0, "guest", function (err) { - assert.ifError(err); - - acl.addUserRoles(1, "member", function (err) { - assert.ifError(err); + it("0 => guest, 1 => member, 2 => admin", async function () { + const acl = new Acl(backend); - acl.addUserRoles(2, "admin", function (err) { - assert.ifError(err); - done(); - }); - }); - }); + await acl.addUserRoles("0", "guest"); + await acl.addUserRoles("1", "member"); + await acl.addUserRoles("2", "admin"); }); }); describe("read User Roles", function () { - it("run userRoles function", function (done) { - var acl = new Acl(backend); - acl.addUserRoles("harry", "admin", function (err) { - if (err) return done(err); - - acl.userRoles("harry", function (err, roles) { - if (err) return done(err); - - assert.deepEqual(roles, ["admin"]); - acl.hasRole("harry", "admin", function (err, is_in_role) { - if (err) return done(err); - - assert.ok(is_in_role); - acl.hasRole("harry", "no role", function (err, is_in_role) { - if (err) return done(err); - - assert.notOk(is_in_role); - done(); - }); - }); - }); - }); + it("run userRoles function", async function () { + const acl = new Acl(backend); + await acl.addUserRoles("harry", "admin"); + + const roles = await acl.userRoles("harry"); + assert.deepEqual(roles, ["admin"]); + + let is_in_role = await acl.hasRole("harry", "admin"); + assert.ok(is_in_role); + + is_in_role = await acl.hasRole("harry", "no role"); + assert.ok(!is_in_role); }); }); describe("read Role Users", function () { - it("run roleUsers function", function (done) { - var acl = new Acl(backend); - acl.addUserRoles("harry", "admin", function (err) { - if (err) return done(err); - - acl.roleUsers("admin", function (err, users) { - if (err) return done(err); - assert.include(users, "harry"); - assert.isFalse("invalid User" in users); - done(); - }); - }); + it("run roleUsers function", async function () { + const acl = new Acl(backend); + await acl.addUserRoles("harry", "admin"); + + const users = await acl.roleUsers("admin"); + + assert(users.includes("harry")); + assert(!("invalid User" in users)); }); }); describe("allow", function () { - it("admin view/add/edit/delete users", function (done) { - var acl = new Acl(backend); + it("admin view/add/edit/delete users", async function () { + const acl = new Acl(backend); - acl.allow("admin", "users", ["add", "edit", "view", "delete"], function (err) { - assert.ifError(err); - done(); - }); + await acl.allow("admin", "users", ["add", "edit", "view", "delete"]); }); - it("foo view/edit blogs", function (done) { - var acl = new Acl(backend); + it("foo view/edit blogs", async function () { + const acl = new Acl(backend); - acl.allow("foo", "blogs", ["edit", "view"], function (err) { - assert.ifError(err); - done(); - }); + await acl.allow("foo", "blogs", ["edit", "view"]); }); - it("bar to view/delete blogs", function (done) { - var acl = new Acl(backend); + it("bar to view/delete blogs", async function () { + const acl = new Acl(backend); - acl.allow("bar", "blogs", ["view", "delete"], function (err) { - assert.ifError(err); - done(); - }); + await acl.allow("bar", "blogs", ["view", "delete"]); }); }); describe("add role parents", function () { - it("add them", function (done) { - var acl = new Acl(backend); + it("add them", async function () { + const acl = new Acl(backend); - acl.addRoleParents("baz", ["foo", "bar"], function (err) { - assert.ifError(err); - done(); - }); + await acl.addRoleParents("baz", ["foo", "bar"]); }); }); describe("add user roles", function () { - it("add them", function (done) { - var acl = new Acl(backend); + it("add them", async function () { + const acl = new Acl(backend); - acl.addUserRoles("james", "baz", function (err) { - assert.ifError(err); - done(); - }); + await acl.addUserRoles("james", "baz"); }); - it("add them (numeric userId)", function (done) { - var acl = new Acl(backend); - acl.addUserRoles(3, "baz", function (err) { - assert.ifError(err); - done(); - }); + it("add them (numeric userId)", async function () { + const acl = new Acl(backend); + + await acl.addUserRoles("3", "baz"); }); }); describe("allow admin to do anything", function () { - it("add them", function (done) { - var acl = new Acl(backend); + it("add them", async function () { + const acl = new Acl(backend); - acl.allow("admin", ["blogs", "forums"], "*", function (err) { - assert.ifError(err); - done(); - }); + await acl.allow("admin", ["blogs", "forums"], "*"); }); }); describe("Arguments in one array", function () { - it("give role fumanchu an array of resources and permissions", function (done) { - var acl = new Acl(backend); - - acl.allow( - [ - { - roles: "fumanchu", - allows: [ - { resources: "blogs", permissions: "get" }, - { - resources: ["forums", "news"], - permissions: ["get", "put", "delete"], - }, - { - resources: ["/path/file/file1.txt", "/path/file/file2.txt"], - permissions: ["get", "put", "delete"], - }, - ], - }, - ], - function (err) { - assert.ifError(err); - done(); - } - ); + it("give role fumanchu an array of resources and permissions", async function () { + const acl = new Acl(backend); + + await acl.allow([ + { + roles: "fumanchu", + allows: [ + { resources: "blogs", permissions: "get" }, + { + resources: ["forums", "news"], + permissions: ["get", "put", "delete"], + }, + { + resources: ["/path/file/file1.txt", "/path/file/file2.txt"], + permissions: ["get", "put", "delete"], + }, + ], + }, + ]); }); }); describe("Add fumanchu role to suzanne", function () { - it("do it", function (done) { - var acl = new Acl(backend); - acl.addUserRoles("suzanne", "fumanchu", function (err) { - assert.ifError(err); - done(); - }); + it("do it", async function () { + const acl = new Acl(backend); + await acl.addUserRoles("suzanne", "fumanchu"); }); - it("do it (numeric userId)", function (done) { - var acl = new Acl(backend); - acl.addUserRoles(4, "fumanchu", function (err) { - assert.ifError(err); - done(); - }); + + it("do it (numeric userId)", async function () { + const acl = new Acl(backend); + await acl.addUserRoles("4", "fumanchu"); }); }); describe("Allowance queries", function () { describe("isAllowed", function () { - it("Can joed view blogs?", function (done) { - var acl = new Acl(backend); - - acl.isAllowed("joed", "blogs", "view", function (err, allow) { - assert.ifError(err); - assert(allow); - done(); - }); + it("Can joed view blogs?", async function () { + const acl = new Acl(backend); + + assert(await acl.isAllowed("joed", "blogs", "view")); }); - it("Can userId=0 view blogs?", function (done) { - var acl = new Acl(backend); + it("Can userId=0 view blogs?", async function () { + const acl = new Acl(backend); - acl.isAllowed(0, "blogs", "view", function (err, allow) { - assert.ifError(err); - assert(allow); - done(); - }); + assert(await acl.isAllowed("0", "blogs", "view")); }); - it("Can joed view forums?", function (done) { - var acl = new Acl(backend); + it("Can joed view forums?", async function () { + const acl = new Acl(backend); - acl.isAllowed("joed", "forums", "view", function (err, allow) { - assert.ifError(err); - assert(allow); - done(); - }); + assert(await acl.isAllowed("joed", "forums", "view")); }); - it("Can userId=0 view forums?", function (done) { - var acl = new Acl(backend); + it("Can userId=0 view forums?", async function () { + const acl = new Acl(backend); - acl.isAllowed(0, "forums", "view", function (err, allow) { - assert.ifError(err); - assert(allow); - done(); - }); + assert(await acl.isAllowed("0", "forums", "view")); }); - it("Can joed edit forums?", function (done) { - var acl = new Acl(backend); + it("Can joed edit forums?", async function () { + const acl = new Acl(backend); - acl.isAllowed("joed", "forums", "edit", function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("joed", "forums", "edit"))); }); - it("Can userId=0 edit forums?", function (done) { - var acl = new Acl(backend); + it("Can userId=0 edit forums?", async function () { + const acl = new Acl(backend); - acl.isAllowed(0, "forums", "edit", function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("0", "forums", "edit"))); }); - it("Can jsmith edit forums?", function (done) { - var acl = new Acl(backend); + it("Can jsmith edit forums?", async function () { + const acl = new Acl(backend); - acl.isAllowed("jsmith", "forums", "edit", function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("jsmith", "forums", "edit"))); }); - it("Can jsmith edit forums?", function (done) { - var acl = new Acl(backend); + it("Can jsmith edit forums?", async function () { + const acl = new Acl(backend); - acl.isAllowed("jsmith", "forums", "edit", function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("jsmith", "forums", "edit"))); }); - it("Can jsmith edit blogs?", function (done) { - var acl = new Acl(backend); + it("Can jsmith edit blogs?", async function () { + const acl = new Acl(backend); - acl.isAllowed("jsmith", "blogs", "edit", function (err, allow) { - assert.ifError(err); - assert(allow); - done(); - }); + assert(await acl.isAllowed("jsmith", "blogs", "edit")); }); - it("Can test@test.com edit forums?", function (done) { - var acl = new Acl(backend); + it("Can test@test.com edit forums?", async function () { + const acl = new Acl(backend); - acl.isAllowed("test@test.com", "forums", "edit", function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("test@test.com", "forums", "edit"))); }); - it("Can test@test.com edit forums?", function (done) { - var acl = new Acl(backend); + it("Can test@test.com edit forums?", async function () { + const acl = new Acl(backend); - acl.isAllowed("test@test.com", "forums", "edit", function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("test@test.com", "forums", "edit"))); }); - it("Can test@test.com edit blogs?", function (done) { - var acl = new Acl(backend); + it("Can test@test.com edit blogs?", async function () { + const acl = new Acl(backend); - acl.isAllowed("test@test.com", "blogs", "edit", function (err, allow) { - assert.ifError(err); - assert(allow); - done(); - }); + assert(await acl.isAllowed("test@test.com", "blogs", "edit")); }); - it("Can userId=1 edit blogs?", function (done) { - var acl = new Acl(backend); + it("Can userId=1 edit blogs?", async function () { + const acl = new Acl(backend); - acl.isAllowed(1, "blogs", "edit", function (err, allow) { - assert.ifError(err); - assert(allow); - done(); - }); + assert(await acl.isAllowed("1", "blogs", "edit")); }); - it("Can jsmith edit, delete and clone blogs?", function (done) { - var acl = new Acl(backend); + it("Can jsmith edit, delete and clone blogs?", async function () { + const acl = new Acl(backend); - acl.isAllowed("jsmith", "blogs", ["edit", "view", "clone"], function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("jsmith", "blogs", ["edit", "view", "clone"]))); }); - it("Can test@test.com edit, delete and clone blogs?", function (done) { - var acl = new Acl(backend); + it("Can test@test.com edit, delete and clone blogs?", async function () { + const acl = new Acl(backend); - acl.isAllowed("test@test.com", "blogs", ["edit", "view", "clone"], function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("test@test.com", "blogs", ["edit", "view", "clone"]))); }); - it("Can userId=1 edit, delete and clone blogs?", function (done) { - var acl = new Acl(backend); + it("Can userId=1 edit, delete and clone blogs?", async function () { + const acl = new Acl(backend); - acl.isAllowed(1, "blogs", ["edit", "view", "clone"], function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("1", "blogs", ["edit", "view", "clone"]))); }); - it("Can jsmith edit, clone blogs?", function (done) { - var acl = new Acl(backend); + it("Can jsmith edit, clone blogs?", async function () { + const acl = new Acl(backend); - acl.isAllowed("jsmith", "blogs", ["edit", "clone"], function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("jsmith", "blogs", ["edit", "clone"]))); }); - it("Can test@test.com edit, clone blogs?", function (done) { - var acl = new Acl(backend); + it("Can test@test.com edit, clone blogs?", async function () { + const acl = new Acl(backend); - acl.isAllowed("test@test.com", "blogs", ["edit", "clone"], function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("test@test.com", "blogs", ["edit", "clone"]))); }); - it("Can userId=1 edit, delete blogs?", function (done) { - var acl = new Acl(backend); + it("Can userId=1 edit, delete blogs?", async function () { + const acl = new Acl(backend); - acl.isAllowed(1, "blogs", ["edit", "clone"], function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("1", "blogs", ["edit", "clone"]))); }); - it("Can james add blogs?", function (done) { - var acl = new Acl(backend); + it("Can james add blogs?", async function () { + const acl = new Acl(backend); - acl.isAllowed("james", "blogs", "add", function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("james", "blogs", "add"))); }); - it("Can userId=3 add blogs?", function (done) { - var acl = new Acl(backend); + it("Can userId=3 add blogs?", async function () { + const acl = new Acl(backend); - acl.isAllowed(3, "blogs", "add", function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("3", "blogs", "add"))); }); - it("Can suzanne add blogs?", function (done) { - var acl = new Acl(backend); + it("Can suzanne add blogs?", async function () { + const acl = new Acl(backend); - acl.isAllowed("suzanne", "blogs", "add", function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("suzanne", "blogs", "add"))); }); - it("Can userId=4 add blogs?", function (done) { - var acl = new Acl(backend); + it("Can userId=4 add blogs?", async function () { + const acl = new Acl(backend); - acl.isAllowed(4, "blogs", "add", function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("4", "blogs", "add"))); }); - it("Can suzanne get blogs?", function (done) { - var acl = new Acl(backend); + it("Can suzanne get blogs?", async function () { + const acl = new Acl(backend); - acl.isAllowed("suzanne", "blogs", "get", function (err, allow) { - assert.ifError(err); - assert(allow); - done(); - }); + assert(await acl.isAllowed("suzanne", "blogs", "get")); }); - it("Can userId=4 get blogs?", function (done) { - var acl = new Acl(backend); + it("Can userId=4 get blogs?", async function () { + const acl = new Acl(backend); - acl.isAllowed(4, "blogs", "get", function (err, allow) { - assert.ifError(err); - assert(allow); - done(); - }); + assert(await acl.isAllowed("4", "blogs", "get")); }); - it("Can suzanne delete and put news?", function (done) { - var acl = new Acl(backend); + it("Can suzanne delete and put news?", async function () { + const acl = new Acl(backend); - acl.isAllowed("suzanne", "news", ["put", "delete"], function (err, allow) { - assert.ifError(err); - assert(allow); - done(); - }); + assert(await acl.isAllowed("suzanne", "news", ["put", "delete"])); }); - it("Can userId=4 delete and put news?", function (done) { - var acl = new Acl(backend); + it("Can userId=4 delete and put news?", async function () { + const acl = new Acl(backend); - acl.isAllowed(4, "news", ["put", "delete"], function (err, allow) { - assert.ifError(err); - assert(allow); - done(); - }); + assert(await acl.isAllowed("4", "news", ["put", "delete"])); }); - it("Can suzanne delete and put forums?", function (done) { - var acl = new Acl(backend); + it("Can suzanne delete and put forums?", async function () { + const acl = new Acl(backend); - acl.isAllowed("suzanne", "forums", ["put", "delete"], function (err, allow) { - assert.ifError(err); - assert(allow); - done(); - }); + assert(await acl.isAllowed("suzanne", "forums", ["put", "delete"])); }); - it("Can userId=4 delete and put forums?", function (done) { - var acl = new Acl(backend); + it("Can userId=4 delete and put forums?", async function () { + const acl = new Acl(backend); - acl.isAllowed(4, "forums", ["put", "delete"], function (err, allow) { - assert.ifError(err); - assert(allow); - done(); - }); + assert(await acl.isAllowed("4", "forums", ["put", "delete"])); }); - it("Can nobody view news?", function (done) { - var acl = new Acl(backend); + it("Can nobody view news?", async function () { + const acl = new Acl(backend); - acl.isAllowed("nobody", "blogs", "view", function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("nobody", "blogs", "view"))); }); - it("Can nobody view nothing?", function (done) { - var acl = new Acl(backend); + it("Can nobody view nothing?", async function () { + const acl = new Acl(backend); - acl.isAllowed("nobody", "nothing", "view", function (err, allow) { - assert.ifError(err); - assert(!allow); - done(); - }); + assert(!(await acl.isAllowed("nobody", "nothing", "view"))); }); }); describe("allowedPermissions", function () { - it("What permissions has james over blogs and forums?", function (done) { - var acl = new Acl(backend); - acl.allowedPermissions("james", ["blogs", "forums"], function (err, permissions) { - assert.ifError(err); + it("What permissions has james over blogs and forums?", async function () { + const acl = new Acl(backend); - assert.property(permissions, "blogs"); - assert.property(permissions, "forums"); + const permissions = await acl.allowedPermissions("james", ["blogs", "forums"]); - assert.include(permissions.blogs, "edit"); - assert.include(permissions.blogs, "delete"); - assert.include(permissions.blogs, "view"); + assert(permissions.blogs); + assert(permissions.forums); - assert(permissions.forums.length === 0); + assert(permissions.blogs.includes("edit")); + assert(permissions.blogs.includes("delete")); + assert(permissions.blogs.includes("view")); - done(); - }); + assert(permissions.forums.length === 0); }); - it("What permissions has userId=3 over blogs and forums?", function (done) { - var acl = new Acl(backend); - acl.allowedPermissions(3, ["blogs", "forums"], function (err, permissions) { - assert.ifError(err); - assert.property(permissions, "blogs"); - assert.property(permissions, "forums"); + it("What permissions has userId=3 over blogs and forums?", async function () { + const acl = new Acl(backend); + + const permissions = await acl.allowedPermissions("3", ["blogs", "forums"]); - assert.include(permissions.blogs, "edit"); - assert.include(permissions.blogs, "delete"); - assert.include(permissions.blogs, "view"); + assert(permissions.blogs); + assert(permissions.forums); - assert(permissions.forums.length === 0); + assert(permissions.blogs.includes("edit")); + assert(permissions.blogs.includes("delete")); + assert(permissions.blogs.includes("view")); - done(); - }); + assert(permissions.forums.length === 0); }); - it("What permissions has nonsenseUser over blogs and forums?", function (done) { - var acl = new Acl(backend); - acl.allowedPermissions("nonsense", ["blogs", "forums"], function (err, permissions) { - assert.ifError(err); - assert(permissions.forums.length === 0); - assert(permissions.blogs.length === 0); + it("What permissions has nonsenseUser over blogs and forums?", async function () { + const acl = new Acl(backend); + + const permissions = await acl.allowedPermissions("nonsense", ["blogs", "forums"]); - done(); - }); + assert(permissions.forums.length === 0); + assert(permissions.blogs.length === 0); }); }); }); describe("whatResources queries", function () { - it('What resources have "bar" some rights on?', function (done) { - var acl = new Acl(backend); - - acl.whatResources("bar", function (err, resources) { - assert.isNull(err); - assert.include(resources.blogs, "view"); - assert.include(resources.blogs, "delete"); - done(); - }); + it('What resources have "bar" some rights on?', async function () { + const acl = new Acl(backend); + + const resources = await acl.whatResources("bar"); + + assert(resources.blogs.includes("view")); + assert(resources.blogs.includes("delete")); }); - it('What resources have "bar" view rights on?', function (done) { - var acl = new Acl(backend); + it('What resources have "bar" view rights on?', async function () { + const acl = new Acl(backend); - acl.whatResources("bar", "view", function (err, resources) { - assert.isNull(err); - assert.include(resources, "blogs"); - done(); - }); + const resources = await acl.whatResources("bar", "view"); + + assert(resources.includes("blogs")); }); - it('What resources have "fumanchu" some rights on?', function (done) { - var acl = new Acl(backend); - - acl.whatResources("fumanchu", function (err, resources) { - assert.isNull(err); - assert.include(resources.blogs, "get"); - assert.include(resources.forums, "delete"); - assert.include(resources.forums, "get"); - assert.include(resources.forums, "put"); - assert.include(resources.news, "delete"); - assert.include(resources.news, "get"); - assert.include(resources.news, "put"); - assert.include(resources["/path/file/file1.txt"], "delete"); - assert.include(resources["/path/file/file1.txt"], "get"); - assert.include(resources["/path/file/file1.txt"], "put"); - assert.include(resources["/path/file/file2.txt"], "delete"); - assert.include(resources["/path/file/file2.txt"], "get"); - assert.include(resources["/path/file/file2.txt"], "put"); - done(); - }); + it('What resources have "fumanchu" some rights on?', async function () { + const acl = new Acl(backend); + + const resources = await acl.whatResources("fumanchu"); + + assert(resources.blogs.includes("get")); + assert(resources.forums.includes("delete")); + assert(resources.forums.includes("get")); + assert(resources.forums.includes("put")); + assert(resources.news.includes("delete")); + assert(resources.news.includes("get")); + assert(resources.news.includes("put")); + assert(resources["/path/file/file1.txt"].includes("delete")); + assert(resources["/path/file/file1.txt"].includes("get")); + assert(resources["/path/file/file1.txt"].includes("put")); + assert(resources["/path/file/file2.txt"].includes("delete")); + assert(resources["/path/file/file2.txt"].includes("get")); + assert(resources["/path/file/file2.txt"].includes("put")); }); - it('What resources have "baz" some rights on?', function (done) { - var acl = new Acl(backend); + it('What resources have "baz" some rights on?', async function () { + const acl = new Acl(backend); - acl.whatResources("baz", function (err, resources) { - assert.isNull(err); - assert.include(resources.blogs, "view"); - assert.include(resources.blogs, "delete"); - assert.include(resources.blogs, "edit"); - done(); - }); + const resources = await acl.whatResources("baz"); + + assert(resources.blogs.includes("view")); + assert(resources.blogs.includes("delete")); + assert(resources.blogs.includes("edit")); }); }); describe("removeAllow", function () { - it("Remove get permissions from resources blogs and forums from role fumanchu", function (done) { - var acl = new Acl(backend); - acl.removeAllow("fumanchu", ["blogs", "forums"], "get", function (err) { - assert.ifError(err); - done(); - }); + it("Remove get permissions from resources blogs and forums from role fumanchu", async function () { + const acl = new Acl(backend); + + await acl.removeAllow("fumanchu", ["blogs", "forums"], "get"); }); - it("Remove delete and put permissions from resource news from role fumanchu", function (done) { - var acl = new Acl(backend); - acl.removeAllow("fumanchu", "news", "delete", function (err) { - assert.ifError(err); - done(); - }); + it("Remove delete and put permissions from resource news from role fumanchu", async function () { + const acl = new Acl(backend); + + await acl.removeAllow("fumanchu", "news", "delete"); }); - it("Remove view permissions from resource blogs from role bar", function (done) { - var acl = new Acl(backend); - acl.removeAllow("bar", "blogs", "view", function (err) { - assert.ifError(err); - done(); - }); + it("Remove view permissions from resource blogs from role bar", async function () { + const acl = new Acl(backend); + + await acl.removeAllow("bar", "blogs", "view"); }); }); describe("See if permissions were removed", function () { - it('What resources have "fumanchu" some rights on after removed some of them?', function (done) { - var acl = new Acl(backend); - acl.whatResources("fumanchu", function (err, resources) { - assert.isNull(err); - - assert.isFalse("blogs" in resources); - assert.property(resources, "news"); - assert.include(resources.news, "get"); - assert.include(resources.news, "put"); - assert.isFalse("delete" in resources.news); - - assert.property(resources, "forums"); - assert.include(resources.forums, "delete"); - assert.include(resources.forums, "put"); - done(); - }); + it('What resources have "fumanchu" some rights on after removed some of them?', async function () { + const acl = new Acl(backend); + + const resources = await acl.whatResources("fumanchu"); + + assert(!("blogs" in resources)); + assert(resources.news); + assert(resources.news.includes("get")); + assert(resources.news.includes("put")); + assert(!("delete" in resources.news)); + + assert(resources.forums); + assert(resources.forums.includes("delete")); + assert(resources.forums.includes("put")); }); }); describe("removeRole", function () { - it("Remove role fumanchu", function (done) { - var acl = new Acl(backend); - acl.removeRole("fumanchu", function (err) { - assert.ifError(err); - done(); - }); + it("Remove role fumanchu", async function () { + const acl = new Acl(backend); + + await acl.removeRole("fumanchu"); }); - it("Remove role member", function (done) { - var acl = new Acl(backend); - acl.removeRole("member", function (err) { - assert.ifError(err); - done(); - }); + it("Remove role member", async function () { + const acl = new Acl(backend); + + await acl.removeRole("member"); }); - it("Remove role foo", function (done) { - var acl = new Acl(backend); - acl.removeRole("foo", function (err) { - assert.ifError(err); - done(); - }); + it("Remove role foo", async function () { + const acl = new Acl(backend); + + await acl.removeRole("foo"); }); }); describe("Was role removed?", function () { - it('What resources have "fumanchu" some rights on after removed?', function (done) { - var acl = new Acl(backend); - acl.whatResources("fumanchu", function (err, resources) { - assert.ifError(err); - assert(Object.keys(resources).length === 0); - done(); - }); + it('What resources have "fumanchu" some rights on after removed?', async function () { + const acl = new Acl(backend); + + const resources = await acl.whatResources("fumanchu"); + + assert(Object.keys(resources).length === 0); }); - it('What resources have "member" some rights on after removed?', function (done) { - var acl = new Acl(backend); - acl.whatResources("member", function (err, resources) { - assert.ifError(err); - assert(Object.keys(resources).length === 0); - done(); - }); + it('What resources have "member" some rights on after removed?', async function () { + const acl = new Acl(backend); + + const resources = await acl.whatResources("member"); + + assert(Object.keys(resources).length === 0); }); describe("allowed permissions", function () { - it("What permissions has jsmith over blogs and forums?", function (done) { - var acl = new Acl(backend); - acl.allowedPermissions("jsmith", ["blogs", "forums"], function (err, permissions) { - assert.ifError(err); - assert(permissions.blogs.length === 0); - assert(permissions.forums.length === 0); - done(); - }); + it("What permissions has jsmith over blogs and forums?", async function () { + const acl = new Acl(backend); + + const permissions = await acl.allowedPermissions("jsmith", ["blogs", "forums"]); + + assert(permissions.blogs.length === 0); + assert(permissions.forums.length === 0); }); - it("What permissions has test@test.com over blogs and forums?", function (done) { - var acl = new Acl(backend); - acl.allowedPermissions("test@test.com", ["blogs", "forums"], function (err, permissions) { - assert.ifError(err); - assert(permissions.blogs.length === 0); - assert(permissions.forums.length === 0); - done(); - }); + it("What permissions has test@test.com over blogs and forums?", async function () { + const acl = new Acl(backend); + + const permissions = await acl.allowedPermissions("test@test.com", ["blogs", "forums"]); + + assert(permissions.blogs.length === 0); + assert(permissions.forums.length === 0); }); - it("What permissions has james over blogs?", function (done) { - var acl = new Acl(backend); - acl.allowedPermissions("james", "blogs", function (err, permissions) { - assert.ifError(err); - assert.property(permissions, "blogs"); - assert.include(permissions.blogs, "delete"); - done(); - }); + it("What permissions has james over blogs?", async function () { + const acl = new Acl(backend); + + const permissions = await acl.allowedPermissions("james", "blogs"); + + assert(permissions.blogs); + assert(permissions.blogs.includes("delete")); }); }); }); describe("RoleParentRemoval", function () { - before(function (done) { - var acl = new Acl(backend); - acl.allow("parent1", "x", "read1") - .then(function () { - return acl.allow("parent2", "x", "read2"); - }) - .then(function () { - return acl.allow("parent3", "x", "read3"); - }) - .then(function () { - return acl.allow("parent4", "x", "read4"); - }) - .then(function () { - return acl.allow("parent5", "x", "read5"); - }) - .then(function () { - return acl.addRoleParents("child", ["parent1", "parent2", "parent3", "parent4", "parent5"]); - }) - .done(done, done); - }); - - var acl; + before(async function () { + const acl = new Acl(backend); + + await acl.allow("parent1", "x", "read1"); + await acl.allow("parent2", "x", "read2"); + await acl.allow("parent3", "x", "read3"); + await acl.allow("parent4", "x", "read4"); + await acl.allow("parent5", "x", "read5"); + + await acl.addRoleParents("child", ["parent1", "parent2", "parent3", "parent4", "parent5"]); + }); + + let acl; beforeEach(function () { acl = new Acl(backend); }); - it("Environment check", function (done) { - acl.whatResources("child") - .then(function (resources) { - assert.lengthOf(resources.x, 5); - assert.include(resources.x, "read1"); - assert.include(resources.x, "read2"); - assert.include(resources.x, "read3"); - assert.include(resources.x, "read4"); - assert.include(resources.x, "read5"); - }) - .done(done, done); + it("Environment check", async function () { + const resources = await acl.whatResources("child"); + + assert.equal(resources.x.length, 5); + assert(resources.x.includes("read1")); + assert(resources.x.includes("read2")); + assert(resources.x.includes("read3")); + assert(resources.x.includes("read4")); + assert(resources.x.includes("read5")); }); - it("Operation uses a callback when removing a specific parent role", function (done) { - acl.removeRoleParents("child", "parentX", function (err) { - assert.ifError(err); - done(); - }); + it("Operation removing a specific parent role", async function () { + await acl.removeRoleParents("child", "parentX"); }); - it("Operation uses a callback when removing multiple specific parent roles", function (done) { - acl.removeRoleParents("child", ["parentX", "parentY"], function (err) { - assert.ifError(err); - done(); - }); + it("Operation removing multiple specific parent roles", async function () { + await acl.removeRoleParents("child", ["parentX", "parentY"]); }); - it('Remove parent role "parentX" from role "child"', function (done) { - acl.removeRoleParents("child", "parentX") - .then(function () { - return acl.whatResources("child"); - }) - .then(function (resources) { - assert.lengthOf(resources.x, 5); - assert.include(resources.x, "read1"); - assert.include(resources.x, "read2"); - assert.include(resources.x, "read3"); - assert.include(resources.x, "read4"); - assert.include(resources.x, "read5"); - }) - .done(done, done); - }); - - it('Remove parent role "parent1" from role "child"', function (done) { - acl.removeRoleParents("child", "parent1") - .then(function () { - return acl.whatResources("child"); - }) - .then(function (resources) { - assert.lengthOf(resources.x, 4); - assert.include(resources.x, "read2"); - assert.include(resources.x, "read3"); - assert.include(resources.x, "read4"); - assert.include(resources.x, "read5"); - }) - .done(done, done); - }); - - it('Remove parent roles "parent2" & "parent3" from role "child"', function (done) { - acl.removeRoleParents("child", ["parent2", "parent3"]) - .then(function () { - return acl.whatResources("child"); - }) - .then(function (resources) { - assert.lengthOf(resources.x, 2); - assert.include(resources.x, "read4"); - assert.include(resources.x, "read5"); - }) - .done(done, done); - }); - - it('Remove all parent roles from role "child"', function (done) { - acl.removeRoleParents("child") - .then(function () { - return acl.whatResources("child"); - }) - .then(function (resources) { - assert.notProperty(resources, "x"); - }) - .done(done, done); - }); - - it('Remove all parent roles from role "child" with no parents', function (done) { - acl.removeRoleParents("child") - .then(function () { - return acl.whatResources("child"); - }) - .then(function (resources) { - assert.notProperty(resources, "x"); - }) - .done(done, done); - }); - - it('Remove parent role "parent1" from role "child" with no parents', function (done) { - acl.removeRoleParents("child", "parent1") - .then(function () { - return acl.whatResources("child"); - }) - .then(function (resources) { - assert.notProperty(resources, "x"); - }) - .done(done, done); - }); - - it("Operation uses a callback when removing all parent roles", function (done) { - acl.removeRoleParents("child", function (err) { - assert.ifError(err); - done(); - }); + it('Remove parent role "parentX" from role "child"', async function () { + await acl.removeRoleParents("child", "parentX"); + + let resources = await acl.whatResources("child"); + + assert.equal(resources.x.length, 5); + assert(resources.x.includes("read1")); + assert(resources.x.includes("read2")); + assert(resources.x.includes("read3")); + assert(resources.x.includes("read4")); + assert(resources.x.includes("read5")); + }); + + it('Remove parent role "parent1" from role "child"', async function () { + await acl.removeRoleParents("child", "parent1"); + + let resources = await acl.whatResources("child"); + + assert.equal(resources.x.length, 4); + assert(resources.x.includes("read2")); + assert(resources.x.includes("read3")); + assert(resources.x.includes("read4")); + assert(resources.x.includes("read5")); + }); + + it('Remove parent roles "parent2" & "parent3" from role "child"', async function () { + await acl.removeRoleParents("child", ["parent2", "parent3"]); + + let resources = await acl.whatResources("child"); + + assert.equal(resources.x.length, 2); + assert(resources.x.includes("read4")); + assert(resources.x.includes("read5")); + }); + + it('Remove all parent roles from role "child"', async function () { + await acl.removeRoleParents("child"); + + let resources = await acl.whatResources("child"); + + assert(!resources.x); + }); + + it('Remove all parent roles from role "child" with no parents', async function () { + await acl.removeRoleParents("child"); + + let resources = await acl.whatResources("child"); + + assert(!resources.x); + }); + + it('Remove parent role "parent1" from role "child" with no parents', async function () { + await acl.removeRoleParents("child", "parent1"); + + let resources = await acl.whatResources("child"); + + assert(!resources.x); + }); + + it("Operation removing all parent roles", async function () { + await acl.removeRoleParents("child"); }); }); describe("removeResource", function () { - it("Remove resource blogs", function (done) { - var acl = new Acl(backend); - acl.removeResource("blogs", function (err) { - assert.ifError(err); - done(); - }); + it("Remove resource blogs", async function () { + const acl = new Acl(backend); + + await acl.removeResource("blogs"); }); - it("Remove resource users", function (done) { - var acl = new Acl(backend); - acl.removeResource("users", function (err) { - assert.ifError(err); - done(); - }); + it("Remove resource users", async function () { + const acl = new Acl(backend); + + await acl.removeResource("users"); }); }); describe("allowedPermissions", function () { - it("What permissions has james over blogs?", function (done) { - var acl = new Acl(backend); - acl.allowedPermissions("james", "blogs", function (err, permissions) { - assert.isNull(err); - assert.property(permissions, "blogs"); - assert(permissions.blogs.length === 0); - done(); - }); + it("What permissions has james over blogs?", async function () { + const acl = new Acl(backend); + + const permissions = await acl.allowedPermissions("james", "blogs"); + + assert(permissions.blogs); + assert(permissions.blogs.length === 0); }); - it("What permissions has userId=4 over blogs?", function (done) { - var acl = new Acl(backend); - acl.allowedPermissions(4, "blogs").then(function (permissions) { - assert.property(permissions, "blogs"); - assert(permissions.blogs.length === 0); - done(); - }, done); + + it("What permissions has userId=4 over blogs?", async function () { + const acl = new Acl(backend); + + const permissions = await acl.allowedPermissions("4", "blogs"); + + assert(permissions.blogs); + assert(permissions.blogs.length === 0); }); }); describe("whatResources", function () { - it('What resources have "baz" some rights on after removed blogs?', function (done) { - var acl = new Acl(backend); - acl.whatResources("baz", function (err, resources) { - assert.ifError(err); - assert.isObject(resources); - assert(Object.keys(resources).length === 0); - - done(); - }); + it('What resources have "baz" some rights on after removed blogs?', async function () { + const acl = new Acl(backend); + + const resources = await acl.whatResources("baz"); + + assert(_.isPlainObject(resources)); + assert(Object.keys(resources).length === 0); }); - it('What resources have "admin" some rights on after removed users resource?', function (done) { - var acl = new Acl(backend); - acl.whatResources("admin", function (err, resources) { - assert.ifError(err); - assert.isFalse("users" in resources); - assert.isFalse("blogs" in resources); + it('What resources have "admin" some rights on after removed users resource?', async function () { + const acl = new Acl(backend); - done(); - }); + const resources = await acl.whatResources("admin"); + + assert(!("users" in resources)); + assert(!("blogs" in resources)); }); }); describe("Remove user roles", function () { - it("Remove role guest from joed", function (done) { - var acl = new Acl(backend); - acl.removeUserRoles("joed", "guest", function (err) { - assert.ifError(err); - done(); - }); + it("Remove role guest from joed", async function () { + const acl = new Acl(backend); + + await acl.removeUserRoles("joed", "guest"); }); - it("Remove role guest from userId=0", function (done) { - var acl = new Acl(backend); - acl.removeUserRoles(0, "guest", function (err) { - assert.ifError(err); - done(); - }); + it("Remove role guest from userId=0", async function () { + const acl = new Acl(backend); + + await acl.removeUserRoles("0", "guest"); }); - it("Remove role admin from harry", function (done) { - var acl = new Acl(backend); - acl.removeUserRoles("harry", "admin", function (err) { - assert.ifError(err); - done(); - }); + it("Remove role admin from harry", async function () { + const acl = new Acl(backend); + + await acl.removeUserRoles("harry", "admin"); }); - it("Remove role admin from userId=2", function (done) { - var acl = new Acl(backend); - acl.removeUserRoles(2, "admin", function (err) { - assert.ifError(err); - done(); - }); + it("Remove role admin from userId=2", async function () { + const acl = new Acl(backend); + + await acl.removeUserRoles("2", "admin"); }); }); describe("Were roles removed?", function () { - it("What permissions has harry over forums and blogs?", function (done) { - var acl = new Acl(backend); - acl.allowedPermissions("harry", ["forums", "blogs"], function (err, permissions) { - assert.ifError(err); - assert.isObject(permissions); - assert(permissions.forums.length === 0); - done(); - }); - it("What permissions has userId=2 over forums and blogs?", function (done) { - var acl = new Acl(backend); - acl.allowedPermissions(2, ["forums", "blogs"], function (err, permissions) { - assert.ifError(err); - assert.isObject(permissions); - assert(permissions.forums.length === 0); - done(); - }); - }); + it("What permissions has harry over forums and blogs?", async function () { + const acl = new Acl(backend); + + const permissions = await acl.allowedPermissions("harry", ["forums", "blogs"]); + + assert(_.isPlainObject(permissions)); + assert(permissions.forums.length === 0); + }); + + it("What permissions has userId=2 over forums and blogs?", async function () { + const acl = new Acl(backend); + + const permissions = await acl.allowedPermissions("2", ["forums", "blogs"]); + + assert(_.isPlainObject(permissions)); + assert(permissions.forums.length === 0); }); }); describe("Github issue #55: removeAllow is removing all permissions.", function () { - it("Add roles/resources/permissions", function () { - var acl = new Acl(backend); - - return acl - .addUserRoles("jannette", "member") - .then(function () { - return acl.allow("member", "blogs", ["view", "update"]); - }) - .then(function () { - return acl.isAllowed("jannette", "blogs", "view", function (err, allowed) { - expect(allowed).to.be.eql(true); - }); - }) - .then(function () { - return acl.removeAllow("member", "blogs", "update"); - }) - .then(function () { - return acl.isAllowed("jannette", "blogs", "view", function (err, allowed) { - expect(allowed).to.be.eql(true); - }); - }) - .then(function () { - return acl.isAllowed("jannette", "blogs", "update", function (err, allowed) { - expect(allowed).to.be.eql(false); - }); - }) - .then(function () { - return acl.removeAllow("member", "blogs", "view"); - }) - .then(function () { - return acl.isAllowed("jannette", "blogs", "view", function (err, allowed) { - expect(allowed).to.be.eql(false); - }); - }); + it("Add roles/resources/permissions", async function () { + const acl = new Acl(backend); + + await acl.addUserRoles("jannette", "member"); + await acl.allow("member", "blogs", ["view", "update"]); + assert(await acl.isAllowed("jannette", "blogs", "view")); + + await acl.removeAllow("member", "blogs", "update"); + assert(await acl.isAllowed("jannette", "blogs", "view")); + + assert(!(await acl.isAllowed("jannette", "blogs", "update"))); + + await acl.removeAllow("member", "blogs", "view"); + assert(!(await acl.isAllowed("jannette", "blogs", "view"))); }); }); describe('Github issue #32: Removing a role removes the entire "allows" document.', function () { - it("Add roles/resources/permissions", function (done) { - var acl = new Acl(backend); - - acl.allow(["role1", "role2", "role3"], ["res1", "res2", "res3"], ["perm1", "perm2", "perm3"], function ( - err - ) { - assert.ifError(err); - done(); - }); + it("Add roles/resources/permissions", async function () { + const acl = new Acl(backend); + + await acl.allow(["role1", "role2", "role3"], ["res1", "res2", "res3"], ["perm1", "perm2", "perm3"]); }); - it("Add user roles and parent roles", function (done) { - var acl = new Acl(backend); + it("Add user roles and parent roles", async function () { + const acl = new Acl(backend); - acl.addUserRoles("user1", "role1", function (err) { - assert.ifError(err); + await acl.addUserRoles("user1", "role1"); - acl.addRoleParents("role1", "parentRole1", function (err) { - assert.ifError(err); - done(); - }); - }); + await acl.addRoleParents("role1", "parentRole1"); }); - it("Add user roles and parent roles", function (done) { - var acl = new Acl(backend); + it("Add user roles and parent roles", async function () { + const acl = new Acl(backend); - acl.addUserRoles(1, "role1", function (err) { - assert.ifError(err); + await acl.addUserRoles("1", "role1"); - acl.addRoleParents("role1", "parentRole1", function (err) { - assert.ifError(err); - done(); - }); - }); + await acl.addRoleParents("role1", "parentRole1"); }); - it("Verify that roles have permissions as assigned", function (done) { - var acl = new Acl(backend); + it("Verify that roles have permissions as assigned", async function () { + const acl = new Acl(backend); - acl.whatResources("role1", function (err, res) { - assert.ifError(err); - assert.deepEqual(res.res1.sort(), ["perm1", "perm2", "perm3"]); + let res = await acl.whatResources("role1"); + assert.deepEqual(res.res1.sort(), ["perm1", "perm2", "perm3"]); - acl.whatResources("role2", function (err, res) { - assert.ifError(err); - assert.deepEqual(res.res1.sort(), ["perm1", "perm2", "perm3"]); - done(); - }); - }); + res = await acl.whatResources("role2"); + assert.deepEqual(res.res1.sort(), ["perm1", "perm2", "perm3"]); }); - it('Remove role "role1"', function (done) { - var acl = new Acl(backend); + it('Remove role "role1"', async function () { + const acl = new Acl(backend); - acl.removeRole("role1", function (err) { - assert.ifError(err); - done(); - }); + await acl.removeRole("role1"); }); - it('Verify that "role1" has no permissions and "role2" has permissions intact', function (done) { - var acl = new Acl(backend); + it('Verify that "role1" has no permissions and "role2" has permissions intact', async function () { + const acl = new Acl(backend); - acl.removeRole("role1", function (err) { - assert.ifError(err); + await acl.removeRole("role1"); - acl.whatResources("role1", function (err, res) { - assert(Object.keys(res).length === 0); + let res = await acl.whatResources("role1"); + assert(Object.keys(res).length === 0); - acl.whatResources("role2", function (err, res) { - assert.deepEqual(res.res1.sort(), ["perm1", "perm2", "perm3"]); - done(); - }); - }); - }); + res = await acl.whatResources("role2"); + assert.deepEqual(res.res1.sort(), ["perm1", "perm2", "perm3"]); }); }); });