diff --git a/README.md b/README.md index fcf891b..784ebba 100644 --- a/README.md +++ b/README.md @@ -125,12 +125,9 @@ const opts = { defaultRole: 'anonymous', unauthenticatedErrorCode: 401, unauthorizedErrorCode: 403, - resourceName: model => model.name, resourceAugments: { true: true, false: false, undefined: undefined }, userFromResult: false, - contextKey: 'req', - library: 'role-acl', - wrapClass: false + contextKey: 'req' } ``` diff --git a/index.js b/index.js index 4245bd7..99416b2 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,8 @@ const assert = require('http-assert') const isEmpty = obj => !Object.keys(obj || {}).length const debug = require('debug')('objection-authorize') +const pick = require('lodash.pick') +const omit = require('lodash.omit') module.exports = (acl, library = 'role-acl', opts) => { if (!acl) throw new Error('acl is a required parameter!') @@ -41,17 +43,11 @@ module.exports = (acl, library = 'role-acl', opts) => { } = this.context() body = body || resource action = _action || action - const ctx = Object.assign( - {}, - { [opts.contextKey]: { user, body } }, - opts.resourceAugments, - resource - ) resource = this.modelClass().fromJson(resource, { skipValidation: true }) - const access = lib.getAccess(acl, user, resource, action, ctx) + const access = lib.getAccess(acl, user, resource, action, ctx, opts) // authorize request assert( @@ -246,9 +242,6 @@ module.exports = (acl, library = 'role-acl', opts) => { } return class extends Model { - // used to filter model's attributes according to a user's read access. - // First pick the fields, and then filter them, as per: - // https://github.com/oscaroox/objection-visibility _filter (access) { const pickFields = lib.pickFields(access) const omitFields = lib.omitFields(access) diff --git a/lib/casl.js b/lib/casl.js index 087ae59..4e1f891 100644 --- a/lib/casl.js +++ b/lib/casl.js @@ -1,19 +1,13 @@ -exports.getAccess = (acl, user, resource, action, ctx) => - acl - .can(user.role) - .execute(action) - .context(ctx) - .on(resource.constructor.name) +const { permittedFieldsOf } = require('@casl/ability/extra') -exports.isAuthorized = access => access.granted +exports.getAccess = (acl, user, resource, action, opts) => + acl(user, resource, action) -exports.filter = (access, body) => access.filter(body) +exports.isAuthorized = (ability, action, subject) => + ability.can(action, subject) -exports.pickFields = access => - access.attributes.filter(field => field !== '*' && !field.startsWith('!')) || - [] +exports.filter = (ability, body) => access.filter(body) -exports.omitFields = access => - access.attributes - .filter(field => field.startsWith('!')) - .map(field => field.substr(1)) || [] +exports.pickFields = (ability, action, subject) => permittedFieldsOf(ability) + +exports.omitFields = ability => [] diff --git a/lib/role-acl.js b/lib/role-acl.js index 087ae59..c37bf36 100644 --- a/lib/role-acl.js +++ b/lib/role-acl.js @@ -1,8 +1,15 @@ -exports.getAccess = (acl, user, resource, action, ctx) => +exports.getAccess = (acl, user, resource, action, body, opts) => acl .can(user.role) .execute(action) - .context(ctx) + .context( + Object.assign( + {}, + { [opts.contextKey]: { user, body } }, + opts.resourceAugments, + resource + ) + ) .on(resource.constructor.name) exports.isAuthorized = access => access.granted diff --git a/package.json b/package.json index 51f80c2..5c84aa1 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,9 @@ }, "dependencies": { "debug": "^4.1.1", - "http-assert": "^1.4.1" + "http-assert": "^1.4.1", + "lodash.omit": "^4.5.0", + "lodash.pick": "^4.4.0" }, "peerDependencies": { "objection": "1.X" diff --git a/test/casl.test.js b/test/casl.test.js index e69de29..fdefb94 100644 --- a/test/casl.test.js +++ b/test/casl.test.js @@ -0,0 +1,21 @@ +const pluginTest = require('./utils/plugin-test') +const { AbilityBuilder, Ability } = require('@casl/ability') + +// TODO: do I need to put the deny before or after the allow? +function acl (user, resource, action, ctx) { + return AbilityBuilder.define((allow, forbid) => { + allow('read', 'User') + forbid('read', 'User', ['email', 'secrethiddenfield']) + + if (user.role === 'anonymous') { + allow('create', 'User') + } else if (user.role === 'user') { + allow('read', 'User', ['email'], { id: user.id }) + allow('update', 'User', { id: user.id }) + forbid('update', 'User', ['id']) + allow('delete', 'User', { id: user.id }) + } + }) +} + +pluginTest(acl, 'casl') diff --git a/yarn.lock b/yarn.lock index 5c92745..a8e918a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3452,6 +3452,16 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash.omit@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" + integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA= + +lodash.pick@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" + integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"