permissions clean up & refactoring

This commit is contained in:
Sacha Greif 2016-07-20 10:25:05 +09:00
parent 3b5d4ea1ab
commit c748bdc5bd
23 changed files with 182 additions and 244 deletions

View file

@ -31,7 +31,7 @@ Note that both versions use the same data format, so you can go back and forth b
- [Forms](#forms)
- [Methods](#methods)
- [Routes](#routes)
- [Groups](#routes)
- [Groups](#groups-permissions)
- [Internationalization](#internationalization)
- [Cheatsheet](#cheatsheet)
@ -485,16 +485,39 @@ Telescope.routes.add({
});
```
## Groups
## Groups & Permissions
User groups let you give your users permission to perform specific actions.
### Creating A Group
To test if a user can perform an action, we don't check if they belong to a specific group (e.g. `user.isAdmin === true`), but instead if *at least one of the groups they belong to* has the rights to perform the current action.
### Permissions API
```js
Users.createGroup("mods"); // let's create a new "mods" group
Users.createGroup(groupName); // create a new group
Users.methods.addGroup(userId, groupName); // add a user to a group (server only)
Users.getGroups(user); // get a list of all the groups a user belongs to
Users.getActions(user); // get a list of all the actions a user can perform
Users.canDo(user, action); // check if a user can perform a specific action
Users.canView(user, document); // shortcut to check if a user can view a specific document
Users.canEdit(user, document); // shortcut to check if a user can edit a specific document
```
Documents can be Posts, Comments, or Users.
Note that some groups are applied automatically without having to call `addToGroup`:
- `anonymous`: any non-logged-in user is considered anonymous. This group is special in that anonymous users are by definition not part of any other group.
- `default`: default group for all existing users. Is applied to every user in addition to any other groups.
- `admins`: any user with the `isAdmin` flag set to true.
### Assigning Actions
```js
@ -502,7 +525,7 @@ Users.groups.mods.can("posts.edit.all"); // mods can edit anybody's posts
Users.groups.mods.can("posts.remove.all"); // mods can delete anybody's posts
```
You can also define and test for your own custom actions:
You can also define your own custom actions:
```js
Users.groups.mods.can("invite"); // new custom action
@ -512,10 +535,18 @@ Here's a list of all out-of-the-box permissions:
```js
// anonymous actions
posts.view
comments.view
posts.view.approved.own
posts.view.approved.all
comments.view.own
comments.view.all
// default actions
posts.view.approved.own
posts.view.approved.all
posts.view.pending.own
posts.view.rejected.own
posts.view.spam.own
posts.view.deleted.own
posts.new
posts.edit.own
posts.remove.own
@ -523,6 +554,8 @@ posts.upvote
posts.cancelUpvote
posts.downvote
posts.cancelDownvote
comments.view.own
comments.view.all
comments.new
comments.edit.own
comments.remove.own
@ -534,6 +567,10 @@ users.edit.own
users.remove.own
// admin actions
posts.view.pending.all
posts.view.rejected.all
posts.view.spam.all
posts.view.deleted.all
posts.new.approved
posts.edit.all
posts.remove.all
@ -543,24 +580,6 @@ users.edit.all
users.remove.all
```
### Groups API
```js
Users.methods.addGroup(userId, groupName); // add a user to a group (server only)
Users.getGroups(user); // get a list of all the groups a user belongs to
Users.getActions(user); // get a list of all the actions a user can perform
Users.canDo(user, action); // check if a user can perform a specific action
```
Note that some groups are applied automatically without having to call `addToGroup`:
- `anonymous`: any non-logged-in user is considered anonymous.
- `admins`: any user with the `isAdmin` flag set to true.
- `default`: default group for all existing users.
## Internationalization
Nova is internationalized using [react-intl](https://github.com/yahoo/react-intl/). To add a new language, you need to:

View file

@ -104,8 +104,8 @@ class CommentsItem extends Component{
<Telescope.components.UsersAvatar size="small" user={comment.user}/>
<Telescope.components.UsersName user={comment.user}/>
<div className="comments-item-date"><FormattedRelative value={comment.postedAt}/></div>
{Users.can.edit(this.props.currentUser, this.props.comment) ? <a className="comment-edit" onClick={this.showEdit}><FormattedMessage id="comments.edit"/></a> : null}
{Users.can.edit(this.props.currentUser, this.props.comment) ? <a className="comment-delete" onClick={this.deleteComment}><FormattedMessage id="comments.delete"/></a> : null}
{Users.canEdit(this.props.currentUser, this.props.comment) ? <a className="comment-edit" onClick={this.showEdit}><FormattedMessage id="comments.edit"/></a> : null}
{Users.canEdit(this.props.currentUser, this.props.comment) ? <a className="comment-delete" onClick={this.deleteComment}><FormattedMessage id="comments.delete"/></a> : null}
</div>
{this.state.showEdit ? this.renderEdit() : this.renderComment()}
</div>

View file

@ -2,7 +2,7 @@ import React, { PropTypes, Component } from 'react';
import Users from 'meteor/nova:users';
const CanEditPost = ({user, post, children}) => {
if (Users.can.edit(user, post)) {
if (Users.canEdit(user, post)) {
return children;
} else if (!user){
return <p>Please log in.</p>;

View file

@ -4,7 +4,7 @@ import Users from 'meteor/nova:users';
const CanEditUser = ({user, userToEdit, children}) => {
if (!user){
return <p>Please log in.</p>;
} else if (Users.can.edit(user, userToEdit)) {
} else if (Users.canEdit(user, userToEdit)) {
return children;
} else {
return <p>Sorry, you do not have permissions to edit this user at this time</p>;

View file

@ -2,7 +2,7 @@ import React, { PropTypes, Component } from 'react';
import Users from 'meteor/nova:users';
const CanView = ({user, children}) => {
if (Users.can.view(user)) {
if (Users.canDo(user, "posts.view.approved.all")) {
return children;
} else if (!user){
return <p>Please log in.</p>;

View file

@ -2,7 +2,7 @@ import React, { PropTypes, Component } from 'react';
import Users from 'meteor/nova:users';
const CanViewPost = ({user, post, children}) => {
if (Users.can.viewPost(this.props.user, this.props.document)) {
if (Users.canView(this.props.user, this.props.document)) {
return this.props.children;
} else if (!this.props.user){
return <p>Please log in.</p>;

View file

@ -27,7 +27,7 @@ class PostsItem extends Component {
return (
<div className="post-actions">
{Users.can.edit(this.context.currentUser, this.props.post) ? component : ""}
{Users.canEdit(this.context.currentUser, this.props.post) ? component : ""}
</div>
)
}

View file

@ -3,7 +3,11 @@ import Users from 'meteor/nova:users';
import Categories from "../collection.js";
Meteor.publish('categories', function() {
if(Users.can.viewById(this.userId)){
const currentUser = this.userId && Users.findOne(this.userId);
if(Users.canDo(currentUser, "posts.view.approved.all")){
var categories = Categories.find();
var publication = this;

View file

@ -103,7 +103,7 @@ function CommentsNewSubmittedPropertiesCheck (comment, user) {
// ok
} else {
var field = schema[fieldName];
if (!Users.can.submitField(user, field)) {
if (!Users.canSubmitField (user, field)) {
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
}
}
@ -253,7 +253,7 @@ Telescope.callbacks.add("comments.new.async", CommentsNewNotifications);
// ------------------------------------- comments.edit.method -------------------------------- //
function CommentsEditUserCheck (modifier, comment, user) {
if (!user || !Users.can.edit(user, comment)) {
if (!user || !Users.canEdit(user, comment)) {
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_comment');
}
return modifier;
@ -269,7 +269,7 @@ function CommentsEditSubmittedPropertiesCheck (modifier, comment, user) {
_.keys(operation).forEach(function (fieldName) {
var field = schema[fieldName];
if (!Users.can.editField(user, field, comment)) {
if (!Users.canEditField(user, field, comment)) {
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
}

View file

@ -95,7 +95,7 @@ Meteor.methods({
var comment = Comments.findOne(commentId);
var user = Meteor.user();
if(Users.can.edit(user, comment)){
if(Users.canEdit(user, comment)){
// decrement post comment count and remove user ID from post
Posts.update(comment.postId, {

View file

@ -1,11 +1,14 @@
import Users from 'meteor/nova:users';
const anonymousActions = [
"comments.view"
"comments.view.own",
"comments.view.all"
];
Users.groups.anonymous.can(anonymousActions);
const defaultActions = [
"comments.view.own",
"comments.view.all",
"comments.new",
"comments.edit.own",
"comments.remove.own",

View file

@ -10,7 +10,7 @@ Comments._ensureIndex({parentCommentId: 1});
*/
Meteor.publish('comments.list', function (terms) {
const currentUser = Meteor.users.findOne(this.userId);
const currentUser = this.userId && Meteor.users.findOne(this.userId);
terms.currentUserId = this.userId; // add currentUserId to terms
({selector, options} = Comments.parameters.get(terms));
@ -24,7 +24,7 @@ Meteor.publish('comments.list', function (terms) {
const posts = Posts.find({_id: {$in: _.pluck(comments.fetch(), 'postId')}}, {fields: Posts.publishedFields.list});
const users = Meteor.users.find({_id: {$in: _.pluck(comments.fetch(), 'userId')}}, {fields: Users.publishedFields.list});
return Users.can.view(currentUser) ? [comments, posts, users] : [];
return Users.canDo(currentUser, "comments.view.all") ? [comments, posts, users] : [];
});
@ -47,7 +47,7 @@ Meteor.publish('comments.list', function (terms) {
// const childCommentIds = _.pluck(Comments.find({parentCommentId: terms._id}, {fields: {_id: 1}}).fetch(), '_id');
// commentIds = commentIds.concat(childCommentIds);
// return Users.can.view(currentUser) ? Comments.find({_id: {$in: commentIds}}, {sort: {score: -1, postedAt: -1}}) : [];
// return Users.canView(currentUser) ? Comments.find({_id: {$in: commentIds}}, {sort: {score: -1, postedAt: -1}}) : [];
// });
@ -62,7 +62,7 @@ Meteor.publish('comments.list', function (terms) {
//
// if(Users.can.viewById(this.userId)){
// if(Users.canViewById(this.userId)){
// var comment = Comments.findOne(commentId);
// return Posts.find({_id: comment && comment.postId});
// }
@ -79,7 +79,7 @@ Meteor.publish('comments.list', function (terms) {
// var userIds = [];
// if(Users.can.viewById(this.userId)){
// if(Users.canViewById(this.userId)){
// var comment = Comments.findOne(commentId);

View file

@ -122,7 +122,7 @@ Meteor.methods({
},
generateThumbnail: function (post) {
check(post, Posts.simpleSchema());
if (Users.can.edit(Meteor.user(), post)) {
if (Users.canEdit(Meteor.user(), post)) {
regenerateThumbnail(post);
}
},

View file

@ -12,7 +12,8 @@ Meteor.methods({
return Newsletter.scheduleNextWithMailChimp(true);
},
'newsletter.addUser'(user){
if (!user || !Users.can.editById(this.userId, user)) {
const currentUser = this.userId && Users.findOne(this.userId);
if (!user || !Users.canEdit(currentUser, user)) {
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_user');
}
@ -23,7 +24,8 @@ Meteor.methods({
}
},
'newsletter.removeUser'(user) {
if (!user || !Users.can.editById(this.userId, user)) {
const currentUser = this.userId && Users.findOne(this.userId);
if (!user || !Users.canEdit(currentUser, user)) {
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_user');
}

View file

@ -155,7 +155,7 @@ function PostsNewSubmittedPropertiesCheck (post, user) {
_.keys(post).forEach(function (fieldName) {
var field = schema[fieldName];
if (!Users.can.submitField(user, field)) {
if (!Users.canSubmitField (user, field)) {
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
}
@ -283,7 +283,7 @@ Telescope.callbacks.add("posts.new.async", PostsNewNotifications);
function PostsEditUserCheck (modifier, post, user) {
// check that user can edit document
if (!user || !Users.can.edit(user, post)) {
if (!user || !Users.canEdit(user, post)) {
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_post');
}
return modifier;
@ -299,7 +299,7 @@ function PostsEditSubmittedPropertiesCheck (modifier, post, user) {
_.keys(operation).forEach(function (fieldName) {
var field = schema[fieldName];
if (!Users.can.editField(user, field, post)) {
if (!Users.canEditField(user, field, post)) {
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
}

View file

@ -179,36 +179,3 @@ ${Posts.getLink(post, true, false)}
};
Posts.helpers({ getEmailShareUrl() { return Posts.getEmailShareUrl(this); } });
///////////////////
// Users Helpers //
///////////////////
/**
* @summary Check if a given user can view a specific post
* @param {Object} user - can be undefined!
* @param {Object} post
*/
Users.can.viewPost = function (user, post) {
if (Users.is.admin(user)) {
return true;
} else {
switch (post.status) {
case Posts.config.STATUS_APPROVED:
return Users.can.view(user);
case Posts.config.STATUS_REJECTED:
case Posts.config.STATUS_SPAM:
case Posts.config.STATUS_PENDING:
return Users.can.view(user) && Users.is.owner(user, post);
case Posts.config.STATUS_DELETED:
return false;
}
}
}
Users.helpers({canViewPost: function () {return Users.can.viewPost(this, post);}});

View file

@ -215,7 +215,7 @@ Meteor.methods({
var post = Posts.findOne({_id: postId});
if (!Meteor.userId() || !Users.can.editById(Meteor.userId(), post)){
if (!Meteor.userId() || !Users.canEdit(Meteor.user(), post)){
throw new Meteor.Error(606, 'You need permission to edit or delete a post');
}

View file

@ -1,12 +1,18 @@
import Users from 'meteor/nova:users';
const anonymousActions = [
"posts.view"
"posts.view.approved.own",
"posts.view.approved.all"
];
Users.groups.anonymous.can(anonymousActions);
const defaultActions = [
"posts.view",
"posts.view.approved.own",
"posts.view.approved.all",
"posts.view.pending.own",
"posts.view.rejected.own",
"posts.view.spam.own",
"posts.view.deleted.own",
"posts.new",
"posts.edit.own",
"posts.remove.own",
@ -18,6 +24,10 @@ const defaultActions = [
Users.groups.default.can(defaultActions);
const adminActions = [
"posts.view.pending.all",
"posts.view.rejected.all",
"posts.view.spam.all",
"posts.view.deleted.all",
"posts.new.approved",
"posts.edit.all",
"posts.remove.all"

View file

@ -70,7 +70,7 @@ Meteor.publish('posts.list', function (terms) {
this.autorun(function () {
const currentUser = Meteor.users.findOne(this.userId);
const currentUser = this.userId && Meteor.users.findOne(this.userId);
terms.currentUserId = this.userId; // add currentUserId to terms
const {selector, options} = Posts.parameters.get(terms);
@ -88,7 +88,7 @@ Meteor.publish('posts.list', function (terms) {
return getPostsListUsers(posts);
});
return Users.can.view(currentUser) ? [posts, users] : [];
return Users.canDo(currentUser, "posts.view.approved.all") ? [posts, users] : [];
});
@ -102,14 +102,14 @@ Meteor.publish('posts.single', function (terms) {
check(terms, Match.OneOf({_id: String}, {_id: String, slug: Match.Any}));
const currentUser = Meteor.users.findOne(this.userId);
const currentUser = this.userId && Meteor.users.findOne(this.userId);
const options = {fields: Posts.publishedFields.single};
const posts = Posts.find(terms._id, options);
const post = posts.fetch()[0];
if (post) {
const users = getSinglePostUsers(post);
return Users.can.viewPost(currentUser, post) ? [posts, users] : [];
return Users.canView(currentUser, post) ? [posts, users] : [];
} else {
console.log(`// posts.single: no post found for _id “${terms._id}`)
return [];

View file

@ -1,5 +1,10 @@
import Users from './collection.js';
/**
* @summary Users.groups object
*/
Users.groups = {};
/**
* @summary Group class
*/
@ -21,11 +26,6 @@ class Group {
}
/**
* @summary Users.groups object
*/
Users.groups = {};
/**
* @summary create a new group
* @param {String} groupName
@ -63,7 +63,6 @@ Users.getGroups = user => {
return userGroups;
};
Users.helpers({getGroups: function () {return Users.getGroups(this);}});
/**
* @summary get a list of all the actions a user can perform
@ -74,7 +73,6 @@ Users.getActions = user => {
const groupActions = userGroups.map(groupName => Users.groups[groupName].actions);
return _.unique(_.flatten(groupActions));
};
Users.helpers({getActions: function () {return Users.getActions(this);}});
/**
* @summary check if a user can perform a specific action
@ -84,7 +82,72 @@ Users.helpers({getActions: function () {return Users.getActions(this);}});
Users.canDo = (user, action) => {
return Users.getActions(user).indexOf(action) !== -1;
};
Users.helpers({canDo: function (action) {return Users.canDo(this, action);}});
/**
* @summary Check if a given user can view a specific document
* @param {Object} user - can be undefined!
* @param {Object} document - Note: only actually works with posts for now
*/
Users.canView = function (user, document) {
const status = _.findWhere(Posts.config.postStatuses, {value: document.status}).label;
const collectionName = document.getCollectionName();
if (!document) {
return false;
}
if (Users.owns(user, document)) {
return Users.canDo(user, `${collectionName}.view.${status}.own`);
} else {
return Users.canDo(user, `${collectionName}.view.${status}.all`);
}
};
/**
* @summary Check if a user can edit a document
* @param {Object} user - The user performing the action
* @param {Object} document - The document being edited
*/
Users.canEdit = function (user, document) {
user = (typeof user === 'undefined') ? Meteor.user() : user;
const collectionName = document.getCollectionName();
if (!user || !document) {
return false;
}
if (document.hasOwnProperty('isDeleted') && document.isDeleted) return false;
if (Users.owns(user, document)) {
// if this is user's document, check if user can edit own documents
return Users.canDo(user, `${collectionName}.edit.own`);
} else {
// if this is not user's document, check if they can edit all documents
return Users.canDo(user, `${collectionName}.edit.all`);
}
};
/**
* @summary Check if a user can submit a field
* @param {Object} user - The user performing the action
* @param {Object} field - The field being edited or inserted
*/
Users.canSubmitField = function (user, field) {
return user && field.insertableIf && field.insertableIf(user);
};
/** @function
* Check if a user can edit a field for now, identical to Users.canSubmitField
* @param {Object} user - The user performing the action
* @param {Object} field - The field being edited or inserted
*/
Users.canEditField = function (user, field, document) {
return user && field.editableIf && field.editableIf(user, document);
};
/**
* @summary initialize the 3 out-of-the-box groups
@ -92,20 +155,3 @@ Users.helpers({canDo: function (action) {return Users.canDo(this, action);}});
Users.createGroup("anonymous"); // non-logged-in users
Users.createGroup("default"); // regular users
Users.createGroup("admins"); // admin users
/**
* @summary add default actions concerning users
*/
const defaultActions = [
"users.edit.own",
"users.remove.own"
];
Users.groups.default.can(defaultActions);
const adminActions = [
"users.edit.all",
"users.remove.all"
];
Users.groups.admins.can(adminActions);
console.log(Users.groups);

View file

@ -72,7 +72,7 @@ Meteor.methods({
// ------------------------------ Checks ------------------------------ //
// check that user can edit document
if (!user || !Users.can.edit(currentUser, user)) {
if (!user || !Users.canEdit(currentUser, user)) {
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_user');
}
@ -97,7 +97,7 @@ Meteor.methods({
// loop over each property being operated on
_.keys(operation).forEach(function (fieldName) {
var field = schema[fieldName];
if (!Users.can.editField(user, field, user)) {
if (!Users.canEditField(user, field, user)) {
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
}
@ -120,7 +120,7 @@ Meteor.methods({
// ------------------------------ Checks ------------------------------ //
// check that user can edit document
if (!user || !Users.can.edit(currentUser, user)) {
if (!user || !Users.canEdit(currentUser, user)) {
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_user');
}
@ -131,7 +131,7 @@ Meteor.methods({
_.keys(operation).forEach(function (fieldName) {
var field = schema[fieldName];
if (!Users.can.editField(currentUser, field, user)) {
if (!Users.canEditField(currentUser, field, user)) {
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
}
@ -166,7 +166,7 @@ Meteor.methods({
user = Users.findOne(userId);
// check that user can edit document
if (!user || !Users.can.edit(currentUser, user)) {
if (!user || !Users.canEdit(currentUser, user)) {
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_user');
}

View file

@ -3,7 +3,6 @@ import Users from './collection.js';
import './roles.js';
import './schema.js';
import './config.js';
import './permissions.js';
import './collection.js';
import './callbacks.js';
import './helpers.js';
@ -13,5 +12,6 @@ import './emails.js';
import './avatar.js';
import './methods.js';
import './groups.js';
import './permissions.js';
export default Users;

View file

@ -1,126 +1,13 @@
import Users from './collection.js';
// note: using collection helpers here is probably a bad idea,
// because they'll throw an error when the user is undefined
/**
* @summary Telescope permissions
* @namespace Users.can
*/
Users.can = {};
/**
* @summary Check if a given user has access to view posts
* @param {Object} user
*/
Users.can.view = function (user) {
if (Telescope.settings.get('requireViewInvite', false)) {
if (Meteor.isClient) {
// on client only, default to the current user
user = (typeof user === 'undefined') ? Meteor.user() : user;
}
return (!!user && (Users.is.admin(user) || Users.is.invited(user)));
}
return true;
};
Users.helpers({canView: function () {return Users.can.view(this);}});
/**
* @summary Check if a given user can view a specific post
* @param {Object} user
* @param {Object} post
*/
Users.can.viewById = function (userId) {
// if an invite is required to view, run permission check, else return true
if (Telescope.settings.get('requireViewInvite', false)) {
return !!userId ? Users.can.view(Meteor.users.findOne(userId)) : false;
}
return true;
};
Users.helpers({canViewById: function () {return Users.can.viewById(this);}});
/**
* @summary Check if a given user has permission to submit new posts
* @param {Object} user
*/
// Users.can.post = function (user) {
// return Users.canDo(user, "posts.new");
// };
// Users.helpers({canPost: function () {return Users.can.post(this);}});
/**
* @summary Check if a given user has permission to comment (same as posting for now)
* @param {Object} user
*/
// Users.can.comment = function (user) {
// return Users.canDo(user, "comments.new");
// };
// Users.helpers({canComment: function () {return Users.can.comment(this);}});
/**
* @summary Check if a user has permission to vote (same as posting for now)
* @param {Object} user
*/
// Users.can.vote = function (user) {
// return Users.can.post(user);
// };
// Users.helpers({canVote: function () {return Users.can.vote(this);}});
/**
* @summary Check if a user can edit a document
* @param {Object} user - The user performing the action
* @param {Object} document - The document being edited
*/
Users.can.edit = function (user, document) {
user = (typeof user === 'undefined') ? Meteor.user() : user;
const collectionName = document.getCollectionName();
if (!user || !document) {
return false;
}
if (document.hasOwnProperty('isDeleted') && document.isDeleted) return false;
// if this is user's document, check if user can edit own documents
const editOwnCheck = user.owns(document) && user.canDo(collectionName+".edit.own");
// if this is not user's document, check if they can edit all documents
const editAllCheck = user.canDo(collectionName+".edit.all");
return editOwnCheck || editAllCheck;
};
Users.helpers({canEdit: function (document) {return Users.can.edit(this, document);}});
Users.can.editById = function (userId, document) {
var user = Meteor.users.findOne(userId);
return Users.can.edit(user, document);
};
Users.helpers({canEditById: function (document) {return Users.can.editById(this, document);}});
/**
* @summary Check if a user can submit a field
* @param {Object} user - The user performing the action
* @param {Object} field - The field being edited or inserted
*/
Users.can.submitField = function (user, field) {
return user && field.insertableIf && field.insertableIf(user);
};
Users.helpers({canSubmitField: function (field) {return Users.can.submitField(this, field);}});
/** @function
* Check if a user can edit a field for now, identical to Users.can.submitField
* @param {Object} user - The user performing the action
* @param {Object} field - The field being edited or inserted
*/
Users.can.editField = function (user, field, document) {
return user && field.editableIf && field.editableIf(user, document);
};
Users.can.invite = function (user) {
return Users.is.invited(user) || Users.is.admin(user);
};
Users.helpers({canInvite: function () {return Users.can.invite(this);}});
const defaultActions = [
"users.edit.own",
"users.remove.own"
];
Users.groups.default.can(defaultActions);
const adminActions = [
"users.edit.all",
"users.remove.all"
];
Users.groups.admins.can(adminActions);