Check users data to access a Collection - meteor

I want to check a specific field in the users collection to give permissions to modify another collection. This is my code:
import { Mongo } from 'meteor/mongo';
export const Empleados = new Mongo.Collection('empleados');
Empleados.allow({
insert(userId, empleado) {
return userId;
},
update(userId, empleado, fields, modifier) {
return userId;
},
remove(userId, empleado) {
return userId
}
});
I can check the userId, but how do I check the other fields of the users collection? I am using the accounts-password package.

Use this in your "allow" rules:
insert: (userId, doc) ->
if (doc.user_id == userId && Meteor.users.findOne({_id: userId}).isAdmin)
true
else
false

You can add the alanning:roles package to your project, which will allow you to give roles to users and you can check if they have the right role assigned to do the operation.
Alternatively you could add a flag to the Meteor.user().profile object in the user record, and look at that to decide.

Related

Firestore transactions with security rules making reads

I want to create two documents
Account/{uid} {
consumerId: ... //client generated id
}
Consumer/{consumerId} {
...
}
and I have a security rule for the consumer collection
match /Consumer/{consumerId} {
allow create: if (consumerId == get(/databases/$(database)/documents/Account/$(request.auth.uid)).data['consumerId'];
}
I need to ensure that an account can only add a consumer document with a consumerId corresponding to the one in their Account document. Both documents should be created together. I've been trying to do this with transactions but I keep getting the error "Transaction failed all retries.". Whats going wrong and how do I fix it?
The data variable is an object and not an array, so you should use data.consumerId instead of data['consumerId']:
match /Consumer/{consumerId} {
allow create: if consumerId == get(/databases/$(database)/documents/Account/$(request.auth.uid)).data.consumerId;
}
I ended up accomplishing this with a batch write and security rules.
match /consumer/{cid} {
function isNewResource() { return resource == null; }
allow create: if isRegistered();
allow read, update: if isNewResource();
}
And then client side with something along the lines of
createThing() {
const db = firebase.firestore();
const { uid, displayName } = this.auth.currentUser;
const batch = this.db.batch();
// Essentially generating a uuid
const newConsumerRef = db.collection("consumer").doc();
// Update the user doc
batch.update(
db.collection('/users').doc(uid),
{ consumerID: newConsuemrRef.id }
);
// Update the admins field in the new doc
batch.set(newConsumerRef, {
admins: {
[uid]: displayName,
},
});
return batch.commit();
}
My problem was the same, but the write to the field in the collections actually needed to be to an object key, so it looked a little funkier
batch.update(
db.collection('/users').doc(uid),
{ [`adminOf.${newRef.id}`]: 'some special name' }
);

Server side meteor this.userprofile

I have the following code that renders the currentUsers' documents in a collection. However I want an admin belonging to the same organization to also be able to view and edit the collection. this.user.profile.organization does not work unfortunately. So, how would I allow the object to be available to admins from belonging to the same organization. EVery document that gets created gets the organization of the currentuser.
Meteor.publish('skills', function skillsPublication() {
return Skills.find({
owner: this.userId,
});
});
When you're on the server, you can always query the user document from the MongoDB database.
Meteor.publish('skills', function skillsPublication() {
const user = Meteor.users.findOne({ _id: this.userId })
// now you can do user.profile.organization
return Skills.find({
$or: [
{ owner: this.userId },
{ organization: user.profile.organization }
] // returns a document owned by the user or owned by the user's organization
})
})
Just a note, Meteor advises against using .profile field on your users collection, because that field is always published to the client. Meteor suggests that you use top-level keys on your user document instead.
For more info, read: https://guide.meteor.com/accounts.html#dont-use-profile

How do you add extra data in currentUser object?

I am trying to add an extra information that is not in profile for the currentUser object in Meteor. I am thinking it is possible and the technique should be somewhere in meteor/alanning:roles.
e.g. if I have a organizations object in users e.g.
{
_id: ...
profile: { ... }
roles: [ ... ]
organizations: [ ... ]
}
I would like to see it when I do
{{ currentUser }}
To push an orgId onto the organizations array in the user object for example:
Meteor.users.update(_id,{$push: {organizations: orgId}});
As #Blaze Sahlzen says, you'll need to publish this field:
Meteor.publish('orgUsers',function(orgId){
return Meteor.users.find({organization: orgId},{fields: {organization: 1, profile: 1}});
});
Basically currentUser is a global helper in meteor which calls returns Meteor.user()
From meteor repo
Package.blaze.Blaze.Template.registerHelper('currentUser', function () {
return Meteor.user();
});
https://github.com/meteor/meteor/blob/2ccb7467c9bb5889a3c36739d2d8b59a0656961c/packages/accounts-base/accounts_client.js#L421
Meteor.user() function returns following data
user() {
var userId = this.userId();
return userId ? this.users.findOne(userId) : null;
}
https://github.com/meteor/meteor/blob/dc3cd6eb92f2bdd1bb44000cdd6abd1e5d0285b1/packages/accounts-base/accounts_common.js#L52
Coming to your question, if you want to add extra fields to currentUser just publish the data to client.
If you want to publish that field to users by default
Meteor.publish(null, function(argument){
if(this.userId){
return Members.findOne({userIds: this.userId}, { fields: { organizations: 1,... } });
}
this.ready()
});

Meteor Collection and Security

I am curious if I'm setting up the allow statement on this collection correctly. I'm using aldeed:autoform and aldeed:collection2.
Below is the snapshot of a issue-collection.js from a toy project.
Is this the proper way to set up allow checks? Do these run on both client (for minimongo) and server? Specifically, on most update calls, is return !!userId && (doc.userId == userId); enough to ensure the user is logged in AND the logged in user is the owner of the document?
Clarification and actual question: Do the allow and deny methods run on BOTH server and client? Or do they run only on the client?
Issues = new Mongo.Collection("issues");
if (Meteor.isClient){
Meteor.subscribe("issues");
}
if(Meteor.isServer){
Meteor.publish('issues', function () {
return Issues.find({}, {limit: ServerSettings.maxSubscribe});
});
}
Issues.attachSchema(new SimpleSchema({
issue: {
type: String,
label: "Describe the issue you noticed",
max:256
}
}));
//SECURITY - Allow Callbacks for posting
Issues.allow({
insert: function(userId, doc) {
/* Throw in some defaults. */
doc.userId = userId;
doc.sumbitDate = new Date();
doc.date = new Date();
// only allow posting if you are logged in
return !! userId;
},
update: function(userId, doc) {
// only allow updating if you are logged in
return !!userId && (doc.userId == userId);
},
remove: function(userID, doc) {
//only allow deleting if you are owner
return doc.submittedById === Meteor.userId();
}
});
Remember, allow/deny comes from the client. And you can't trust anything that comes from the client (userId, date, etc).
What you want to do is call a Meteor.method from the client & _.extend the document with trustworthy data from the server.
As an example, rewrite that code in the browser console & change the value for the userId.
Check out Discover Meteor blog to learn more (it's probably the best source for learning basic patterns) https://www.discovermeteor.com/blog.

Add extra user field

In my Meteor app I use the default accounts package, which gives me the default login and registration functionality. Now I want to add an extra field to user, say nickname, and for the logged in user the possibility to edit this information.
For editing the profile I suppose I should be doing something like this:
Template.profileEdit.events({
'submit form': function(e) {
e.preventDefault();
if(!Meteor.user())
throw new Meteor.Error(401, "You need to login first");
var currentUserId = this._id;
var user = {
"profile.nickname": $(e.target).find('[name=nickname]').val()
};
Meteor.users.update(currentUserId, {
$set: user
}, function(error){
if(error){
alert(error.reason);
} else {
Router.go('myProfile', {_id: currentUserId});
}
});
}
});
But I doesn't store the info if I look in Mongo. Also when showing the profile, {{profile.nickname}} returns empty. What is wrong here?
Edit: added collections\users.js to show permissions:
Meteor.users.allow({
update: function (userId, doc) {
if (userId && doc._id === userId) {
return true;
}
}
});
Meteor.users.deny({
update: function(userId, user, fieldNames) {
return (_.without(fieldNames, 'profile.nickname').length > 0);
}
});
Yeah, I believe that should do the job, although I haven't actually run the code. The idea is certainly right.
The main things to be aware of are:
The necessity to allow the user doc to be edited from the client with an appropriate Meteor.users.allow() block on the server, assuming you're going to remove the "insecure" package (which you need to before doing anything in production).
The fact that "by default the server publishes username, emails, and profile", so you'll need to write a Meteor.publish function on the server and subscribe to it if you want to expose any other fields within the user document to the client once you've removed the "autopublish" package (which again, you really should).

Resources