meteor-roles
This package is based on v1 of the alanning:meteor-roles package. It has been updated to work with newer Meteor versions and to be more performant when querying the database.
Also UI helpers have been removed, and roles are no longer published by default. You can publish them yourself if you want to use them on the client.
Table of Contents
- Contributors
 - Authorization
 - Permissions vs roles
 - What are "scopes"?
 - Changes to default Meteor
 - Installation
 - Migration to 3.0
 - Usage examples
 - Online API docs
 - Example apps
 - Running tests
 
Contributors
Thanks to:
- @mitar
 - @challett
 - @ianserlin
 - @leebenson
 - @pward123
 - @dandv
 - @aldeed
 - @kevb
 - @zimme
 - @danieljonce
 - @pascoual
 - @nickmoylan
 - @mcrider
 - @alanning
 - @simonsimcity
 
Authorization
This package lets you attach permissions to a user which you can then check against later when deciding whether to grant access to Meteor methods or publish data. The core concept is very simple, essentially you are attaching strings to a user object and then checking for the existence of those strings later. In some sense, it is very similar to tags on blog posts. This package provides helper methods to make the process of adding, removing, and verifying those permissions easier.
Permissions vs roles (or What's in a name...)
Although the name of this package is roles, you can define your roles, scopes or permissions however you like.
They are essentially just tags that you assign to a user and which you can check upon later.
You can have traditional roles like, admin or webmaster, or you can assign more granular permissions such
as, view-secrets, users.view, or users.manage.  Often, more granular is actually better because you are
able to handle all those pesky edge cases that come up in real-life usage without creating a ton of higher-level
roles.  With the roles package, it's all just a role object.
Roles can be put into a hierarchy.
Roles can have multiple parents and can be children (subroles) of multiple roles.
If a parent role is set to the user, all its descendants are also applying.
You can use this to create "super roles" aggregating permissions all the way through the bottom of the tree.
For example, you could name two top-level roles user and admin and then you could use your second-level roles as permissions and name them USERS_VIEW, POST_EDIT, and similar.
Then you could set admin role as parent role for USERS_VIEW and POST_EDIT, while user would be parent
only of the POST_EDIT role. You can then assign user and admin roles to your users. And if you need to
change permissions later for the whole role, just add or remove children roles. You can create roles from this example
with:
1import { Roles } from 'meteor/alanning:roles'; 2 3Sometimes it's useful to let a user have independent sets of permissions. The `roles` package calls these independent sets, "groups" for lack of a better term. You can think of them as "partitions" if that is more clear. Users can have one set of permissions in group A and another set of permissions in group B. Let's go through an example of this using soccer/football teams as groups. 4 5```js 6import { Roles } from 'meteor/alanning:roles' 7 8Roles.addUsersToRoles(joesUserId, ['manage-team','schedule-game'], 'manchester-united.com') 9Roles.addUsersToRoles(joesUserId, ['player','goalie'], 'real-madrid.com') 10 11 12 13### What are "scopes"? 14 15Sometimes it is useful to let a user have independent sets of roles. The `roles` package calls these independent 16sets "scopes" for lack of a better term. You can use them to represent various communities inside of your 17application. Or maybe your application supports [multiple tenants](https://en.wikipedia.org/wiki/Multitenancy). 18You can put each of those tenants into their own scope. Alternatively, you can use scopes to represent 19various resources you have. 20 21Users can have both scope roles assigned, and global roles. Global roles are in effect for all scopes. 22But scopes are independent from each other. Users can have one set of roles in scope A and another set 23of roles in scope B. Let's go through an example of this using soccer/football teams as scopes. 24 25```js 26import { Roles } from 'meteor/alanning:roles' 27 28Roles.addUsersToRoles(joesUserId, 'super-admin', Roles.GLOBAL_GROUP) 29 30In this example we can see that Joe manages Manchester United and plays for Real Madrid. By using scopes, we can 31assign roles independently and make sure that they don't get mixed up between scopes. 32 33Now, let's take a look at how to use the global roles. Say we want to give Joe permission to do something across 34all of our scopes. That is what the global roles are for: 35 36```javascript 37Roles.addUsersToRoles(joesUserId, 'super-admin', null); // Or you could just omit the last argument. 38 39if (Roles.userIsInRole(joesUserId, ['manage-team', 'super-admin'], 'real-madrid.com')) { 40 // True! Even though Joe doesn't manage Real Madrid, he has 41 // a 'super-admin' global role so this check succeeds. 42}
Changes to default Meteor behavior
- A new collection 
Meteor.roleAssignmentcontains the information which role has been assigned to which user. - A new collection 
Meteor.rolescontains a global list of defined role names. - All existing roles are automatically published to the client.
 
† The type of the roles field depends on whether or not groups are used:
1import { Roles } from 'meteor/alanning:roles' 2 3Roles.addUsersToRoles(bobsUserId, ['manage-team','schedule-game']) 4// internal representation - no groups 5// user.roles = ['manage-team','schedule-game'] 6 7Roles.addUsersToRoles(joesUserId, ['manage-team','schedule-game'], 'manchester-united.com') 8Roles.addUsersToRoles(joesUserId, ['player','goalie'], 'real-madrid.com') 9// internal representation - groups 10// NOTE: MongoDB uses periods to represent hierarchy so periods in group names 11// are converted to underscores. 12// 13// user.roles = { 14// 'manchester-united_com': ['manage-team','schedule-game'], 15// 'real-madrid_com': ['player','goalie'] 16// }
Note: See the addUsersToRoles documentation for restrictions on group names.
†† The Meteor.roles collection is currently only for convenience on the UI-side and is not used functionally within this package.  In the future it may be used to support role hierarchies.  Since it is not currently required, it is not automatically published to the client.  Here's how you would publish it to every client without needing a subscription:
1// in server/publish.js 2Meteor.publish(null, function (){ 3 return Meteor.roles.find({}) 4})
Installing
- 
Add one of the built-in accounts packages so the
Meteor.userscollection exists. From a command prompt:meteor add accounts-password - 
Add this package to your project. From a command prompt:
meteor add alanning:roles - 
Publish the role assignments you need to the client:
1Meteor.publish(null, function () { 2 if (this.userId) { 3 return Meteor.roleAssignment.find({ 'user._id': this.userId }); 4 } else { 5 this.ready() 6 } 7}) - 
Run your application:
meteor 
Migration to 3.0
If you are currently using this package in a version older than 2.x, please upgrade to 2.0 by running the migration script required there: https://github.com/Meteor-Community-Packages/meteor-roles/tree/v2#migration-to-20
In meteor-roles 3.0, functions are mostly backwards compatible with 2.x, but roles are stored differently in the database. Please take a backup of the users collection before migrating. To migrate the database to the new schema, run Meteor._forwardMigrate2() on the server:
meteor shell > Package['alanning:roles'].Roles._forwardMigrate2()
In case something fails, there is also a script available for rolling back the changes. But be warned that a backward migration takes a magnitude longer than a foward migration. To migrate the database back to the old schema, run Meteor._backwardMigrate2() on the server:
meteor shell > Package['alanning:roles'].Roles._backwardMigrate2()
Changes between 2.x and 3.0
Here is the list of important changes between meteor-roles 2.x and 3.0 to consider when migrating to 3.0:
- Role assignments have been moved from the 
usersdocuments to a separate collection calledrole-assignment, available atMeteor.roleAssignment. - Role assignments are not published automatically. If you want all your role-assignments to be published automatically please include the following code:
 
1Meteor.publish(null, function () { 2 if (this.userId) { 3 return Meteor.roleAssignment.find({ 'user._id': this.userId }); 4 } else { 5 this.ready() 6 } 7})
- [BC] The behavior of 
getRolesForUser()used with the optionfullObjectschanged. In case you need the old behavior ... - Added option 
anyScopetoremoveUsersFromRoles() - Add option 
onlyScopedtogetRolesForUser()to allow limiting the result to only scoped permissions - All functions (excepted for those listed above) work with 2.x arguments, but in 3.x accept extra arguments and/or options.
 - Details and reasoning can be found in #276
 
Usage Examples
Here are some potential use cases:
-- Server --
Add users to roles:
1import { Roles } from 'meteor/alanning:roles' 2 3var users = [ 4 {name:"Normal User",email:"normal@example.com",roles:[]}, 5 {name:"View-Secrets User",email:"view@example.com",roles:['view-secrets']}, 6 {name:"Manage-Users User",email:"manage@example.com",roles:['manage-users']}, 7 {name:"Admin User",email:"admin@example.com",roles:['admin']} 8 ]; 9 10users.forEach(function (user) { 11 var id; 12 13 id = Accounts.createUser({ 14 email: user.email, 15 password: "apple1", 16 profile: { name: user.name } 17 }); 18 19 if (Meteor.roleAssignment.find({ 'user._id': id }).count() === 0) { 20 import { Roles } from 'meteor/alanning:roles'; 21 22 user.roles.forEach(function (role) { 23 Roles.createRole(role, {unlessExists: true}); 24 }); 25 // Need _id of existing user record so this call must come after `Accounts.createUser`. 26 Roles.addUsersToRoles(id, user.roles); 27 } 28 29});
Note that the Roles.addUsersToRoles call needs to come after Accounts.createUser or else
the roles package won't be able to find the user record (since it hasn't been created yet).
You can use postSignUpHook to assign roles when using
user accounts package.
This SO answer gives more detail: http://stackoverflow.com/a/22650399/219238
Check user roles before publishing sensitive data:
1// server/publish.js 2import { Roles } from 'meteor/alanning:roles' 3 4 5// Give authorized users access to sensitive data by group 6Meteor.publish('secrets', function (group) { 7 if (Roles.userIsInRole(this.userId, ['view-secrets','admin'], group)) { 8 9 if (Roles.userIsInRole(this.userId, ['view-secrets','admin'], scope)) { 10 11 return Meteor.secrets.find({scope: scope}); 12 13 } else { 14 15 // user not authorized. do not publish secrets 16 this.stop(); 17 return; 18 19 } 20});
Prevent non-authorized users from creating new users:
1import { Roles } from 'meteor/alanning:roles' 2 3Accounts.validateNewUser(function (user) { 4 import { Roles } from 'meteor/alanning:roles' 5 6 var loggedInUser = Meteor.user(); 7 8 if (Roles.userIsInRole(loggedInUser, ['admin','manage-users'])) { 9 return true; 10 } 11 12 throw new Meteor.Error('unauthorized', "Not authorized to create new users"); 13});
Prevent access to certain functionality, such as deleting a user:
1// server/userMethods.js 2import { Roles } from 'meteor/alanning:roles' 3 4Meteor.methods({ 5 /** 6 * Revokes roles for a user in a specific scope. 7 * 8 * @method revokeUser 9 * @param {String} targetUserId ID of user to revoke roles for. 10 * @param {String} scope Company to update roles for. 11 */ 12 revokeUser: function (targetUserId, scope) { 13 check(targetUserId, String); 14 check(scope, String); 15 16 var loggedInUser = Meteor.user(); 17 18 if (!loggedInUser || 19 !Roles.userIsInRole(loggedInUser, 20 ['manage-users', 'support-staff'], scope)) { 21 throw new Meteor.Error('access-denied', "Access denied") 22 } 23 24 // remove roles for target scope 25 Roles.setUserRoles(targetUserId, [], scope) 26 } 27})
Manage a user's roles:
1// server/userMethods.js 2import { Roles } from 'meteor/alanning:roles' 3 4Meteor.methods({ 5 /** 6 * Update a user's roles. 7 * 8 * @param {Object} targetUserId Id of user to update. 9 * @param {Array} roles User's new roles. 10 * @param {String} scope Company to update roles for. 11 */ 12 updateRoles: function (targetUserId, roles, scope) { 13 check(targetUserId, String); 14 check(roles, [String]); 15 check(scope, String); 16 17 var loggedInUser = Meteor.user(); 18 19 if (!loggedInUser || 20 !Roles.userIsInRole(loggedInUser, 21 ['manage-users', 'support-staff'], scope)) { 22 throw new Meteor.Error('access-denied', "Access denied"); 23 } 24 25 Roles.setUserRoles(targetUserId, roles, scope); 26 } 27})
Perform complex permission checks in a declaritive way:
(requires mdg:validated-method and didericis:permissions-mixin).
1// server/userMethods.js 2 3/** 4 * A user of role 'partner_admin' and group Roles.GLOBAL_GROUP can only create valid partner topics 5 * A user of role 'admin' and group Roles.GLOBAL_GROUP can create any arbitrary topic 6 * 7 * @param {Object} input Input parameters 8 * @param {String} input.title Topic title 9 */ 10const createTopic = new ValidatedMethod({ 11 name: 'CreateTopic', 12 mixins: [PermissionsMixin], 13 allow: [{ 14 roles: ['partner_admin'], 15 group: Roles.GLOBAL_GROUP, 16 allow({ title }) { 17 return VALID_PARTNER_TOPIC_TITLES.includes(title) 18 } 19 }, { 20 roles: ['admin'], 21 group: Roles.GLOBAL_GROUP 22 }], 23 validate: new SimpleSchema({ 24 title: { type: String } 25 }).validator(), 26 run({ title }) { 27 return topics.insert({ title }); 28 } 29}); 30
-- Client --
Client javascript does not by default have access to all the same Roles functions as the server unless you publish
these role-assignments. In addition, Blaze will have the addition of a isInRole handlebars helper which is
automatically registered by the Roles package.
As with all Meteor applications, client-side checks are a convenience, rather than a true security implementation since Meteor bundles the same client-side code to all users. Providing the Roles functions client-side also allows for latency compensation during Meteor method calls. Roles functions which modify the database should not be called directly, but inside the Meteor methods.
NOTE: Any sensitive data needs to be controlled server-side to prevent unwanted disclosure. To be clear, Meteor sends
all templates, client-side javascript, and published data to the client's browser. This is by design and is a good thing.
The following example is just sugar to help improve the user experience for normal users. Those interested in seeing
the 'admin_nav' template in the example below will still be able to do so by manually reading the bundled client.js
file. It won't be pretty but it is possible. But this is not a problem as long as the actual data is restricted server-side.
To check for global roles or when not using scopes:
API Docs
Online API docs found here: https://meteor-community-packages.github.io/meteor-roles/
API docs generated using YUIDoc
To re-generate documentation:
- install YUIDoc
 cd meteor-rolesyuidoc
To serve documentation locally:
- install YUIDoc
 cd meteor-rolesyuidoc --server 5000- point browser at http://localhost:5000/