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

dmv

v1.3.4

Published

A MEAN stack role/permission management system

Downloads

65

Readme

Build status Code Climate Dependency Status

DMV

Intro

DMV is a MEAN stack library that lets you define roles and permissions. It lets you express thoughts like:

student can read chapter. teacher can assign chapter.

I like to think about the previous sentence like this

  [Role] can [Permission]
                  ^
                /   \
            [Verb] [Noun]

Practical Example

We use DMV to register the roles and nouns:

dmv.role('author');
dmv.role('moderator');
dmv.role('admin');

dmv.noun('article', function(articleNoun) {
  // by default the noun has crud verbs
  // we're adding the approve verb here
  articleNoun.verb('approve');

  // we say that admins can do all verbs
  articleNoun.authorize('admin', '*');

  // moderators can 
  articleNoun.authorize('moderator', ['approve', 'read']);

  // authors can
  articleNoun.authorize('author', ['create', 'update'])

});

dmv.noun('comment', function(commentNoun) {
  commentNoun.verb('approve');

  // we say that admins can do all verbs
  commentNoun.authorize('admin', '*');

  // moderators can 
  commentNoun.authorize('moderator', ['approve']);
});

Mongoose


const userSchema = new mongoose.Schema({
  name: String
  // ...
});

//

/**
 * This plugin will add three paths to your user model
 * 
 * - roles
 * - permissionWhitelist
 * - permissionBlacklist
 * 
 * More on the latter two in a minute.
 * It will also add the `can` method.
 */

userSchema.plugin(require('dmv/mongoosePlugin'));

mongoose.model('User', userSchema);

You can use your user instance's can method like this


const sarah = new User({
  roles: ['author'],
  name: 'Sarah'
});

const alex = new User({
  roles: ['moderator'],
  name: 'Alex'
});

const hilary = new User({
  roles: ['admin'],
  name: 'Hilary'
});

sarah.can('create', 'article'); // => true
sarah.can('update', 'article'); // => true
sarah.can('delete', 'article'); // => false

alex.can('approve', 'article'); // true
alex.can('create', 'article');  // false

hilary.can('delete', 'article') // true
hilary.can('approve', 'article') // true

Express

const dmv = require('dmv');
const auth = require('dmv/expressMiddleware');

// articles router

// everyone can read all articles
router.get('/', controller.index);
router.post('/', auth.permits('create', 'article'), controller.create);

router.use('/:id', controller.load);

router.get('/:id', controller.read);


router.put('/:id', auth.permits('update', 'article'), controller.update);
router.delete('/:id', auth.permits('delete', 'article'), controller.delete);

Dmv's express middleware calls the can method on the req.user by default. If your application doesn't have a req.user, you can define another function to return a user object.

auth.user(function(req, res) {
  return req.yourOtherUserObject;
});

If you have a router that really only concerns itself with one noun (like above), you can use permitsFactory to generate a version of permits that only works for one noun. That would look like this.

const dmv = require('dmv');
const auth = dmv.expressMiddleware
const permits = auth.permitsFactory('article')

// articles router

// everyone can read all articles
router.get('/', controller.index);
router.post('/', permits('create'), controller.create);

// ...

Angular

Note: we assume you use ui-router for now.

First, you can use our browser file

<script src="./path/to/browser.js"></script>

We expose the dmv object on the global scope. Dmv is isomorphic so you can run the same code defining permissions and roles that you ran on the server. Doing this will depend on your build process and environment.

Once your frontend knows about the roles and permissions you can plug in our angular module

angular.module('yourApp', ['ui.router', 'dmv']);

Then, you need to hook us into your user model. We expose a factory called canPlugin. canPlugin is a function that gives your user model a can method by modifying its prototype. You pass the prototype to canPlugin. You do it like this.

yourModule.factory('YourUserClass', funciton($httpMaybe?, canPlugin) {
  const User = function() {
    // your constructor
  };

  canPlugin(User.prototype);

  return User;
})

You can call can on any of your user instances. In addition, we've added a hasRole method on your user class.

You need to tell dmv how to find your logged in user. We've exposed a method called getUser on our authConfig factory. The getUser method should return your user synchronously. You can inject your own angular services. You just need to return your user. The getUser method also attaches can and hasRole to your $rootScope so you can show and hide view segments easily.

yourModule
  .run(function(authConfig) {
    // Note, ng-annotate and others won't annotate the dependencies for this method. So if you minify in your build process, you should annotate your own dependencies.
    authConfig.getUser(function(YourUserService, YourAuthService) {
      return YouAuthService.getLoggedInUser();
    })
  })
  

Then you can add a special auth property to your ui-router state definitions.

yourModule
  .config(function($stateProvider) {
    $stateProvider
      .state('articles', {
        url: '/articles',
        template: 'everyone can see these great articles',
        auth: false // you could omit this, it just documents here
      })
      .state('new_article', {
        url: '/newarticle',
        template: 'form only for those who can write',
        auth: {
          create: 'article'
        }
      });

  }) 

The auth property can take a boolean, function or an object. If you pass true to auth it will ensure the user has logged in. An object lets you specify the nouns and verbs that the user must have access to to load the route. You can also pass auth a function. If you return true from that function, the user will be allowed to enter the state. The function's this is bound to the state change event. It is passed the user object and the next state.

Our module will broadcast a NOT_AUTHORIZED event on the root scope if a user goes to a route they are not authorized to see. We will broadcast a NOT_AUTHENTICATED event if a user that hasn't logged in attempts to see a route that has a truthy auth property.

With these events you can configure the behavior of failed route loads.

If you would like the auth property to be evaluated only after the user has been fetched asynchronously, you may pass a method that returns a Promise for your user to the authConfig via the getUserAsync method.

yourModule
  .run(function(authConfig) {

    // optionally tell dmv how to get the user asynchronously
    authConfig.getUserAsync(function (YourUserService, YourAuthService) {
      return YourAuthService.getLoggedInUserAsync();
    })

    // you still must tell dmv how to get the user synchronously - it is required!
    authConfig.getUser(function(YourUserService, YourAuthService) {
      return YourAuthService.getLoggedInUser();
    })
  })
  

If an asynchronous method is present, dmv will use it to obtain the user before evaluating the auth property. Note any front-end caching is the responsibility of your Angular services - dmv will only expect to receive a Promise for the user.