3

I am using Alanning Roles to maintain a set of groups/roles for the users of my application. When a user creates an "Application", I generate a new role for them as the app_name + UUID, then add that as a group with the roles of Admin to the user that created it. I can then use the combination of the generated group name plus either the Admin or Viewer roles to determine which Applications the user has rights to see and/or edit.

The issue that I am having is that I can't figure out a good way to get the publication to only publish the things the user should see. I know that, by default at least, publications are not "reactive" in the way the client is, and they they are only reactive for the cursors they return. But, in my code I create the group/role first, add it to the user, then save the "Application", which I thought would rerun my publication, but it did not:

Meteor.publish('myApplications', function(groups) {
  if (this.userId) {
    console.log('Running myApplications publication...');
    console.log('Found roles for user ' + this.userId + ': ', Roles.getGroupsForUser(this.userId));
    return Applications.find({group: {$in: Roles.getGroupsForUser(this.userId)}});
  } else {
    //console.log("Skipping null user");
    return null;
  }
});

But, contrary to what I thought would happen (the whole publication method would re-run), I am guessing what really happens is that only the Cursor is updates. So for my next attempt, I added the mrt:reactive-publications package and simply got a cursor to the Meteor.users collection for the user, thinking that would "trigger" the publication to re-run when the user gets updated with the new group/role, but that didn't work.

I have this finally working by simply passing in the groups for the user:

Meteor.publish('myApplications', function(groups) {
  if (this.userId) {
    if (!groups || groups.length === 0) {
      groups = Roles.getGroupsForUser(this.userId);
    }
    console.log('Running myApplications publication...');
    console.log('Found roles for user ' + this.userId + ': ', Roles.getGroupsForUser(this.userId));
    return Applications.find({group: {$in: groups}});
  } else {
    //console.log("Skipping null user");
    return null;
  }
});

And then I just call the publication like Meteor.subscribe('myApplications', Roles.getGroupsForUser(Meteor.userId())) in my route's waitOn, but this would mean that any client could call the same publication and pass in any groups they like, and potentially see documents they were not intended to see. That seems like a pretty large security flaw.

Is there a better way to implement this such that the client would not be able to coax their way to seeing stuff not theirs? I think the only real way would be to gather the groups on the publication side, but then it breaks the reactivity.

CodeChimp
  • 8,016
  • 5
  • 41
  • 79

1 Answers1

2

After sifting through a bunch of docs and a few very helpful stack posts, this is the alternative I came up with. Works like a charm!

My objective was to publish 'guest' users' info to the group admins for approval/denial of enhanced permissions.

Meteor.publish('groupAdmin', function(groupId) {
  // only publish guest users info to group admins
  if(Roles.userIsInRole(this.userId, ['group-admin'], groupId)) {
    // I can't explain it but it works!
    var obj = {key: {$in: ['guest']}};
    var query = {};
    var key = ('roles.' + groupId);
    query[key] = {$in: ['guest']};
    return Meteor.users.find(query, {
        fields: {
          createdAt: 1,
          profile: 1
        }
      });
  } else {
    this.stop();
    return;
  }
});

Reference: How to set mongo field from variable & How do I use a variable as a field name in a Mongo query in Meteor?

Community
  • 1
  • 1