MCPcopy Index your code
hub / github.com/Meteor-Community-Packages/meteor-roles

github.com/Meteor-Community-Packages/meteor-roles @v4.0.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v4.0.0 ↗ · + Follow
28 symbols 55 edges 46 files 0 documented · 0%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

meteor-roles v3

Project Status: Active – The project has reached a stable, usable state and is being actively developed. GitHub JavaScript Style Guide CodeQL GitHub tag (latest SemVer)

Authorization package for Meteor - compatible with built-in accounts package.

There are also older versions of this package: * v1 * v2 * v3

Table of Contents

Contributors

Thanks to:

Authorization

This package lets you attach roles 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 creating an assignment of roles to a user and then checking for the existence of those roles later. This package provides helper methods to make the process of adding, removing, and verifying those roles 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:

import { Roles } from 'meteor/alanning:roles';

Roles.createRoleAsync('user');
Roles.createRoleAsync('admin');
Roles.createRoleAsync('USERS_VIEW');
Roles.createRoleAsync('POST_EDIT');
Roles.addRolesToParentAsync('USERS_VIEW', 'admin');
Roles.addRolesToParentAsync('POST_EDIT', 'admin');
Roles.addRolesToParentAsync('POST_EDIT', 'user');

What are "scopes"?

Sometimes it is useful to let a user have independent sets of roles. The roles package calls these independent sets "scopes" for lack of a better term. You can use them to represent various communities inside your application. Or maybe your application supports multiple tenants. You can put each of those tenants into their own scope. Alternatively, you can use scopes to represent various resources you have.

Users can have both scope roles assigned, and global roles. Global roles are in effect for all scopes. But scopes are independent of each other. Users can have one set of roles in scope A and another set of roles in scope B. Let's go through an example of this using soccer/football teams as scopes.

Roles.addUsersToRolesAsync(joesUserId, ['manage-team','schedule-game'], 'manchester-united.com');
Roles.addUsersToRolesAsync(joesUserId, ['player','goalie'], 'real-madrid.com');

Roles.userIsInRoleAsync(joesUserId, 'manage-team', 'manchester-united.com'); // true
Roles.userIsInRoleAsync(joesUserId, 'manage-team', 'real-madrid.com'); // false

In this example, we can see that Joe manages Manchester United and plays for Real Madrid. By using scopes, we can assign roles independently and make sure that they don't get mixed up between scopes.

Now, let's take a look at how to use the global roles. Say we want to give Joe permission to do something across all of our scopes. That is what the global roles are for:

Roles.addUsersToRolesAsync(joesUserId, 'super-admin', null); // Or you could just omit the last argument.

const isInRole = await Roles.userIsInRoleAsync(joesUserId, ['manage-team', 'super-admin'], 'real-madrid.com')
if (isInRole) {
  // True! Even though Joe doesn't manage Real Madrid, he has
  // a 'super-admin' global role so this check succeeds.
}

Changes to default Meteor behavior

  1. A new collection Meteor.roleAssignment contains the information about which role has been assigned to which user.
  2. A new collection Meteor.roles contains a global list of defined role names.
  3. All existing roles are automatically published to the client.

Installing

  1. Add one of the built-in accounts packages so the Meteor.users collection exists. From a command prompt: bash meteor add accounts-password

  2. Add this package to your project. From a command prompt: bash meteor add alanning:roles

  3. Publish the role assignments you need to the client: js Meteor.publish(null, function () { if (this.userId) { return Meteor.roleAssignment.find({ 'user._id': this.userId }); } else { this.ready() } })

  4. Run your application: bash meteor

Migration to 4.0

If you are currently using this package in a version older than 3.6, please upgrade to 3.6 and follow all the steps described for previous major version upgrades. Make sure to stay on 3.x until you have run the migration scripts as those are no longer available in version 4.

Before upgrading to version 4 make sure that all your server side roles call use the async versions of the functions.

Here is a full list of new async functions: * createRoleAsync * deleteRoleAsync * renameRoleAsync * addRolesToParentAsync * removeRolesFromParentAsync * addUsersToRolesAsync * setUserRolesAsync * removeUsersFromRolesAsync * userIsInRoleAsync * getRolesForUserAsync * getUsersInRoleAsync * getGroupsForUserAsync * getScopesForUserAsync * renameScopeAsync * removeScopeAsync * isParentOfAsync

