Merge branch 'devel' into docs/add-meteor-rpc-to-community-packages

This commit is contained in:
Gabriel Grubba
2024-12-13 09:55:38 -03:00
committed by GitHub
10 changed files with 865 additions and 469 deletions

View File

@@ -4,14 +4,14 @@
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// Minimum TypeScript Version: 4.1
import { Mongo } from 'meteor/mongo'
import { Mongo } from "meteor/mongo";
/**
* Provides functions related to user authorization. Compatible with built-in Meteor accounts packages.
*
* @module Roles
*/
declare namespace Roles {
export declare namespace Roles {
/**
* Constant used to reference the special 'global' group that
* can be used to apply blanket permissions across all groups.
@@ -27,7 +27,7 @@ declare namespace Roles {
* @static
* @final
*/
var GLOBAL_GROUP: string
var GLOBAL_GROUP: string;
/**
* Subscription handle for the currently logged in user's permissions.
@@ -42,7 +42,7 @@ declare namespace Roles {
*
* @for Roles
*/
var subscription: Subscription
var subscription: Subscription;
/**
* Add users to roles.
@@ -68,12 +68,12 @@ declare namespace Roles {
users: string | string[] | Meteor.User | Meteor.User[],
roles: string | string[],
options?: string | { scope?: string; ifExists?: boolean }
): void
): void;
function addUsersToRolesAsync(
users: string | string[] | Meteor.User | Meteor.User[],
roles: string | string[],
options?: string | { scope?: string; ifExists?: boolean }
): Promise<void>
): Promise<void>;
/**
* Create a new role.
@@ -84,8 +84,14 @@ declare namespace Roles {
* - `unlessExists`: if `true`, exception will not be thrown in the role already exists
* @return {String} ID of the new role or null.
*/
function createRole(roleName: string, options?: { unlessExists: boolean }): string
function createRoleAsync(roleName: string, options?: { unlessExists: boolean }): Promise<string>
function createRole(
roleName: string,
options?: { unlessExists: boolean }
): string;
function createRoleAsync(
roleName: string,
options?: { unlessExists: boolean }
): Promise<string>;
/**
* Delete an existing role.
@@ -95,8 +101,8 @@ declare namespace Roles {
* @method deleteRole
* @param {String} roleName Name of role.
*/
function deleteRole(roleName: string): void
function deleteRoleAsync(roleName: string): Promise<void>
function deleteRole(roleName: string): void;
function deleteRoleAsync(roleName: string): Promise<void>;
/**
* Rename an existing role.
@@ -105,8 +111,8 @@ declare namespace Roles {
* @param {String} oldName Old name of a role.
* @param {String} newName New name of a role.
*/
function renameRole(oldName: string, newName: string): void
function renameRoleAsync(oldName: string, newName: string): Promise<void>
function renameRole(oldName: string, newName: string): void;
function renameRoleAsync(oldName: string, newName: string): Promise<void>;
/**
* Add role parent to roles.
@@ -118,8 +124,14 @@ declare namespace Roles {
* @param {Array|String} rolesNames Name(s) of role(s).
* @param {String} parentName Name of parent role.
*/
function addRolesToParent(rolesNames: string | string[], parentName: string): void
function addRolesToParentAsync(rolesNames: string | string[], parentName: string): Promise<void>
function addRolesToParent(
rolesNames: string | string[],
parentName: string
): void;
function addRolesToParentAsync(
rolesNames: string | string[],
parentName: string
): Promise<void>;
/**
* Remove role parent from roles.
@@ -131,8 +143,14 @@ declare namespace Roles {
* @param {Array|String} rolesNames Name(s) of role(s).
* @param {String} parentName Name of parent role.
*/
function removeRolesFromParent(rolesNames: string | string[], parentName: string): void
function removeRolesFromParentAsync(rolesNames: string | string[], parentName: string): Promise<void>
function removeRolesFromParent(
rolesNames: string | string[],
parentName: string
): void;
function removeRolesFromParentAsync(
rolesNames: string | string[],
parentName: string
): Promise<void>;
/**
* Retrieve cursor of all existing roles.
@@ -142,7 +160,7 @@ declare namespace Roles {
* through to `Meteor.roles.find(query, options)`.
* @return {Cursor} Cursor of existing roles.
*/
function getAllRoles(queryOptions?: QueryOptions): Mongo.Cursor<Role>
function getAllRoles(queryOptions?: QueryOptions): Mongo.Cursor<Role>;
/**
* Retrieve users groups, if any
@@ -153,8 +171,14 @@ declare namespace Roles {
*
* @return {Array} Array of user's groups, unsorted. Roles.GLOBAL_GROUP will be omitted
*/
function getGroupsForUser(user: string | Meteor.User, role?: string): string[]
function getGroupsForUserAsync(user: string | Meteor.User, role?: string): Promise<string[]>
function getGroupsForUser(
user: string | Meteor.User,
role?: string
): string[];
function getGroupsForUserAsync(
user: string | Meteor.User,
role?: string
): Promise<string[]>;
/**
* Retrieve users scopes, if any.
@@ -165,8 +189,14 @@ declare namespace Roles {
*
* @return {Array} Array of user's scopes, unsorted.
*/
function getScopesForUser(user: string | Meteor.User, roles?: string | string[]): string[]
function getScopesForUserAsync(user: string | Meteor.User, roles?: string | string[]): Promise<string[]>
function getScopesForUser(
user: string | Meteor.User,
roles?: string | string[]
): string[];
function getScopesForUserAsync(
user: string | Meteor.User,
roles?: string | string[]
): Promise<string[]>;
/**
* Rename a scope.
@@ -177,8 +207,8 @@ declare namespace Roles {
* @param {String} oldName Old name of a scope.
* @param {String} newName New name of a scope.
*/
function renameScope(oldName: string, newName: string): void
function renameScopeAsync(oldName: string, newName: string): Promise<void>
function renameScope(oldName: string, newName: string): void;
function renameScopeAsync(oldName: string, newName: string): Promise<void>;
/**
* Remove a scope.
@@ -189,8 +219,8 @@ declare namespace Roles {
* @param {String} name The name of a scope.
*
*/
function removeScope(name: String): void
function removeScopeAsync(name: String): Promise<void>
function removeScope(name: String): void;
function removeScopeAsync(name: String): Promise<void>;
/**
* Find out if a role is an ancestor of another role.
@@ -202,8 +232,11 @@ declare namespace Roles {
* @param {String} childRoleName The role you expect to be among the children of parentRoleName.
* @return {Boolean}
*/
function isParentOf(parentRoleName: string, childRoleName: string): boolean
function isParentOfAsync(parentRoleName: string, childRoleName: string): Promise<boolean>
function isParentOf(parentRoleName: string, childRoleName: string): boolean;
function isParentOfAsync(
parentRoleName: string,
childRoleName: string
): Promise<boolean>;
/**
* Retrieve user's roles.
@@ -222,20 +255,30 @@ declare namespace Roles {
* Alternatively, it can be a scope name string.
* @return {Array} Array of user's roles, unsorted.
*/
function getRolesForUser(user: string | Meteor.User, options?: string | {
scope?: string;
anyScope?: boolean;
onlyScoped?: boolean;
onlyAssigned?: boolean;
fullObjects?: boolean
}): string[]
function getRolesForUserAsync(user: string | Meteor.User, options?: string | {
scope?: string;
anyScope?: boolean;
onlyScoped?: boolean;
onlyAssigned?: boolean;
fullObjects?: boolean
}): Promise<string[]>
function getRolesForUser(
user: string | Meteor.User,
options?:
| string
| {
scope?: string;
anyScope?: boolean;
onlyScoped?: boolean;
onlyAssigned?: boolean;
fullObjects?: boolean;
}
): string[];
function getRolesForUserAsync(
user: string | Meteor.User,
options?:
| string
| {
scope?: string;
anyScope?: boolean;
onlyScoped?: boolean;
onlyAssigned?: boolean;
fullObjects?: boolean;
}
): Promise<string[]>;
/**
* Retrieve all assignments of a user which are for the target role.
@@ -257,11 +300,16 @@ declare namespace Roles {
* Alternatively, it can be a scope name string.
* @return {Cursor} Cursor of user assignments for roles.
*/
function getUserAssignmentsForRole(roles: string | string[], options?: string | {
scope?: string
anyScope?: boolean
queryOptions?: QueryOptions
}): Mongo.Cursor<RoleAssignment>
function getUserAssignmentsForRole(
roles: string | string[],
options?:
| string
| {
scope?: string;
anyScope?: boolean;
queryOptions?: QueryOptions;
}
): Mongo.Cursor<RoleAssignment>;
/**
* Retrieve all users who are in target role.
@@ -288,14 +336,28 @@ declare namespace Roles {
*/
function getUsersInRole(
roles: string | string[],
options?: string | { scope?: string; anyScope?: boolean; onlyScoped?: boolean; queryOptions?: QueryOptions },
options?:
| string
| {
scope?: string;
anyScope?: boolean;
onlyScoped?: boolean;
queryOptions?: QueryOptions;
},
queryOptions?: QueryOptions
): Mongo.Cursor<Meteor.User>
): Mongo.Cursor<Meteor.User>;
function getUsersInRoleAsync(
roles: string | string[],
options?: string | { scope?: string; anyScope?: boolean; onlyScoped?: boolean; queryOptions?: QueryOptions },
options?:
| string
| {
scope?: string;
anyScope?: boolean;
onlyScoped?: boolean;
queryOptions?: QueryOptions;
},
queryOptions?: QueryOptions
): Promise<Mongo.Cursor<Meteor.User>>
): Promise<Mongo.Cursor<Meteor.User>>;
/**
* Remove users from assigned roles.
@@ -318,12 +380,12 @@ declare namespace Roles {
users: string | string[] | Meteor.User | Meteor.User[],
roles?: string | string[],
options?: string | { scope?: string; anyScope?: boolean }
): void
): void;
function removeUsersFromRolesAsync(
users: string | string[] | Meteor.User | Meteor.User[],
roles?: string | string[],
options?: string | { scope?: string; anyScope?: boolean }
): Promise<void>
): Promise<void>;
/**
* Set users' roles.
@@ -349,13 +411,17 @@ declare namespace Roles {
function setUserRoles(
users: string | string[] | Meteor.User | Meteor.User[],
roles: string | string[],
options?: string | { scope?: string; anyScope?: boolean; ifExists?: boolean }
): void
options?:
| string
| { scope?: string; anyScope?: boolean; ifExists?: boolean }
): void;
function setUserRolesAsync(
users: string | string[] | Meteor.User | Meteor.User[],
roles: string | string[],
options?: string | { scope?: string; anyScope?: boolean; ifExists?: boolean }
): Promise<void>
options?:
| string
| { scope?: string; anyScope?: boolean; ifExists?: boolean }
): Promise<void>;
/**
* Check if user has specified roles.
@@ -389,55 +455,55 @@ declare namespace Roles {
user: string | string[] | Meteor.User | Meteor.User[],
roles: string | string[],
options?: string | { scope?: string; anyScope?: boolean }
): boolean
): boolean;
function userIsInRoleAsync(
user: string | string[] | Meteor.User | Meteor.User[],
roles: string | string[],
options?: string | { scope?: string; anyScope?: boolean }
): Promise<boolean>
): Promise<boolean>;
// The schema for the roles collection
interface Role {
_id: string
name: string
children: { _id: string }[]
_id: string;
name: string;
children: { _id: string }[];
}
// The schema for the role-assignment collection
interface RoleAssignment {
_id: string
_id: string;
user: {
_id: string
}
_id: string;
};
role: {
_id: string
}
_id: string;
};
inheritedRoles?: {
_id: string
}[]
scope?: string
_id: string;
}[];
scope?: string;
}
interface QueryOptions {
sort?: Mongo.SortSpecifier | undefined
skip?: number | undefined
limit?: number | undefined
fields?: Mongo.FieldSpecifier | undefined
projection?: Mongo.FieldSpecifier | undefined
reactive?: boolean | undefined
transform?: Function | undefined
sort?: Mongo.SortSpecifier | undefined;
skip?: number | undefined;
limit?: number | undefined;
fields?: Mongo.FieldSpecifier | undefined;
projection?: Mongo.FieldSpecifier | undefined;
reactive?: boolean | undefined;
transform?: Function | undefined;
}
} // module
// Exported collections
declare type RolesCollection = Mongo.Collection<Roles.Role>
declare type RoleAssignmentsCollection = Mongo.Collection<Roles.RoleAssignment>
export declare type RolesCollection = Mongo.Collection<Roles.Role>;
export declare type RoleAssignmentsCollection =
Mongo.Collection<Roles.RoleAssignment>;
// Additions to the Meteor object
declare module 'meteor/meteor' {
namespace Meteor {
const roles: Mongo.Collection<Roles.Role>
const roleAssignment: Mongo.Collection<Roles.RoleAssignment>
declare module "meteor/meteor" {
export namespace Meteor {
const roles: Mongo.Collection<Roles.Role>;
const roleAssignment: Mongo.Collection<Roles.RoleAssignment>;
}
}

View File

@@ -0,0 +1,3 @@
{
"typesEntry": "definitions.d.ts"
}

View File

@@ -15,8 +15,6 @@ Package.onUse(function (api) {
both
);
api.use("zodern:types@1.0.13");
api.use(["blaze@2.9.0 || 3.0.0"], "client", { weak: true });
api.export(["Roles", "RolesCollection", "RoleAssignmentCollection"]);
@@ -25,17 +23,15 @@ Package.onUse(function (api) {
api.addFiles("roles_common_async.js", both);
api.addFiles("roles_server.js", "server");
api.addFiles(["client/debug.js", "client/uiHelpers.js"], "client");
api.addAssets("definitions.d.ts", "server");
api.addAssets("package-types.json", "server");
});
Package.onTest(function (api) {
const both = ["client", "server"];
api.use([
"tinytest",
"ecmascript",
"mongo",
"roles"
], both);
api.use(["tinytest", "ecmascript", "mongo", "roles"], both);
api.addFiles("tests/serverAsync.js", "server");
api.addFiles("tests/client.js", "client");

View File

@@ -24,7 +24,6 @@ import { Mongo } from 'meteor/mongo'
* - `scope`: scope name
* - `inheritedRoles`: A list of all the roles objects inherited by the assigned role.
*
* @module Roles
*/
export const RolesCollection = new Mongo.Collection('roles')
@@ -39,7 +38,7 @@ if (!Meteor.roleAssignment) {
}
/**
* @class Roles
* class Roles
*/
if (typeof Roles === 'undefined') {
Roles = {} // eslint-disable-line no-global-assign
@@ -60,6 +59,10 @@ const asyncSome = async (arr, predicate) => {
return false
}
/**
* @namespace Roles
* @summary The namespace for all Roles types and methods.
*/
Object.assign(Roles, {
/**
* Used as a global group (now scope) name. Not used anymore.
@@ -71,14 +74,13 @@ Object.assign(Roles, {
GLOBAL_GROUP: null,
/**
* Create a new role.
*
* @method createRoleAsync
* @summary Create a new role.
* @memberof Roles
* @locus Anywhere
* @param {String} roleName Name of role.
* @param {Object} [options] Options:
* - `unlessExists`: if `true`, exception will not be thrown in the role already exists
* @return {Promise<String>} ID of the new role or null.
* @static
* - `unlessExists`: if `true`, exception will not be thrown in the role already exists
* @return {Promise<String>} ID of the new role or null
*/
createRoleAsync: async function (roleName, options) {
Roles._checkRoleName(roleName)
@@ -116,14 +118,12 @@ Object.assign(Roles, {
},
/**
* Delete an existing role.
*
* @summary Delete an existing role.
* If the role is set for any user, it is automatically unset.
*
* @method deleteRoleAsync
* @memberof Roles
* @locus Anywhere
* @param {String} roleName Name of role.
* @returns {Promise}
* @static
*/
deleteRoleAsync: async function (roleName) {
let roles
@@ -182,13 +182,12 @@ Object.assign(Roles, {
},
/**
* Rename an existing role.
*
* @method renameRoleAsync
* @summary Rename an existing role.
* @memberof Roles
* @locus Anywhere
* @param {String} oldName Old name of a role.
* @param {String} newName New name of a role.
* @returns {Promise}
* @static
*/
renameRoleAsync: async function (oldName, newName) {
let count
@@ -254,16 +253,12 @@ Object.assign(Roles, {
},
/**
* Add role parent to roles.
*
* Previous parents are kept (role can have multiple parents). For users which have the
* parent role set, new subroles are added automatically.
*
* @method addRolesToParentAsync
* @summary Add role parent to roles.
* @memberof Roles
* @locus Anywhere
* @param {Array|String} rolesNames Name(s) of role(s).
* @param {String} parentName Name of parent role.
* @returns {Promise}
* @static
*/
addRolesToParentAsync: async function (rolesNames, parentName) {
// ensure arrays
@@ -339,16 +334,14 @@ Object.assign(Roles, {
},
/**
* Remove role parent from roles.
*
* @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.
*
* @method removeRolesFromParentAsync
* @param {Array|String} rolesNames Name(s) of role(s).
* @param {String} parentName Name of parent role.
* @returns {Promise}
* @static
* @memberof Roles
* @locus Anywhere
@param {Array|String} rolesNames Name(s) of role(s).
@param {String} parentName Name of parent role.
@returns {Promise}
*/
removeRolesFromParentAsync: async function (rolesNames, parentName) {
// ensure arrays
@@ -431,26 +424,16 @@ Object.assign(Roles, {
},
/**
* Add users to roles.
*
* @summary Add users to roles.
* Adds roles to existing roles for each user.
*
* @example
* Roles.addUsersToRolesAsync(userId, 'admin')
* Roles.addUsersToRolesAsync(userId, ['view-secrets'], 'example.com')
* Roles.addUsersToRolesAsync([user1, user2], ['user','editor'])
* Roles.addUsersToRolesAsync([user1, user2], ['glorious-admin', 'perform-action'], 'example.org')
*
* @method addUsersToRolesAsync
* @memberof Roles
* @locus Anywhere
* @param {Array|String} users User ID(s) or object(s) with an `_id` field.
* @param {Array|String} roles Name(s) of roles to add users to. Roles have to exist.
* @param {Object|String} [options] 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
* @returns {Promise}
*
* Alternatively, it can be a scope name string.
* @static
*/
addUsersToRolesAsync: async function (users, roles, options) {
let id
@@ -487,17 +470,10 @@ Object.assign(Roles, {
},
/**
* Set users' roles.
*
* @summary Set users' roles.
* Replaces all existing roles with a new set of roles.
*
* @example
* await Roles.setUserRolesAsync(userId, 'admin')
* await Roles.setUserRolesAsync(userId, ['view-secrets'], 'example.com')
* await Roles.setUserRolesAsync([user1, user2], ['user','editor'])
* await Roles.setUserRolesAsync([user1, user2], ['glorious-admin', 'perform-action'], 'example.org')
*
* @method setUserRolesAsync
* @memberof Roles
* @locus Anywhere
* @param {Array|String} users User ID(s) or object(s) with an `_id` field.
* @param {Array|String} roles Name(s) of roles to add users to. Roles have to exist.
* @param {Object|String} [options] Options:
@@ -505,9 +481,6 @@ Object.assign(Roles, {
* - `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
* @returns {Promise}
*
* Alternatively, it can be a scope name string.
* @static
*/
setUserRolesAsync: async function (users, roles, options) {
let id
@@ -714,23 +687,15 @@ Object.assign(Roles, {
},
/**
* Remove users from assigned roles.
*
* @example
* await Roles.removeUsersFromRolesAsync(userId, 'admin')
* await Roles.removeUsersFromRolesAsync([userId, user2], ['editor'])
* await Roles.removeUsersFromRolesAsync(userId, ['user'], 'group1')
*
* @method removeUsersFromRolesAsync
* @summary Remove users from assigned roles.
* @memberof Roles
* @locus Anywhere
* @param {Array|String} users User ID(s) or object(s) with an `_id` field.
* @param {Array|String} roles Name(s) of roles to remove users from. Roles have to exist.
* @param {Object|String} [options] 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)
* @returns {Promise}
*
* Alternatively, it can be a scope name string.
* @static
*/
removeUsersFromRolesAsync: async function (users, roles, options) {
if (!users) throw new Error("Missing 'users' param.")
@@ -792,21 +757,9 @@ Object.assign(Roles, {
},
/**
* Check if user has specified roles.
*
* @example
* // global roles
* await Roles.userIsInRoleAsync(user, 'admin')
* await Roles.userIsInRoleAsync(user, ['admin','editor'])
* await Roles.userIsInRoleAsync(userId, 'admin')
* await Roles.userIsInRoleAsync(userId, ['admin','editor'])
*
* // scope roles (global roles are still checked)
* await Roles.userIsInRoleAsync(user, 'admin', 'group1')
* await Roles.userIsInRoleAsync(userId, ['admin','editor'], 'group1')
* await Roles.userIsInRoleAsync(userId, ['admin','editor'], {scope: 'group1'})
*
* @method userIsInRoleAsync
* @summary Check if user has specified roles.
* @memberof Roles
* @locus Anywhere
* @param {String|Object} user User ID or an actual user object.
* @param {Array|String} roles Name of role or an array of roles to check against. If array,
* will return `true` if user is in _any_ role.
@@ -818,7 +771,6 @@ Object.assign(Roles, {
*
* Alternatively, it can be a scope name string.
* @return {Promise<Boolean>} `true` if user is in _any_ of the target roles
* @static
*/
userIsInRoleAsync: async function (user, roles, options) {
let id
@@ -869,9 +821,9 @@ Object.assign(Roles, {
},
/**
* Retrieve user's roles.
*
* @method getRolesForUserAsync
* @summary Retrieve user's roles.
* @memberof Roles
* @locus Anywhere
* @param {String|Object} user User ID or an actual user object.
* @param {Object|String} [options] Options:
* - `scope`: name of scope to provide roles for; if not specified, global roles are returned
@@ -884,7 +836,6 @@ Object.assign(Roles, {
*
* Alternatively, it can be a scope name string.
* @return {Promise<Array>} Array of user's roles, unsorted.
* @static
*/
getRolesForUserAsync: async function (user, options) {
let id
@@ -954,13 +905,133 @@ Object.assign(Roles, {
},
/**
* Retrieve cursor of all existing roles.
*
* @method getAllRoles
* @param {Object} [queryOptions] Options which are passed directly
* through to `RolesCollection.find(query, options)`.
* @summary Retrieve users scopes, if any.
* @memberof Roles
* @locus Anywhere
* @param {String|Object} user User ID or an actual user object.
* @param {Array|String} [roles] Name of roles to restrict scopes to.
* @return {Promise<Array>} Array of user's scopes, unsorted.
*/
getScopesForUserAsync: async function (user, roles) {
let id
if (roles && !Array.isArray(roles)) roles = [roles]
if (user && typeof user === 'object') {
id = user._id
} else {
id = user
}
if (!id) return []
const selector = {
'user._id': id,
scope: { $ne: null }
}
if (roles) {
selector['inheritedRoles._id'] = { $in: roles }
}
const scopes = (
await Meteor.roleAssignment
.find(selector, { fields: { scope: 1 } })
.fetchAsync()
).map((obi) => obi.scope)
return [...new Set(scopes)]
},
/**
* @summary Rename a scope.
* @memberof Roles
* @locus Anywhere
* @param {String} oldName Old name of a scope.
* @param {String} newName New name of a scope.
* @returns {Promise}
*/
renameScopeAsync: async function (oldName, newName) {
let count
Roles._checkScopeName(oldName)
Roles._checkScopeName(newName)
if (oldName === newName) return
do {
count = await Meteor.roleAssignment.updateAsync(
{
scope: oldName
},
{
$set: {
scope: newName
}
},
{ multi: true }
)
} while (count > 0)
},
/**
* @summary Remove a scope and all its role assignments.
* @memberof Roles
* @locus Anywhere
* @param {String} name The name of a scope.
* @returns {Promise}
*/
removeScopeAsync: async function (name) {
Roles._checkScopeName(name)
await Meteor.roleAssignment.removeAsync({ scope: name })
},
/**
* @summary Find out if a role is an ancestor of another role.
* @memberof Roles
* @locus Anywhere
* @param {String} parentRoleName The role you want to research.
* @param {String} childRoleName The role you expect to be among the children of parentRoleName.
* @returns {Promise<Boolean>} True if parent role is ancestor of child role
*/
isParentOfAsync: async function (parentRoleName, childRoleName) {
if (parentRoleName === childRoleName) {
return true
}
if (parentRoleName == null || childRoleName == null) {
return false
}
Roles._checkRoleName(parentRoleName)
Roles._checkRoleName(childRoleName)
let rolesToCheck = [parentRoleName]
while (rolesToCheck.length !== 0) {
const roleName = rolesToCheck.pop()
if (roleName === childRoleName) {
return true
}
const role = await Meteor.roles.findOneAsync({ _id: roleName })
// This should not happen, but this is a problem to address at some other time.
if (!role) continue
rolesToCheck = rolesToCheck.concat(role.children.map((r) => r._id))
}
return false
},
/**
* @summary Retrieve cursor of all existing roles.
* @memberof Roles
* @locus Anywhere
* @param {Object} [queryOptions] Options which are passed directly through to `Meteor.roles.find(query, options)`.
* @return {Cursor} Cursor of existing roles.
* @static
*/
getAllRoles: function (queryOptions) {
queryOptions = queryOptions || { sort: { _id: 1 } }
@@ -969,28 +1040,16 @@ Object.assign(Roles, {
},
/**
* Retrieve all users who are in target role.
*
* Options:
*
* @method getUsersInRoleAsync
* @param {Array|String} roles Name of role or an array of roles. If array, users
* returned will have at least one of the roles
* specified but need not have _all_ roles.
* Roles do not have to exist.
* @summary Retrieve all users who are in target role.
* @memberof Roles
* @locus Anywhere
* @param {Array|String} roles Name of role or an array of roles.
* @param {Object|String} [options] Options:
* - `scope`: name of the scope to restrict roles to; user's global
* roles will also be checked
* - `anyScope`: if set, role can be in any scope (`scope` option is ignored)
* - `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)`
*
* Alternatively, it can be a scope name string.
* @param {Object} [queryOptions] Options which are passed directly
* through to `Meteor.users.find(query, options)`
* - `queryOptions`: options which are passed directly through to `Meteor.users.find(query, options)`
* @return {Promise<Cursor>} Cursor of users in roles.
* @static
*/
getUsersInRoleAsync: async function (roles, options, queryOptions) {
const ids = (
@@ -1004,25 +1063,15 @@ Object.assign(Roles, {
},
/**
* Retrieve all assignments of a user which are for the target role.
*
* Options:
*
* @method getUserAssignmentsForRole
* @param {Array|String} roles Name of role or an array of roles. If array, users
* returned will have at least one of the roles
* specified but need not have _all_ roles.
* Roles do not have to exist.
* @summary Retrieve all assignments of a user which are for the target role.
* @memberof Roles
* @locus Anywhere
* @param {Array|String} roles Name of role or an array of roles.
* @param {Object|String} [options] Options:
* - `scope`: name of the scope to restrict roles to; user's global
* roles will also be checked
* - `anyScope`: if set, role can be in any scope (`scope` option is ignored)
* - `queryOptions`: options which are passed directly
* through to `RoleAssignmentCollection.find(query, options)`
* Alternatively, it can be a scope name string.
* - `scope`: name of the scope to restrict roles to
* - `anyScope`: if set, role can be in any scope
* - `queryOptions`: options which are passed directly through to `RoleAssignmentCollection.find(query, options)`
* @return {Cursor} Cursor of user assignments for roles.
* @static
*/
getUserAssignmentsForRole: function (roles, options) {
options = Roles._normalizeOptions(options)
@@ -1114,157 +1163,6 @@ Object.assign(Roles, {
return await Roles.getScopesForUser(...args)
},
/**
* Retrieve users scopes, if any.
*
* @method getScopesForUserAsync
* @param {String|Object} user User ID or an actual user object.
* @param {Array|String} [roles] Name of roles to restrict scopes to.
*
* @return {Promise<Array>} Array of user's scopes, unsorted.
* @static
*/
getScopesForUserAsync: async function (user, roles) {
let id
if (roles && !Array.isArray(roles)) roles = [roles]
if (user && typeof user === 'object') {
id = user._id
} else {
id = user
}
if (!id) return []
const selector = {
'user._id': id,
scope: { $ne: null }
}
if (roles) {
selector['inheritedRoles._id'] = { $in: roles }
}
const scopes = (
await Meteor.roleAssignment
.find(selector, { fields: { scope: 1 } })
.fetchAsync()
).map((obi) => obi.scope)
return [...new Set(scopes)]
},
/**
* Rename a scope.
*
* Roles assigned with a given scope are changed to be under the new scope.
*
* @method renameScopeAsync
* @param {String} oldName Old name of a scope.
* @param {String} newName New name of a scope.
* @returns {Promise}
* @static
*/
renameScopeAsync: async function (oldName, newName) {
let count
Roles._checkScopeName(oldName)
Roles._checkScopeName(newName)
if (oldName === newName) return
do {
count = await Meteor.roleAssignment.updateAsync(
{
scope: oldName
},
{
$set: {
scope: newName
}
},
{ multi: true }
)
} while (count > 0)
},
/**
* Remove a scope.
*
* Roles assigned with a given scope are removed.
*
* @method removeScopeAsync
* @param {String} name The name of a scope.
* @returns {Promise}
* @static
*/
removeScopeAsync: async function (name) {
Roles._checkScopeName(name)
await Meteor.roleAssignment.removeAsync({ scope: name })
},
/**
* Throw an exception if `roleName` is an invalid role name.
*
* @method _checkRoleName
* @param {String} roleName A role name to match against.
* @private
* @static
*/
_checkRoleName: function (roleName) {
if (
!roleName ||
typeof roleName !== 'string' ||
roleName.trim() !== roleName
) {
throw new Error(`Invalid role name '${roleName}'.`)
}
},
/**
* Find out if a role is an ancestor of another role.
*
* WARNING: If you check this on the client, please make sure all roles are published.
*
* @method isParentOfAsync
* @param {String} parentRoleName The role you want to research.
* @param {String} childRoleName The role you expect to be among the children of parentRoleName.
* @returns {Promise}
* @static
*/
isParentOfAsync: async function (parentRoleName, childRoleName) {
if (parentRoleName === childRoleName) {
return true
}
if (parentRoleName == null || childRoleName == null) {
return false
}
Roles._checkRoleName(parentRoleName)
Roles._checkRoleName(childRoleName)
let rolesToCheck = [parentRoleName]
while (rolesToCheck.length !== 0) {
const roleName = rolesToCheck.pop()
if (roleName === childRoleName) {
return true
}
const role = await Meteor.roles.findOneAsync({ _id: roleName })
// This should not happen, but this is a problem to address at some other time.
if (!role) continue
rolesToCheck = rolesToCheck.concat(role.children.map((r) => r._id))
}
return false
},
/**
* Normalize options.
*

View File

@@ -57,23 +57,23 @@ const sameMembers = (test, value, expected) => {
);
};
const sameDeepMembers = (test, value, expected) => {
// Helper to sort object keys recursively
const sortObjectKeys = (obj) => {
if (Array.isArray(obj)) {
return obj.map(sortObjectKeys);
}
if (obj && typeof obj === "object") {
return Object.keys(obj)
.sort()
.reduce((sorted, key) => {
sorted[key] = sortObjectKeys(obj[key]);
return sorted;
}, {});
}
return obj;
};
// Helper to sort object keys recursively
const sortObjectKeys = (obj) => {
if (Array.isArray(obj)) {
return obj.map(sortObjectKeys);
}
if (obj && typeof obj === "object") {
return Object.keys(obj)
.sort()
.reduce((sorted, key) => {
sorted[key] = sortObjectKeys(obj[key]);
return sorted;
}, {});
}
return obj;
};
const sameDeepMembers = (test, value, expected) => {
const sortedValue = sortObjectKeys(value);
const sortedExpected = sortObjectKeys(expected);
@@ -84,6 +84,16 @@ const sameDeepMembers = (test, value, expected) => {
);
};
const sameDeepUnorderedMembers = (test, value, expected) => {
const sortAndStringify = (arr) => {
return JSON.stringify(arr.map(item => JSON.stringify(sortObjectKeys(item))).sort());
};
const sortedValue = sortAndStringify(value);
const sortedExpected = sortAndStringify(expected);
test.equal(sortedValue, sortedExpected, 'Arrays should have the same elements, regardless of order');
};
const hasProp = (target, prop) => Object.hasOwnProperty.call(target, prop);
let users = {};
@@ -1885,7 +1895,7 @@ Tinytest.addAsync("roles -keep assigned roles", async function (test) {
anyScope: true,
fullObjects: true,
});
sameDeepMembers(
sameDeepUnorderedMembers(
test,
rolesForUser.map((obj) => {
delete obj._id;
@@ -1914,7 +1924,7 @@ Tinytest.addAsync("roles -keep assigned roles", async function (test) {
anyScope: true,
fullObjects: true,
});
sameDeepMembers(
sameDeepUnorderedMembers(
test,
rolesForUser2.map((obj) => {
delete obj._id;
@@ -1949,7 +1959,7 @@ Tinytest.addAsync("roles -keep assigned roles", async function (test) {
anyScope: true,
fullObjects: true,
});
sameDeepMembers(
sameDeepUnorderedMembers(
test,
rolesForUser3.map((obj) => {
delete obj._id;
@@ -1973,7 +1983,7 @@ Tinytest.addAsync("roles -keep assigned roles", async function (test) {
anyScope: true,
fullObjects: true,
});
sameDeepMembers(
sameDeepUnorderedMembers(
test,
rolesForUser4.map((obj) => {
delete obj._id;
@@ -2063,7 +2073,7 @@ Tinytest.addAsync(
anyScope: true,
fullObjects: true,
});
sameDeepMembers(
sameDeepUnorderedMembers(
test,
usersRoles.map((obj) => {
delete obj._id;
@@ -2109,7 +2119,7 @@ Tinytest.addAsync(
anyScope: true,
fullObjects: true,
});
sameDeepMembers(
sameDeepUnorderedMembers(
test,
usersRoles2.map((obj) => {
delete obj._id;
@@ -2153,7 +2163,7 @@ Tinytest.addAsync(
anyScope: true,
fullObjects: true,
});
sameDeepMembers(
sameDeepUnorderedMembers(
test,
usersRoles3.map((obj) => {
delete obj._id;
@@ -2203,7 +2213,7 @@ Tinytest.addAsync(
anyScope: true,
fullObjects: true,
});
sameDeepMembers(
sameDeepUnorderedMembers(
test,
usersRoles4.map((obj) => {
delete obj._id;
@@ -2255,7 +2265,7 @@ Tinytest.addAsync(
anyScope: true,
fullObjects: true,
});
sameDeepMembers(
sameDeepUnorderedMembers(
test,
usersRoles5.map((obj) => {
delete obj._id;
@@ -2308,7 +2318,7 @@ Tinytest.addAsync(
anyScope: true,
fullObjects: true,
});
sameDeepMembers(
sameDeepUnorderedMembers(
test,
usersRoles6.map((obj) => {
delete obj._id;

View File

@@ -322,6 +322,10 @@ export default defineConfig({
text: "hot-module-replacement",
link: "/packages/hot-module-replacement",
},
{
text: "roles",
link: "/packages/roles",
},
{
text: "less",
link: "/packages/less",
@@ -374,22 +378,13 @@ export default defineConfig({
link: "/packages/packages-listing",
text: "Maintained Packages",
},
{
link: "packages/community-packages",
text: "Community Packages",
},
],
collapsed: true,
},
{
text: "Community Packages",
link: "/community-packages/index",
items: [
{
text: "Meteor RPC",
link: "/community-packages/meteor-rpc",
},
],
items: [],
collapsed: true,
},
{
@@ -400,10 +395,6 @@ export default defineConfig({
link: "/troubleshooting/expired-certificate",
},
{ text: "Windows", link: "/troubleshooting/windows" },
{
text: "Known issues in 2.13",
link: "/troubleshooting/known-issues",
},
],
collapsed: true,
},

View File

@@ -1,10 +1,18 @@
# Community Packages
::: tip
The Meteor community is making great efforts to migrate popular packages to Meteor 3.0.
A [spreadsheet maintained](https://docs.google.com/spreadsheets/u/0/d/1JbUZmJab3owZ9LV71Ubto32YX_QWQljRypJTOQupxL8/htmlview) by [@harryadel](https://github.com/harryadel) tracks the status of your favorite packages and offers opportunities to help.
:::
There are some very popular community packages that do not have a documentation website or only have a readme file.
This section tries to list and add some information about usage, configuration, and examples for these packages.
> Think this section as an `Awesome List`, similar to [`awesome-node`](https://github.com/sindresorhus/awesome-nodejs) or [`awesome-react`](https://github.com/enaqx/awesome-react)
Many packages have been consolidated into the [Meteor Community organization](https://github.com/Meteor-Community-Packages). Others are maintained by individual developers or companies.
If you use or have a package that you think would be useful to add to this list, please open a pull request.
Please bear in mind if you are adding a package to this list, try providing as much information as possible, including:

View File

@@ -1,17 +0,0 @@
[//]: # (Do not edit this file by hand.)
[//]: # (This is a generated file.)
[//]: # (If you want to change something in this file)
[//]: # (go to meteor/docs/generators/packages-listing)
# Community Packages
The Meteor community is making great efforts to migrate popular packages to Meteor 3.0.
Many packages have been consolidated into the [Meteor Community organization](https://github.com/Meteor-Community-Packages). Others are maintained by individual developers or companies.
A [spreadsheet maintained](https://docs.google.com/spreadsheets/u/0/d/1JbUZmJab3owZ9LV71Ubto32YX_QWQljRypJTOQupxL8/htmlview) by [@harryadel](https://github.com/harryadel) tracks the status of your favorite packages and offers opportunities to help.

View File

@@ -0,0 +1,480 @@
# Roles
Authorization package for Meteor - compatible with built-in accounts package.
> 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
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
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.
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
<ApiBox name="Roles.createRoleAsync" hasCustomExample/>
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
<ApiBox name="Roles.addRolesToParentAsync" hasCustomExample />
Example:
```js
// Make 'editor' a child role of 'admin'
await Roles.addRolesToParentAsync("editor", "admin");
// Add multiple child roles
await Roles.addRolesToParentAsync(["editor", "moderator"], "admin");
```
<ApiBox name="Roles.removeRolesFromParentAsync" hasCustomExample />
Example:
```js
// Remove 'editor' as child role of 'admin'
await Roles.removeRolesFromParentAsync("editor", "admin");
```
<ApiBox name="Roles.deleteRoleAsync" hasCustomExample />
Example:
```js
// Delete role and all its assignments
await Roles.deleteRoleAsync("temp-role");
```
<ApiBox name="Roles.renameRoleAsync" hasCustomExample />
Example:
```js
// Rename an existing role
await Roles.renameRoleAsync("editor", "content-editor");
```
### Assigning Roles
<ApiBox name="Roles.addUsersToRolesAsync" hasCustomExample />
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"]);
```
<ApiBox name="Roles.setUserRolesAsync" hasCustomExample />
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");
```
<ApiBox name="Roles.removeUsersFromRolesAsync" hasCustomExample />
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"]);
```
<ApiBox name="Roles.renameScopeAsync" hasCustomExample />
Example:
```js
// Rename a scope
await Roles.renameScopeAsync("department-1", "marketing");
```
<ApiBox name="Roles.removeScopeAsync" hasCustomExample />
Example:
```js
// Remove a scope and all its role assignments
await Roles.removeScopeAsync("old-department");
```
<ApiBox name="Roles.getAllRoles" hasCustomExample />
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 },
});
```
<ApiBox name="Roles.getUsersInRoleAsync" hasCustomExample />
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
<ApiBox name="Roles.userIsInRoleAsync" hasCustomExample />
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,
});
```
<ApiBox name="Roles.getRolesForUserAsync" hasCustomExample />
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,
});
```
<ApiBox name="Roles.isParentOfAsync" hasCustomExample />
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");
```
<ApiBox name="Roles.getScopesForUserAsync" hasCustomExample />
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();
});
```
## Client only APIs
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
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
If you are currently using the `alanning:roles` package, follow these steps to migrate to the core version:
1. 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. 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);
},
});
```

View File

@@ -1,39 +0,0 @@
# Known issues in 2.13
Troubleshooting in Meteor 2.13
## Cannot extract version of meteor tool {#cannot-extract-meteor-tool}
For some users, the `meteor update` to version 2.13 command may fail with the following error or similar:
```shell
Error: incorrect data check
at Zlib.zlibOnError [as onerror] (zlib.js:187:17)
=> awaited here:
...
at /tools/cli/main.js:1165:7 {
errno: -3,
code: 'Z_DATA_ERROR'
}
```
## The issue {#the-issue}
It seems related to [our first ESM version of Node.js v14.21.4](https://github.com/meteor/node-v14-esm) and the `zlib` package.
We have been able to reproduce this issue only in Mac Intel.
You can follow along with the [GitHub issue](https://github.com/meteor/meteor/issues/12731) for updates.
## Solution {#solution}
The solution for this issue is running the following command in your terminal:
```shell
curl https://install.meteor.com/\?release\=2.13.3 | sh
```