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;dmv;dmv; dmv; dmv;
Mongoose
const userSchema = 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; mongoose;
You can use your user instance's can method like this
const sarah = roles: 'author' name: 'Sarah'; const alex = roles: 'moderator' name: 'Alex'; const hilary = roles: 'admin' name: 'Hilary'; sarah; // => truesarah; // => truesarah; // => false alex; // truealex; // false hilary // truehilary // true
Express
const dmv = ;const auth = ; // articles router // everyone can read all articlesrouter;router; router; router; router;router;
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;
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 = ;const auth = dmvexpressMiddlewareconst permits = auth // articles router // everyone can read all articlesrouter;router; // ...
Angular
Note: we assume you use ui-router for now.
First, you can use our browser file
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;
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
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
Then you can add a special auth
property to your ui-router state definitions.
yourModule
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
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.