Skip to content

角色

¥Roles

Meteor 授权包 - 与内置 accounts 包兼容。

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

自 Meteor 3.1.0 起可用(以前称为 alanning:roles)

¥Available since Meteor 3.1.0 (previously alanning:roles)

安装

¥Installation

要向你的应用添加角色,请在终端中运行以下命令:

¥To add roles to your application, run this command in your terminal:

bash
meteor add roles

概述

¥Overview

roles 包允许你将角色附加到用户,然后在决定是否授予 Meteor 方法的访问权限或发布数据时对照这些角色进行检查。核心概念很简单 - 你为用户创建角色分配,然后稍后验证这些角色。此软件包提供了辅助方法,使添加、删除和验证角色的过程更加轻松。

¥The roles package lets you attach roles to users and then check against those roles when deciding whether to grant access to Meteor methods or publish data. The core concept is simple - you create role assignments for users and then verify those roles later. This package provides helper methods to make the process of adding, removing, and verifying roles easier.

概念

¥Concepts

角色与权限

¥Roles vs Permissions

虽然名为 "roles",但你可以根据需要定义角色、范围或权限。它们本质上是分配给用户的标签,你可以稍后检查。

¥Although named "roles", you can define your roles, scopes or permissions however you like. They are essentially tags assigned to users that you can check later.

你可以拥有像 adminwebmaster 这样的传统角色,也可以拥有像 view-secretsusers.viewusers.manage 这样的更细粒度的权限。通常,更细粒度的权限更好,因为它们可以处理边缘情况而无需创建许多更高级别的角色。

¥You can have traditional roles like admin or webmaster, or more granular permissions like view-secrets, users.view, or users.manage. Often, more granular permissions are better as they handle edge cases without creating many higher-level roles.

角色层级

¥Role Hierarchy

角色可以按层次结构组织:

¥Roles can be organized in a hierarchy:

  • 角色可以有多个父角色和子角色(子角色)

    ¥Roles can have multiple parents and children (subroles)

  • 如果为用户分配了父角色,则其所有后代角色也适用。

    ¥If a parent role is assigned to a user, all its descendant roles also apply

  • 这允许创建聚合权限的 "超级角色"。

    ¥This allows creating "super roles" that aggregate permissions

层次结构设置示例:

¥Example hierarchy setup:

js
import { Roles } from "meteor/roles";

// Create base roles
await Roles.createRoleAsync("user");
await Roles.createRoleAsync("admin");

// Create permission roles
await Roles.createRoleAsync("USERS_VIEW");
await Roles.createRoleAsync("POST_EDIT");

// Set up hierarchy
await Roles.addRolesToParentAsync("USERS_VIEW", "admin");
await Roles.addRolesToParentAsync("POST_EDIT", "admin");
await Roles.addRolesToParentAsync("POST_EDIT", "user");

范围

¥Scopes

作用域允许用户拥有独立的角色集。用例包括:

¥Scopes allow users to have independent sets of roles. Use cases include:

  • 应用内的不同社区

    ¥Different communities within your app

  • 多租户应用中的多个租户

    ¥Multiple tenants in a multi-tenant application

  • 不同的资源组

    ¥Different resource groups

用户可以同时拥有作用域角色和全局角色:

¥Users can have both scoped roles and global roles:

  • 全局角色适用于所有范围

    ¥Global roles apply across all scopes

  • 作用域角色仅在其特定作用域内有效

    ¥Scoped roles only apply within their specific scope

  • 作用域彼此独立

    ¥Scopes are independent of each other

使用范围的示例:

¥Example using scopes:

js
// Assign scoped roles
await Roles.addUsersToRolesAsync(userId, ["manage-team"], "team-a");
await Roles.addUsersToRolesAsync(userId, ["player"], "team-b");

// Check scoped roles
await Roles.userIsInRoleAsync(userId, "manage-team", "team-a"); // true
await Roles.userIsInRoleAsync(userId, "manage-team", "team-b"); // false

