npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

bookshelf-shield

v2.2.2

Published

Access control list based authorization for bookshelf models

Downloads

9

Readme

Shield

bookshelf-shield

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

Codeship Status for MRN-Code/bookshelf-shield

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

  1. Set up your ACL
relations.define('study', {
    PI: ['read_Study'],
    siteAdmin: [
        'read_Study',
        'update_Study',
        'create_Study',
        'delete_Study'
    ]
});
  1. Set up you bookshelf models
const models = {
    Study: bookshelf.Model.extend({tableName: 'mrs_studies'}),
    //...
};
  1. Create a shieldConfig for each model (see configuration section for more)
const config = [
    {
        defaults: {
            modelName: 'Study',
            authKey: 'id',
            aclContextName: 'study'
        },
        //optional action-specific configs here
    },
    //...
  1. Shields up!
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.

  1. create
    const user = { username: 'dylan' };
    const widget = new Widget({ color: 'blue' });
    widget.create(user).then((newWidget) => {
        //new Widget successfully created
    }).catch((error) => {
        //handle Error
    });
  1. read
    const user = { username: 'dylan' };
    const widget = new Widget({ id: '101' });
    widget.read(user).then((newWidget) => {
        //newWidget successfully read
    }).catch((error) => {
        //handle Error
    });
  1. readAll
    const user = { username: 'dylan' };
    const widget = new Widget();
    widget.query({color: 'blue'});
    widget.readAll(user).then((newWidgets) => {
        //widgets successfully read into newWidgets collection
    }).catch((error) => {
        //handle Error
    });
  1. 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) => {
        //widget successfully updated
    }).catch((error) => {
        //handle Error
    });
  1. 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) => {
        //widgets successfully deleted (newWidget should now be empty)
    }).catch((error) => {
        //handle Error
    });
  1. bypass
    const widget = new Widget({ id: '101' });
    widget.bypass('fetch').then((newWidget) => {
        //new Widget successfully created
    }).catch((error) => {
        //handle 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: { // These defaults will be applied to all CRUD access methods, unless overridden below.
        modelName: 'Study', // The name of the model: must match the key associated with the model in the models object passed to shield init.
        authKey: 'id', // The property that should be used for authorization.
        aclContextName: 'study' // The name of the ACL (relations) context to be used
    },
    create: { //specifying any CRUD method allows you to override the defaults secified above
        authKey: 'siteId', //alternative auth key to be used when evaluating create access
        aclContextName: 'site',
        method: function validateSiteAdmin(user) {
            // this is a cusom authentication method that will be invoked instead of the generic method.
            // `this` refers to the current instance of the bookshelf model
            const siteId = this.get('siteId');
            // data stored on the shield can be accessed through the current object's constructor (the bookshelf Model).
            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:

// Note options is the config for the current Model and action
function genericAuthMethod(user) {
    const authVal = this.get(options.authKey);
    const aclQuestion = 'can ' +
        user.username +
        ' ' +
        permissionName +
        ' from ' +
        authVal;
    const aclContext = options.acl[aclContextName];

    //TODO: optimize to cache perms instead of loading from redis
    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