
bookshelf-shield
Form a protective shield around your bookshelf models.
This module adds ACL-based authorization, and a CRUD API to bookshelf models.

Dependencies
relations
As of right now, bookshelf-shield only can interact with the ACL module called relations
Provides an intuitive interface for storing and querying Access Conrtol Lists.
Relations is used to determine whether a user has been granted access to perform an action on the model.
ES6
This module utilizes ES6 features, including classes, arrow functions and Promises. As a result node 4.0.0+ is required.
Usage
relations.define('study', {
PI: ['read_Study'],
siteAdmin: [
'read_Study',
'update_Study',
'create_Study',
'delete_Study'
]
});
- Set up you bookshelf models
const models = {
Study: bookshelf.Model.extend({tableName: 'mrs_studies'}),
};
- Create a shieldConfig for each model (see configuration section for more)
const config = [
{
defaults: {
modelName: 'Study',
authKey: 'id',
aclContextName: 'study'
},
},
const shield = require('bookshelf-shield');
shield({
config: config,
acl: relations,
models: models
});
API
Once a model has been shielded, you can interact with it using a standard CRUD API, rather than the traditional fetch, save, destroy
bookshelf API. This was implemented to more easily map to user's permissions.
const user = { username: 'dylan' };
const widget = new Widget({ color: 'blue' });
widget.create(user).then((newWidget) => {
}).catch((error) => {
});
const user = { username: 'dylan' };
const widget = new Widget({ id: '101' });
widget.read(user).then((newWidget) => {
}).catch((error) => {
});
const user = { username: 'dylan' };
const widget = new Widget();
widget.query({color: 'blue'});
widget.readAll(user).then((newWidgets) => {
}).catch((error) => {
});
- update (note: by default, read access is required to perform an update)
const user = { username: 'dylan' };
widget.set('color', 'red');
widget.update(user).then((newWidget) => {
}).catch((error) => {
});
- delete (note: by default, read access is required to perform a delete)
const user = { username: 'dylan' };
const widget = new Widget({ id: '101' });
widget.delete(user).then((newWidget) => {
}).catch((error) => {
});
const widget = new Widget({ id: '101' });
widget.bypass('fetch').then((newWidget) => {
}).catch((error) => {
});
Configuration
Each model to be shielded requires a config object. During initialization, these config objects should be provided as an array. Here is an example config object:
module.exports = {
defaults: {
modelName: 'Study',
authKey: 'id',
aclContextName: 'study'
},
create: {
authKey: 'siteId',
aclContextName: 'site',
method: function validateSiteAdmin(user) {
const siteId = this.get('siteId');
const Model = this.constructor;
const aclContext = Model.shield.acl.site;
const aclQuestion = 'can ' +
user.username +
' create_Study from ' +
siteId;
if (!siteId) {
return Promise.reject(
new Error('Study has no valide siteId')
);
}
return aclContext(aclQuestion).then(function checkAuth(allow) {
let errorMsg;
if (allow) {
return allow;
}
errorMsg =
user.username +
' cannot create studies in Site ' +
siteId;
throw new Error(errorMsg);
});
}
}
};
Because there are no configuration objects specified for read
, update
and delete
operations, those operations will be protected using the generic method (see below).
Generic Auth Method
Unless a custom method is specified in the Model's config, the following generic method will be applied:
function genericAuthMethod(user) {
const authVal = this.get(options.authKey);
const aclQuestion = 'can ' +
user.username +
' ' +
permissionName +
' from ' +
authVal;
const aclContext = options.acl[aclContextName];
return aclContext(aclQuestion).then(function checkAuth(allow) {
let errorMsg;
if (allow) {
return allow;
}
errorMsg = [
user.username,
'cannot',
permissionName.replace('_', ' '),
'in',
options.authKey,
'`' + authVal + '`'
].join(' ');
throw new AuthError(errorMsg);
});
Examples
See test/integration/main.js
for a full example
Tests
Fully unit and integration tested
Contributing
Please follow the MRN Javascript Style Guide (forked from AirBnB). Use grunt lint
to check yo-self