NOTE: The sync version of these functions are still available on the client.

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 forward 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 users documents to a separate collection called role-assignment, available at Meteor.roleAssignment.
  • Role assignments are not published automatically. If you want all your role-assignments to be published automatically, please include the following code:
Meteor.publish(null, function () {
  if (this.userId) {
    return Meteor.roleAssignment.find({ 'user._id': this.userId });
  } else {
    this.ready()
  }
})
  • [BC] The behavior of getRolesForUser() used with the option fullObjects changed. In case you need the old behavior ...
  • Added option anyScope to removeUsersFromRoles()
  • Add option onlyScoped to getRolesForUser() 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:

var users = [
      {name:"Normal User",email:"normal@example.com",roles:[]},
      {name:"View-Secrets User",email:"view@example.com",roles:['view-secrets']},
      {name:"Manage-Users User",email:"manage@example.com",roles:['manage-users']},
      {name:"Admin User",email:"admin@example.com",roles:['admin']}
    ];

for (const user of users) {
  var id;

  id = await Accounts.createUserAsync({
    email: user.email,
    password: "apple1",
    profile: { name: user.name }
  });

  const count = await Meteor.roleAssignment.coundDocuments({ 'user._id': id })
  if (count === 0) {
    import { Roles } from 'meteor/alanning:roles';

    for (const role of user.roles)  {
      Roles.createRoleAsync(role, {unlessExists: true});
    }
    // Need _id of existing user record so this call must come after `Accounts.createUser`.
    Roles.addUsersToRolesAsync(id, user.roles);
  }

}

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:

// server/publish.js
import { Roles } from 'meteor/alanning:roles'

// Give authorized users access to sensitive data by scope
Meteor.publish('secrets', async function (scope) {
  check(scope, String);

  const isInRole = await Roles.userIsInRoleAsync(this.userId, ['view-secrets','admin'], scope)
  if (isInRole) {

    return Meteor.secrets.find({scope: scope});

  } else {

    // user not authorized. do not publish secrets
    this.stop();
    return;

  }
});

Prevent non-authorized users from creating new users:

Accounts.validateNewUser(async function (user) {
  import { Roles } from 'meteor/alanning:roles'

  var loggedInUser = Meteor.user();

  const isInRole = await Roles.userIsInRoleAsync(loggedInUser, ['admin','manage-users'])
  if () {
    return true;
  }

  throw new Meteor.Error('unauthorized', "Not authorized to create new users");
});

Prevent access to certain functionality, such

Extension points exported contracts — how you extend this code

Role (Interface)
(no doc)
definitions.d.ts
RoleAssignment (Interface)
(no doc)
definitions.d.ts
QueryOptions (Interface)
(no doc)
definitions.d.ts

Core symbols most depended-on inside this repo

testUser
called by 215
roles/tests/serverAsync.js
safeInsert
called by 6
roles/tests/client.js
testUser
called by 6
roles/tests/client.js
safeInsert
called by 6
roles/tests/clientAsync.js
testUser
called by 6
roles/tests/clientAsync.js
addUser
called by 3
roles/tests/serverAsync.js
displayName
called by 2
examples/iron-router/client/client.js
displayName
called by 2
examples/flow-router/client/client.js

Shape

Function 25
Interface 3

Languages

TypeScript100%

Modules by API surface

roles/tests/serverAsync.js10 symbols
definitions.d.ts3 symbols
roles/tests/clientAsync.js2 symbols
roles/tests/client.js2 symbols
examples/iron-router/client/client.js2 symbols
examples/flow-router/client/client.js2 symbols
roles/roles_common_async.js1 symbols
examples/iron-router/client/routing.js1 symbols
examples/flow-router/client/layouts.js1 symbols
examples/flow-router-advanced/secrets/server/fixtures.js1 symbols
examples/flow-router-advanced/main/server/startup/fixtures.js1 symbols
examples/flow-router-advanced/main/client/layouts.js1 symbols

For agents

$ claude mcp add meteor-roles \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact