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

authorized

v1.0.0

Published

Action based authorization middleware.

Downloads

921

Readme

Authorized!

Action based authorization middleware

The authorized package is available on npm.

$ npm install authorized

Quick start

Import an authorization manager.

var auth = require('authorized');

Roles

Provide getters for your application roles.

auth.role('admin', function(req, done) {
  done(null, req.user && req.user.admin);
});

Roles can use <entity>.<relation> syntax.

// getters for entity.relation type roles are called with the entity
auth.role('organization.owner', function(org, req, done) {
  if (!req.user) {
    done();
  } else {
    done(null, !!~org.owners.indexOf(req.user.id));
  }
});

Entities

Provide getters for your application entities.

auth.entity('organization', function(req, done) {
  // assume url like /organizations/:orgId
  var match = req.url.match(/^\/organizations\/(\w+)/);
  if (!match) {
    done(new Error('Expected url like /organizations/:orgId'));
  }
  // pretend we're going to the db for the organization
  process.nextTick(function() {
    // mock org
    var org = {id: match[1], owners: ['user.1']};
    done(null, org);
  });
});

Actions

Now define what roles are required for your actions.

auth.action('add members to organization', ['admin', 'organization.owner']);
auth.action('delete organization', ['admin']);

To perform the provided action, a user must have at least one of the given roles. In the first case, a user must be admin or organization.owner to add members to an organization. In the second case, a user must be admin to be able to delete an organization.

Note that entity and role getters can be added in any order, but you cannot configure actions until all entity and role getters have been added.

Middleware

Now you're ready to generate authorization middleware.

var middleware = auth.can('add members to organization');

This middleware can be used in Connect/Express apps in your route definitions.

var assert = require('assert');
var express = require('express');
var app = express();
app.post(
    '/organizations/:orgId/members', 
    auth.can('add members to organization'),
    function(req, res, next) {
      // you can safely let the user add members to the org here
      // you can also access entities, roles, and actions for your view
      var view = auth.view(req);
      assert.ok(view.get('organization'));
      assert.strictEqual(view.has('admin'), false);
      assert.strictEqual(view.has('organization.owner'), true);
      // this is implicit since this middleware is only called if true
      assert.strictEqual(view.can('add members to organization'), true);
      // pretend we added a member to the org
      res.send(202, 'member added');
    });

If you have a view that might allow a user to perform multiple actions, you can create middleware that allows the view to be rendered if any of a list of actions are allowed. In this case, the view will also have access to which specific actions are allowed so you can conditionally render page elements.

app.get(
    '/organizations/:orgId/manage', 
    auth.can('add members to organization', 'delete organization'),
    function(req, res, next) {
      /**
       * We've reached this point because the user can either add members or
       * delete the organization.
       */
      var view = auth.view(req);
      /**
       * To determine which actions are allowed, call the `can` method (or
       * inspect all of `view.actions`).
       */
      res.render('manage.html', {
        actions: view.actions
      });
    });

Handling unauthorized actions

If the auth manager decides a user is not authorized to perform a specific action, an UnauthorizedError will be passed down the middleware chain. To provide specific handling for this error, configure your application with error handling middleware.

app.use(function(err, req, res, next) {
  if (err instanceof auth.UnauthorizedError) {
    res.send(401, 'Unauthorized');
  } else {
    next(err);
  }
});

API

var auth = require('authorized');

The authorized module exports a Manager instance with the methods below.

Manager

role(role, getter)

  • role string - Role name (e.g. 'organization.owner').
  • getter function(req, done) - Function that determines if the current user has the given role. This function will be called with the request object and a callback. The callback has the form function(Error, boolean) where the first argument is any error value generated while checking for the given role and the second is a boolean indicating whether the user has the role.

Register a getter for a role. If the role is a string of the form entity.relation, a getter for the entity must be registered with the entity method. Roles without . are "simple" roles (e.g. "admin") and no entity is looked up. Throws ConfigError if called with an invalid role name.

entity(type, getter)

  • type {string} - Entity type (e.g. 'organization').
  • getter {function(req, done) - Function called to get an entity from the provided request. The done function has the form function(Error, Object) where the first argument is any error value generated while getting the entity and the second is the target entity.

Register a getter for an entity. Throws ConfigError if called with invalid arguments.

action(name, roles)

  • name string - Action name (e.g. 'add member to organization').
  • roles Array.<string>Roles allowed to perform this action. If the current user has any one of the supplied roles, they can perform the action (e.g. ['admin', 'organization.owner']).

Specify the roles that a user must have to perform the named action. Throws ConfigError if the provided roles have not yet been registered with the role method.

can(action)

  • action string Action name (e.g. 'add members to organization'). May also be called with multiple action arguments. Supplying '*' is an alternative to specifying all actions.

Create action based authorization middleware. Returns a middleware function with the signature function(IncomingMessage, ServerResponse, function). An UnauthorizedError will be passed to following middleware when the user is not authorized to perform the given action. Throws ConfigError if the provide action has not been registered with the action method.

view(req)

  • req Object - The request object.

Get cached authorization info for a request. Returns a View instance for accessing authorization info for the given request.

View

can(action)

  • action string - Action name.

Returns a boolean indicating whether the given action may be performed.

get(type)

  • type string - The entity type.

Returns the cached entity Object (or null if none found).

has(role)

  • role string - The role name.

Returns a boolean indicating whether the current user has the given role.

freeze()

Freeze the view. This prevents entities, actions, and roles from being modified.

ConfigError

Thrown on configuration error.

UnauthorizedError

Passed down the middleware chain when a user is not authorized to perform an action.

What else?

This package is strictly about authorization. For a full-featured authentication package, see PassportJS.

Inspiration is drawn here from connect-roles. One major difference is that this is all async (you don't have to determine if a user can perform an action synchronously).

Check out the tests for more

See the unit and integration tests for more detail on how authorized is used.

To run the linter and tests:

npm test

During development, the linter and tests can be run continously:

npm run watch

Current Status