meteor-roles

repository·master·Indexed 21 days ago

https://github.com/meteor-community-packages/meteor-roles

An authorization package for Meteor that allows developers to attach roles, permissions, and scopes to users. It supports hierarchical roles, multi-tenant scoping, and is compatible with the built-in Meteor accounts package. The package provides async server-side functions for role management and an `isInRole` Handlebars helper for Blaze templates.

Tokens
2.6K
Snippets
8
Records
10
Agent score
25%

What's inside meteor-roles

  1. How roles, permissions, and hierarchies work

    master

    In meteor-roles, roles, permissions, and scopes are all treated as simple tags assigned to users. You can use them for high-level roles (e.g., admin) or granular permissions (e.g., users.view).

    Role Hierarchies

    You can create a hierarchy where a parent role automatically includes the permissions of its children (subroles). This allows you to create "super roles" that aggregate multiple permissions. If a user is assigned a parent role, they are considered to have all descendant roles as well.

    Example of creating a hierarchy:

    import { Roles } from 'meteor/alanning:roles';
    
    // Create the roles
    await Roles.createRoleAsync('user');
    await Roles.createRoleAsync('admin');
    await Roles.createRoleAsync('USERS_VIEW');
    await Roles.createRoleAsync('POST_EDIT');
    
    // Define hierarchy: admin has USERS_VIEW and POST_EDIT; user only has POST_EDIT
    await Roles.addRolesToParentAsync('USERS_VIEW', 'admin');
    await Roles.addRolesToParentAsync('POST_EDIT', 'admin');
    await Roles.addRolesToParentAsync('POST_EDIT', 'user');
    import { Roles } from 'meteor/alanning:roles';
    
    await Roles.createRoleAsync('user');
    await Roles.createRoleAsync('admin');
    await Roles.createRoleAsync('USERS_VIEW');
    await Roles.createRoleAsync('POST_EDIT');
    await Roles.addRolesToParentAsync('USERS_VIEW', 'admin');
    await Roles.addRolesToParentAsync('POST_EDIT', 'admin');
    await Roles.addRolesToParentAsync('POST_EDIT', 'user');
  2. How scopes work for multi-tenancy

    master

    Scopes allow you to assign independent sets of roles to a user. This is useful for multi-tenant applications or representing different communities/resources within one app.

    • Scope Roles: Roles assigned within a specific scope (e.g., a specific domain or team). These do not overlap with other scopes.
    • Global Roles: Roles assigned with a null scope. Global roles are effective across all scopes. If a user has a global role, a check for a specific scope role will return true if that global role is present.

    Example of scoped vs global roles:

    // Assigning scoped roles
    await Roles.addUsersToRolesAsync(joesUserId, ['manage-team','schedule-game'], 'manchester-united.com');
    await Roles.addUsersToRolesAsync(joesUserId, ['player','goalie'], 'real-madrid.com');
    
    // Scoped check: true
    await Roles.userIsInRoleAsync(joesUserId, 'manage-team', 'manchester-united.com'); 
    // Scoped check: false
    await Roles.userIsInRoleAsync(joesUserId, 'manage-team', 'real-madrid.com'); 
    
    // Assigning a global role
    await Roles.addUsersToRolesAsync(joesUserId, 'super-admin', null);
    
    // Global check: true (even though 'super-admin' isn't specific to real-madrid.com)
    const isInRole = await Roles.userIsInRoleAsync(joesUserId, ['manage-team', 'super-admin'], 'real-madrid.com');
    // Scoped roles
    await Roles.addUsersToRolesAsync(joesUserId, ['manage-team'], 'manchester-united.com');
    await Roles.userIsInRoleAsync(joesUserId, 'manage-team', 'manchester-united.com'); // true
    
    // Global roles
    await Roles.addUsersToRolesAsync(joesUserId, 'super-admin', null);
    await Roles.userIsInRoleAsync(joesUserId, ['super-admin'], 'manchester-united.com'); // true
  3. Migrate to meteor-roles v4

    master

    To upgrade to version 4.0, follow these requirements:

    1. Intermediate Upgrade: If you are on a version older than 3.6, you must first upgrade to 3.6 and follow the migration steps for that version. Stay on 3.x until you have run the migration scripts, as they are not available in v4.
    2. Async Conversion: Before upgrading to v4, ensure all server-side role calls use the Async versions of the functions.

    Required Async Functions for Server-side:

    • createRoleAsync
    • deleteRoleAsync
    • renameRoleAsync
    • addRolesToParentAsync
    • removeRolesFromParentAsync
    • addUsersToRolesAsync
    • setUserRolesAsync
    • removeUsersFromRolesAsync
    • userIsInRoleAsync
    • getRolesForUserAsync
    • getUsersInRoleAsync
    • getGroupsForUserAsync
    • getScopesForUserAsync
    • renameScopeAsync
    • removeScopeAsync
    • isParentOfAsync

    Note: The synchronous versions of these functions are still available on the client side.

  4. Implement reusable template-based authorization with Flow Router

    master

    You can implement a modular authorization pattern using 'auth controllers'—reusable, nestable templates designed to handle authorization logic. This pattern allows you to split different sections of your application into separate directories while maintaining a consistent authorization flow.

    In this pattern:

    1. Modular Structure: Application sections are split into distinct directories.
    2. Auth Controllers: Authorization logic is encapsulated in nestable templates.
    3. Centralized Authentication: The application's main layout handles the primary authentication state, while the auth controllers manage route-specific permissions.

    Note: While this example uses flow-router-advanced, the pattern is applicable to iron-router as well.

  5. Migrate to meteor-roles 3.0

    master

    If upgrading from a version older than 2.x, first upgrade to 2.0 using the official migration script.

    To migrate from 2.x to 3.0, note that roles are stored in a new schema. Back up your users collection before proceeding.

    Run the following command in meteor shell to migrate the database to the new schema:

    Package['alanning:roles'].Roles._forwardMigrate2()

    If the migration fails, you can roll back to the old schema using:

    Package['alanning:roles'].Roles._backwardMigrate2()

    Note: Backward migrations take significantly longer than forward migrations.

    meteor shell
    > Package['alanning:roles'].Roles._forwardMigrate2()
  6. Install meteor-roles

    master

    To use meteor-roles, you must first ensure a built-in accounts package (like accounts-password) is installed so that the Meteor.users collection exists. Then, add the roles package and publish role assignments to the client so the client-side can verify roles.

    1. Add an accounts package:
      meteor add accounts-password
    2. Add the roles package:
      meteor add alanning:roles
    3. Publish role assignments in your server code:
      Meteor.publish(null, function () {
        if (this.userId) {
          return Meteor.roleAssignment.find({ 'user._id': this.userId });
        } else {
          this.ready();
        }
      });
    meteor add accounts-password
    meteor add alanning:roles
  7. Configure automatic publication of role assignments in 3.x

    master

    In version 3.0, role assignments were moved from the users collection to a separate collection called role-assignment (accessible via Meteor.roleAssignment). These assignments are not published automatically.

    To allow client-side access to role assignments for the logged-in user, include this publication on the server:

    Meteor.publish(null, function () {
      if (this.userId) {
        return Meteor.roleAssignment.find({ 'user._id': this.userId });
      } else {
        this.ready()
      }
    })
  8. Use the `isInRole` Handlebars helper in Blaze templates

    master

    The Roles package provides an isInRole helper for Blaze templates. This allows you to conditionally show/hide UI elements based on user roles.

    Note: Client-side checks are for UX/latency compensation only. Always enforce security and data restrictions on the server.

    To check for global roles:

    {{#if isInRole 'admin'}}
      {{> admin_nav}}
    {{/if}}

    To check for roles within a specific scope:

    {{#if isInRole 'admin,editor' 'group1'}}
      {{> editor_stuff}}
    {{/if}}
    <!-- Global role check -->
    {{#if isInRole 'admin'}}
      {{> admin_nav}}
    {{/if}}
    
    <!-- Scoped role check -->
    {{#if isInRole 'admin,editor' 'group1'}}
      {{> editor_stuff}}
    {{/if}}
  9. Add users to roles on the server

    master

    When creating users, ensure Roles.addUsersToRolesAsync is called after Accounts.createUserAsync. If the user record does not exist in the database yet, the roles package will fail to associate the roles with the user.

    Example of creating a user and assigning roles:

    import { Roles } from 'meteor/alanning:roles';
    
    // ... inside an async function ...
    const id = await Accounts.createUserAsync({
      email: user.email,
      password: "password",
      profile: { name: user.name }
    });
    
    // Ensure roles exist and then assign them
    for (const role of user.roles) {
      await Roles.createRoleAsync(role, {unlessExists: true});
    }
    
    await Roles.addUsersToRolesAsync(id, user.roles);
  10. Authorize data publishing based on roles and scopes

    master

    Use Roles.userIsInRoleAsync within a Meteor publication to restrict access to sensitive data based on a user's roles and a specific scope.

    import { Roles } from 'meteor/alanning:roles';
    import { check } from 'meteor/check';
    
    Meteor.publish('secrets', async function (scope) {
      check(scope, String);
    
      // Check if user has 'view-secrets' or 'admin' role within the provided scope
      const isInRole = await Roles.userIsInRoleAsync(this.userId, ['view-secrets','admin'], scope);
      
      if (isInRole) {
        return Meteor.secrets.find({scope: scope});
      } else {
        this.stop();
        return;
      }
    });