// Assign global role
await Roles.addUsersToRolesAsync(userId, "super-admin", null);

// Global roles work in all scopes
await Roles.userIsInRoleAsync(userId, ["manage-team", "super-admin"], "team-b"); // true

角色管理

¥Role Management

Roles.createRoleAsync

Summary:

Create a new role.

Arguments:

Source code
NameTypeDescriptionRequired
roleNameString

Name of role.

Yes
optionsObject

Options:

  • unlessExists: if true, exception will not be thrown in the role already exists
No

示例:

¥Example:

js
import { Roles } from "meteor/roles";

// Create a new role
await Roles.createRoleAsync("admin");

// Create if doesn't exist
await Roles.createRoleAsync("editor", { unlessExists: true });

修改角色

¥Modifying Roles

Roles.addRolesToParentAsync

Summary:

Add role parent to roles.

Arguments:

Source code
NameTypeDescriptionRequired
rolesNamesArray or String

Name(s) of role(s).

Yes
parentNameString

Name of parent role.

Yes

示例:

¥Example:

js
// Make 'editor' a child role of 'admin'
await Roles.addRolesToParentAsync("editor", "admin");

// Add multiple child roles
await Roles.addRolesToParentAsync(["editor", "moderator"], "admin");

Roles.removeRolesFromParentAsync

Summary:

Remove role parent from roles. Other parents are kept (role can have multiple parents). For users which have the parent role set, removed subrole is removed automatically.

Arguments:

Source code
NameTypeDescriptionRequired
rolesNamesArray or String

Name(s) of role(s).

Yes
parentNameString

Name of parent role.

Yes

示例:

¥Example:

js
// Remove 'editor' as child role of 'admin'
await Roles.removeRolesFromParentAsync("editor", "admin");

Roles.deleteRoleAsync

Summary:

Delete an existing role. If the role is set for any user, it is automatically unset.

Arguments:

Source code
NameTypeDescriptionRequired
roleNameString

Name of role.

Yes

示例:

¥Example:

js
// Delete role and all its assignments
await Roles.deleteRoleAsync("temp-role");

Roles.renameRoleAsync

Summary:

Rename an existing role.

Arguments:

Source code
NameTypeDescriptionRequired
oldNameString

Old name of a role.

Yes
newNameString

New name of a role.

Yes

示例:

¥Example:

js
// Rename an existing role
await Roles.renameRoleAsync("editor", "content-editor");

分配角色

¥Assigning Roles

Roles.addUsersToRolesAsync

Summary:

Add users to roles. Adds roles to existing roles for each user.

Arguments:

Source code
NameTypeDescriptionRequired
usersArray or String

User ID(s) or object(s) with an _id field.

Yes
rolesArray or String

Name(s) of roles to add users to. Roles have to exist.

Yes
optionsObject or String

Options:

  • scope: name of the scope, or null for the global role
  • ifExists: if true, do not throw an exception if the role does not exist
No

示例:

¥Example:

js
// Add global roles
await Roles.addUsersToRolesAsync(userId, ["admin", "editor"]);

// Add scoped roles
await Roles.addUsersToRolesAsync(userId, ["manager"], "department-a");

// Add roles to multiple users
await Roles.addUsersToRolesAsync([user1Id, user2Id], ["user"]);

Roles.setUserRolesAsync

Summary:

Set users' roles. Replaces all existing roles with a new set of roles.

Arguments:

Source code
NameTypeDescriptionRequired
usersArray or String

User ID(s) or object(s) with an _id field.

Yes
rolesArray or String

Name(s) of roles to add users to. Roles have to exist.

Yes
optionsObject or String

Options:

  • scope: name of the scope, or null for the global role
  • anyScope: if true, remove all roles the user has, of any scope, if false, only the one in the same scope
  • ifExists: if true, do not throw an exception if the role does not exist
No

示例:

