In my app an user has the option to delete all his notifications. As an event handler I have this:
Template.clearNotifications.events({
'click .clear-notifications': function() {
Notifications.remove({userId: Meteor.user()._id});
}
});
When it is called, I get this error:
Uncaught Error: Not permitted. Untrusted code may only remove documents by ID. [403]
Is this a permission I forgot to add, or is it generally not allowed to do this? And if so, what are my options to remove the user's notifications?
On the client, meteor only allows the removal of documents by id. Fortunately there's an easy solution - just iterate over all of the current user's notifications and remove each one:
Template.clearNotifications.events({
'click .clear-notifications': function() {
Notifications
.find({userId: Meteor.userId()})
.forEach(function(notification) {
Notifications.remove(notification._id);
});
}
});
Keep in mind that this will only remove notifications which the client knows about (those which have been published). If there are additional notifications in the database which also need to be removed (maybe you only published the 10 most recent documents), you would need to use a method. For example:
Meteor.methods({
removeAllNotifications: function() {
Notifications.remove({userId: this.userId});
}
});
Which you can invoke from the client with:
Meteor.call('removeAllNotifications');
Removing in untrusted area (client) works only when you pass _id of document:
var notification = Notifications.findOne({userId:Meteor.userId()});
Notifications.remove({_id:notification._id})
Explanation:
The behavior of remove differs depending on whether it is called by trusted or untrusted code. Trusted code includes server code and method code. Untrusted code includes client-side code such as event handlers and a browser's JavaScript console.
Trusted code can use an arbitrary Mongo selector to find the documents to remove, and can remove more than one document at once by passing a selector that matches multiple documents. It bypasses any access control rules set up by allow and deny. The number of removed documents will be returned from remove if you don't pass a callback.
As a safety measure, if selector is omitted (or is undefined), no documents will be removed. Set selector to {} if you really want to remove all documents from your collection.
Untrusted code can only remove a single document at a time, specified by its _id. The document is removed only after checking any applicable allow and deny rules. The number of removed documents will be returned to the callback.
http://docs.meteor.com/#remove
Related
I have removed the auto-publish package from my Meteor app and I have also created a publication on my server:
Meteor.publish("userData", function () {
return Meteor.users.find(
{_id: this.userId},
{fields: {'profile': 0}}
);
});
As you can see above I have set the profile to 0 which means I would like to exclude it. However...
On my client I have this code:
Meteor.subscribe("userData", (param) => {
console.log( Meteor.users.find(Meteor.userId()).fetch() )
})
and the output still includes the profile:
createdAt: Sat May 19 2018 11:16:25 GMT+0800 (+08) {}
emails: [{…}]
profile: {name: "Second Second"}
services: {password: {…}, resume: {…}}
username: "seconduser"
_id: "ESmRokNscFcBA9yN4"
__proto__: Object
length: 1
__proto__: Array(0)
What's the reason for this?
It is possible that some other subscription has subscribed to the profile field of the user.
You can find out if this is the case by looking at the information sent over the websocket.
Open the debugger,
find a "networking" tab,
find the websocket connection,
find the content or "frames".
There you can see which subs have been made and which updates the server publishes to a collection. See what it looks like without your sub; maybe the user doc is published already.
You see the profile field on the client since you have already edited and thus enabled this special field of the User object while creating or updating user. In order to secure this object from client-side modifications you can deny all writes from the client with the following server-side code.
// Deny all client-side updates to user documents
Meteor.users.deny({
update () { return true; },
});
So, even though the profile field is be available on the client within the Meteor.user() object, no modification can be made by the client.
If it is a custom data that you publish than you can control its exposition in your way. For example, let's assume that we introduce a new field customProfile into the user object, than with the following code, the customProfile will not be visible to the client.
Meteor.publish("userData", function () {
console.log('publishing userData with id', this.userId);
return Meteor.users.find(
{_id: this.userId},
{fields: {'customProfile': 0}}
);
});
You may find more information in the guide.
First of all, make sure whether you want to use Meteor.subscribe() or you want to use this.subscribe(). There is a lot of difference between them.
Meteor.subscribe() will keep subscription undestroyed when you change between screens/routes/UI.
this.subscribe() will have the scope of subscription till the life of Template exists. When you switch to other routes/path/UI, the subscription will be destroyed. This is used in a specific case when you have multiple kinds of subscription among consecutive transitions of the screen and problem occurs for unwanted data shown in UI despite filtering in Collection Query.
For more insight, click here.
Comming to your exact question, well when Meteor knows that you are a valid and logged in user, it sends entire Users specific collection fields _id, emails, profile, username on UI. So, it is recommended that you put only the required data into the User collection. Whether or not you make special kind of subscription to self-data, you will always be able to access your own data on UI, even on production build. You can check by putting console.log(Meteor.user()); in chrome console. This is how Meteor.user() is made, whether you like it or not. It was assumed by MDG (Meteor Development Group) that when the user has logged in, a user can fully access his/her own data at UI as it is safe and valid.
see below image for reference,
I am coding a basic meteor app and I ask myself how to notify other session of a change.
I have a basic template showing details of a document.
And someone can delete this document,
How can I notify other session that was watching this document, that it was deleted and redirect them?
If your document is in a collection and the delete corresponds to removing the document from the collection, you can use Meteor's observe on the collection to register a callback when the document you're watching is removed:
Documents.find({_id: myDocumentId}).observe({
removed: function () {
console.log('document removed');
}
});
and from there do any DOM/Session manipulation you want to notify clients of the change.
If you display your document by accessing directly your collection client-side (i.e. you don't use a method), your page fields values should reactively disappear since the document does not exist anymore.
What you can do is add a field deleted to your document and when it changes to true, you display your notification. I would advise to use something like a modal, so the user cant dodge it (when he close it, you redirect).
It also means that instead of deleting a document, the other user just change its deleted field to true. Once you set it to true you can also set a time differed function to effectively delete the doc for example 5mn later:
Meteor.setInterval(function () {
Document.remove(yourDocumentID);
}, 300000)
I have a publication based on server-side user permissions. I want it to be reactive to changes in these permissions.
// SERVER CODE
Meteor.publish("my_publication", function(parent_id) {
//fetch our parent record and lookup this user's permissions
var parent = ParentCollection.findOne({_id: parent_id});
var myPermissionsList = parent.permissionsDict[this.userId];
//use these permissions to make our query
ChildCollection.find({parent_id: parent_id, permissions: {$in: myPermissionsList}})
}
// CLIENT CODE
Tracker.autorun(function () {
Meteor.subscribe('my_publication', Session.get("my_parent_id"));
});
This properly returns all the elements of the "child" collection specified parent, as long as the parent says the user has at least one of the permissions in the child element's list. It does this without the user actually knowing what their permissions are, which is a requirement.
This behaves like one would expect in Meteor:
The subscription does automatically update if any of the returned ChildCollection elements are changed.
The subscription does automatically update if the client changes the "my_parent_id" Session variable, triggering the Tracker.autorun resubscribe.
The subscription does not automatically update if the permissions used to make the query (parent.permissionsDict[this.userId]) are changed.
We're looking for the best (highest performing) way to get an automatic update in the last case.
This article was a helpful, more detailed resource on the topic:
https://www.discovermeteor.com/blog/reactive-joins-in-meteor/
My current understanding is that I need to utilize cursor.observeChanges() to react to changes in my permissions query. However, I am not sure how this fits into the rest of the Meteor publish/subscribe model--where would I call this, and how could the callback instruct Meteor to republish "my_publication"?
I believe https://atmospherejs.com/mrt/reactive-publish addresses this, but I feel like I should try to get a better grasp on core reactivity in meteor before turning to an external package. I also lack an understanding about the performance costs.
Any help would be greatly appreciated!
You can use the reactive-publish package (I am one of authors):
Meteor.publish("my_publication", function(parent_id) {
this.autorun(function (computation) {
//fetch our parent record and lookup this user's permissions
var parent = ParentCollection.findOne({_id: parent_id}, {fields: {permissionsDict: 1}});
var myPermissionsList = parent.permissionsDict[this.userId];
//use these permissions to make our query
return ChildCollection.find({parent_id: parent._id, permissions: {$in: myPermissionsList}});
});
}
It is important that you limit the fields you are interested in the parent document, otherwise autorun would rerun every time any field changes in the document, even if you do not care/use that field.
For security reasons, I want to add and remove properties of documents before publishing them to the client, depending on some dynamic calculations. I follow the Meteor documentation and this other SO question.
For example simplicity, say I try to add the following static property to every document (SERVER SIDE ONLY):
var Docs = new Meteor.Collection('docs', {
transform: function (f) {
console.log('Tagging doc: ' + f._id);
f.myProp = 1;
return f;
}
});
For some strange reason, this does not work:
Only some documents trigger the transform function, not all (I can see this through the console logging)
On the client side, none of the documents are tagged with myProp
I haven't tried to put the transform on both the client and the server, because in my real life app I cannot do the necessary computation on the client.
Transform functions on collections are intended for convenience, not security -- note that when you call observeChanges on a cursor, the information is not passed through the transform function (it is passed through the transform when you call observe). The default way of publishing a cursor works by calling observeChanges on it.
If you want to strip off fields of a cursor you're publishing, use the fields option to find on your collection. If you want to do something more complicated, you can explicitly do whatever computation you need if your publish function calls added, changed, and removed itself, instead of returning a cursor. Check out the docs for Meteor.publish for details.
I understand that when writing code that depends on the collection being loaded into the client minimongo, that you should explicitly subscribe to the collection and pass in the appropriate callback for when it is finished loading.
My problem is that I store a lot of important subdocuments that my page needs to access in the users collection. I am using Meteor Accounts, and am trying to figure out a similar way to wait until the entire logged in user document is available. When using this to test:
console.log(Meteor.user());
the logged in case, it seems like it first registers an object with just the _id, and then sends the other fields later (I know I have to explicitly add other fields to publish from the server beyond email, etc.).
Is there a way for me to wait for the logged in user document to load completely before executing my code?
Thanks!
Deps.autorun (previously Meteor.autorun) reruns when something reactive changes, which might fit your use case:
Client js
Deps.autorun(function () {
if(Meteor.user() {
//Collection available
}
});
If you're using a subscription you can also use its callback. Have a read about it on the docs as you might have to customize it a bit, and remove the autopublish package as well as get your other collections set up to subscriptions
Server js:
Meteor.publish("userdata", function () {
//You might want to alter this depending on what you want to send down
return Meteor.users.find({}, {}});
});
Client js
Meteor.subscribe("userdata", function() {
//Collection available
});