¥Example:

js
// Replace user's global roles
await Roles.setUserRolesAsync(userId, ["editor"]);

// Replace scoped roles
await Roles.setUserRolesAsync(userId, ["viewer"], "project-x");

// Clear all roles in scope
await Roles.setUserRolesAsync(userId, [], "project-x");

Roles.removeUsersFromRolesAsync

Summary:

Remove users from assigned roles.

Arguments:

Source code
NameTypeDescriptionRequired
usersArray or String

User ID(s) or object(s) with an _id field.

Yes
rolesArray or String

Name(s) of roles to remove users from. Roles have to exist.

Yes
optionsObject or String

Options:

  • scope: name of the scope, or null for the global role
  • anyScope: if set, role can be in any scope (scope option is ignored)
No

示例:

¥Example:

js
// Remove global roles
await Roles.removeUsersFromRolesAsync(userId, ["admin"]);

// Remove scoped roles
await Roles.removeUsersFromRolesAsync(userId, ["manager"], "department-a");

// Remove roles from multiple users
await Roles.removeUsersFromRolesAsync([user1Id, user2Id], ["temp-role"]);

Roles.renameScopeAsync

Summary:

Rename a scope.

Arguments:

Source code
NameTypeDescriptionRequired
oldNameString

Old name of a scope.

Yes
newNameString

New name of a scope.

Yes

示例:

¥Example:

js
// Rename a scope
await Roles.renameScopeAsync("department-1", "marketing");

Roles.removeScopeAsync

Summary:

Remove a scope and all its role assignments.

Arguments:

Source code
NameTypeDescriptionRequired
nameString

The name of a scope.

Yes

示例:

¥Example:

js
// Remove a scope and all its role assignments
await Roles.removeScopeAsync("old-department");

Roles.getAllRoles

Summary:

Retrieve cursor of all existing roles.

Arguments:

Source code
NameTypeDescriptionRequired
queryOptionsObject

Options which are passed directly through to Meteor.roles.find(query, options).

No

示例:

¥Example:

js
// Get all roles sorted by name
const roles = Roles.getAllRoles({ sort: { _id: 1 } });

// Get roles with custom query
const customRoles = Roles.getAllRoles({
  fields: { _id: 1, children: 1 },
  sort: { _id: -1 },
});

Roles.getUsersInRoleAsync

Summary:

Retrieve all users who are in target role.

Arguments:

Source code
NameTypeDescriptionRequired
rolesArray or String

Name of role or an array of roles.

Yes
optionsObject or String

Options:

  • scope: name of the scope to restrict roles to
  • anyScope: if set, role can be in any scope
  • onlyScoped: if set, only roles in the specified scope are returned
  • queryOptions: options which are passed directly through to Meteor.users.find(query, options)
No

示例:

¥Example:

js
// Find all admin users
const adminUsers = await Roles.getUsersInRoleAsync("admin");

// Find users with specific roles in a scope
const scopedUsers = await Roles.getUsersInRoleAsync(
  ["editor", "writer"],
  "blog"
);

// Find users with custom options
const users = await Roles.getUsersInRoleAsync("manager", {
  scope: "department-a",
  queryOptions: {
    sort: { createdAt: -1 },
    limit: 10,
  },
});

检查角色

¥Checking Roles

Roles.userIsInRoleAsync

Summary:

Check if user has specified roles.

Arguments:

Source code
NameTypeDescriptionRequired
userString or Object

User ID or an actual user object.

Yes
rolesArray or String

Name of role or an array of roles to check against. If array, will return true if user is in any role. Roles do not have to exist.

Yes
optionsObject or String

Options:

  • scope: name of the scope; if supplied, limits check to just that scope the user's global roles will always be checked whether scope is specified or not
  • anyScope: if set, role can be in any scope (scope option is ignored)

Alternatively, it can be a scope name string.

No

示例:

¥Example:

js
// Check global role
const isAdmin = await Roles.userIsInRoleAsync(userId, "admin");

// Check any of multiple roles
const canEdit = await Roles.userIsInRoleAsync(userId, ["editor", "admin"]);

// Check scoped role
const isManager = await Roles.userIsInRoleAsync(
  userId,
  "manager",
  "department-a"
);

// Check role in any scope
const hasRole = await Roles.userIsInRoleAsync(userId, "viewer", {
  anyScope: true,
});

Roles.getRolesForUserAsync

Summary:

Retrieve user's roles.

Arguments:

Source code
NameTypeDescriptionRequired
userString or Object

User ID or an actual user object.

Yes
optionsObject or String

Options:

  • scope: name of scope to provide roles for; if not specified, global roles are returned
  • anyScope: if set, role can be in any scope (scope and onlyAssigned options are ignored)
  • onlyScoped: if set, only roles in the specified scope are returned
  • onlyAssigned: return only assigned roles and not automatically inferred (like subroles)
  • fullObjects: return full roles objects (true) or just names (false) (onlyAssigned option is ignored) (default false) If you have a use-case for this option, please file a feature-request. You shouldn't need to use it as it's result strongly dependent on the internal data structure of this plugin.

Alternatively, it can be a scope name string.

No

示例:

¥Example:

js
// Get user's global roles
const globalRoles = await Roles.getRolesForUserAsync(userId);

// Get scoped roles
const deptRoles = await Roles.getRolesForUserAsync(userId, "department-a");

// Get all roles including inherited
const allRoles = await Roles.getRolesForUserAsync(userId, {
  anyScope: true,
  fullObjects: true,
});

Roles.isParentOfAsync

Summary:

Find out if a role is an ancestor of another role.

Arguments:

Source code
NameTypeDescriptionRequired
parentRoleNameString

The role you want to research.

Yes
childRoleNameString

The role you expect to be among the children of parentRoleName.

Yes

示例:

¥Example:

js
// Check if admin is a parent of editor
const isParent = await Roles.isParentOfAsync("admin", "editor");

// Can be used to check inheritance chains
const hasPermission = await Roles.isParentOfAsync("super-admin", "post-edit");

Roles.getScopesForUserAsync

Summary:

Retrieve users scopes, if any.

Arguments:

Source code
NameTypeDescriptionRequired
userString or Object

User ID or an actual user object.

Yes
rolesArray or String

Name of roles to restrict scopes to.

No

示例:

¥Example:

js
// Get all scopes for user
const allScopes = await Roles.getScopesForUserAsync(userId);

// Get scopes where user has specific roles
const editorScopes = await Roles.getScopesForUserAsync(userId, ["editor"]);

发布角色

¥Publishing Roles

角色分配需要发布才能在客户端使用。发布示例:

¥Role assignments need to be published to be available on the client. Example publication:

js
// Publish user's own roles
Meteor.publish(null, function () {
  if (this.userId) {
    return Meteor.roleAssignment.find({ "user._id": this.userId });
  }
  this.ready();
});

// Publish roles for specific scope
Meteor.publish("scopeRoles", function (scope) {
  if (this.userId) {
    return Meteor.roleAssignment.find({ scope: scope });
  }
  this.ready();
});

仅限客户端 API

¥Client only APIs

在客户端,除了异步方法之外,你还可以使用 sync 版本的函数:

¥On the client alongside the async methods, you can use the sync versions of the functions:

  • Roles.userIsInRole(userId, roles, scope)

  • Roles.getRolesForUser(userId, scope)

  • Roles.getScopesForUser(userId)

  • Roles.isParentOf(parent, child)

  • Roles.getUsersInRole(role, scope)

  • Roles.getAllRoles(options)

  • Roles.createRole(role, options)

  • Roles.addUsersToRoles(userId, roles, scope)

  • Roles.setUserRoles(userId, roles, scope)

  • Roles.removeUsersFromRoles(userId, roles, scope)

  • Roles.addRolesToParent(child, parent)

  • Roles.removeRolesFromParent(child, parent)

  • Roles.deleteRole(role)

  • Roles.renameRole(oldRole, newRole)

  • Roles.renameScope(oldScope, newScope)

  • Roles.removeScope(scope)

与模板一起使用

¥Using with Templates

roles 包自动为模板提供 isInRole 助手:

¥The roles package automatically provides an isInRole helper for templates:

handlebars


{{#if isInRole "admin"}}
  <div class="admin-panel">
    <!-- Admin only content -->
  </div>
{{/if}}





{{#if isInRole "editor,writer" "blog"}}
  <div class="editor-tools">
    <!-- Blog editor tools -->
  </div>
{{/if}}

迁移到核心版本

¥Migration to Core Version

如果你当前正在使用 alanning:roles 包,请按照以下步骤迁移到核心版本:

¥If you are currently using the alanning:roles package, follow these steps to migrate to the core version:

  1. 首先确保你使用的是 alanning:roles 3.6 版本。

    ¥Make sure you are on version 3.6 of alanning:roles first

  2. 运行所有待处理的旧版本迁移

    ¥Run any pending migrations from previous versions

  3. 将所有服务器端角色操作切换为使用以下函数的异步版本:

    ¥Switch all server-side role operations to use the async versions of the functions:

    • createRoleAsync

    • deleteRoleAsync

    • addUsersToRolesAsync

    • setUserRolesAsync

    • removeUsersFromRolesAsync

    • 等等

      ¥etc.

  4. 移除 alanning:roles 软件包:

    ¥Remove alanning:roles package:

    bash
    meteor remove alanning:roles
  5. 添加核心角色包:

    ¥Add the core roles package:

    bash
    meteor add roles
  6. 更新导入以使用新软件包:

    ¥Update imports to use the new package:

    js
    import { Roles } from "meteor/roles";

这些函数的同步版本在客户端仍然可用。

¥The sync versions of these functions are still available on the client.

安全注意事项

¥Security Considerations

  1. 客户端角色检查仅供参考 - 始终在服务器上验证权限

    ¥Client-side role checks are for convenience only - always verify permissions on the server

  2. 仅发布用户需要的角色数据

    ¥Publish only the role data that users need

  3. 使用作用域正确隔离角色分配

    ¥Use scopes to properly isolate role assignments

  4. 验证角色名称和作用域以防止注入攻击

    ¥Validate role names and scopes to prevent injection attacks

  5. 考虑使用更细粒度的权限,而不是广泛的角色分配

    ¥Consider using more granular permissions over broad role assignments

示例用法

¥Example Usage

方法安全性

¥Method Security

js
// server/methods.js
Meteor.methods({
  deletePost: async function (postId) {
    check(postId, String);

    const canDelete = await Roles.userIsInRoleAsync(
      this.userId,
      ["admin", "moderator"],
      "posts"
    );

    if (!canDelete) {
      throw new Meteor.Error("unauthorized", "Not authorized to delete posts");
    }

    Posts.remove(postId);
  },
});

发布安全性

¥Publication Security

js
// server/publications.js
Meteor.publish("secretDocuments", async function (scope) {
  check(scope, String);

  const canView = await Roles.userIsInRoleAsync(
    this.userId,
    ["view-secrets", "admin"],
    scope
  );

  if (canView) {
    return SecretDocs.find({ scope: scope });
  }

  this.ready();
});

用户管理

¥User Management

js
// server/users.js
Meteor.methods({
  promoteToEditor: async function (userId, scope) {
    check(userId, String);
    check(scope, String);

    const canPromote = await Roles.userIsInRoleAsync(
      this.userId,
      "admin",
      scope
    );

    if (!canPromote) {
      throw new Meteor.Error("unauthorized");
    }

    await Roles.addUsersToRolesAsync(userId, ["editor"], scope);
  },
});