I'm kind of new to web development and I became a fan of meteor because of the way it lets me do cool stuf really easy. I have been toying around with the parties example and I have added a date attribute for the parties. I would like to only subscribe the client to parties that have not yet expired.
Essentially where datenow < partydate.
I find myself stuck in writing the correct subscribe code as I only find documentation on how to subscribe based on database attributes and not based on comparing the date of the party with the current date.
Meteor.subscribe("parties"); --> I think this is the part of the code on the client that I need to edit.
I really hope somebody could show me in the right direction on writing the correct subscribe code.
The client subscribes to what the server is willing to send to them.
if(Meteor.isClient){
Meteor.subscribe("parties");
}
The server filters data the client shouldn't have, typically for security reasons. You wouldn't want passwords or private information being published. Any client can open up the console and browse the full data set that was published to them.
if(Meteor.isServer){
Meteor.publish("parties", function(){
return Parties.find({date: {$gt: Date.now()}});
});
}
If you want clients to be able to see both expired parties and non-expired parties, you would publish the whole set from the server, then filter it on the client in a template helper.
if(Meteor.isServer){
Meteor.publish("parties", function(){
return Parties.find();
});
}
if(Meteor.isClient){
Meteor.subscribe("parties");
Template.templateName.allParties = function(){
return Parties.find();
}
Template.templateName.activeParties = function(){
return Parties.find({date: {$gt: Date.now()}});
}
}
Related
Just how Meteor.user() method is available on Client & Server for the "current user" I would love to reproduce this kind of functionality for different custom collections. For example, my app uses a "clouds" collection as a type of room for a group of users to be in. Obviously there are various cloud instances and I don't always want to be passing the cloudId into every single meteor method. Ideally I could have like a Meteor.cloud() function that would give me the current cloud on the client and server.
My thoughts on approaching this:
What I have been doing thus far is piggy-backing off of Meteor.user() by storing a currentCloudId property inside the user profile and setting that on a route beforeAction. However this limits the user to only being in 1 cloud at a time.
Using the Meteor.connection ID somehow to keep a map of connectionIds to cloudIds. This would work great in theory....however it seems that Meteor connection IDs cannot be heavily relied on as they might change during reconnects or other random scenarios. Also you would have to then "manange" that collection of "cloudConnections" and remove old stale ones and such.
Im using Iron Router....and if it were possible to get the current route data on the server that would also solve my problem but I am not sure how to access that on the server?
--- Basically I would love for a simple straight forward way to mimic Meteor.user() behavior for other collections.
Thanks for all your help :)
You can just create a function inside /lib that looks something like this:
getUserClouds = function getUserClouds () {
return Clouds.find({ $elemMatch: { $eq: Meteor.userId() } })
}
This will work both on the client and on the server. But it will always return a Cursor pointing to 0 docs. So you'll need a publication:
Meteor.publish('userClouds', function () {
return Clouds.find({ $elemMatch: { $eq: this.userId } })
})
I have 2 collections: Meteor.users and Projecs.
Users collection have field "projects" which contains array of user's project's ids.
"projects" : [
"jut6MHx6a7kSALPEP",
"XuJNvq7KTRheK6dSZ"
]
Also I have a publication for user's projects:
Meteor.publish('projects', function() {
var userProjects = Meteor.users.findOne(this.userId).projects;
return Projects.find({_id: {$in: userProjects}});
});
Everything works fine, but when I add new project (and update users ("projects" field) who are in this project) reactive publication doesn't works. Projects page doesn't contains recently added project. It works only when I refresh page.
Subscription made in router:
waitOn: function() {
return [
Meteor.subscribe('projects')
]
},
What should I do with this publication? Thanks a lot.
This is happening because Meteor.users is not reactive. I don't know what the reason behind but I saw many developers, specially developers who try to get famous by publish really cool articles about their awesome application, exposing the tokens.
So if some idiot publish the Meteor.users to the browser, it's a security flaw. It would be even worst if it was reactive because the token would be updated in realtime. Maybe this a block to newbie who don't really know that they're doing. Just my opinion about this decision.
This collection is design to be used for managing users and after the login, it makes no sense to use to store data, as it is designed.
Yea, this is a known "problem". Publish functions aren't reactive, so Meteor.users.findOne(this.userId).projects will only be evaluated when the client subscribes. You'll find a lot of information about this if you search for "meteor reactive joins", for example https://www.discovermeteor.com/blog/reactive-joins-in-meteor/
In your case, the clients will always have access to their array of project ids, right? Then the simplest solution would probably be to do something like this on the client:
Tracker.autorun(function(){
var user = Meteor.user()
if(user){
Meteor.subscribe(user.projects)
}
})
So, when the client notices that the array of project ids has changed, it renews the subscription (I'm unsure if passing user.projects to the subscribe call is necessary, but I'm a bit afraid that the subscription isn't is renewed if it's called with the same arguments as before).
Using the reactive-publish package (I am one of authors) you can do:
Meteor.publish('projects', function () {
this.autorun(function (computation) {
var userProjects = Meteor.users.findOne(this.userId, {fields: {projects: 1}}).projects;
return Projects.find({_id: {$in: userProjects}});
});
});
Just be careful to limit the first query only to projects so that autorun is not rerun for changes in other fields.
I am fairly new to Meteor and am just trying to figure out meteor security.
I am writing a quiz app that allows a logged in user to save their scores. I have created a collection which consists of a user id and an array of scores. The way I expose a push of new score is a method on the server side:
Meteor.methods({
'pushScore' : function(playerId, playerScore) {
UserScores.upsert({ userId : playerId}, {$push : {scores : playerScore}});
}
});
I call the method on click of a button from the client like so:
if (Meteor.userId()){
Meteor.call('pushScore', Meteor.userId(), Session.get("score"));
}
I have the following concerns here:
Obviously the user can manipulate the score value in "Session" and cheat the system. What could be an alternate secure mechanism to keep track of the running score while a quiz is being taken?
The other one is probably a bigger concern. How do I prevent the user from just firing a console call to my method "pushScore" and again cheat the system by adding, say a score of 100?
Is there an inherent flaw in the way I have designed here?
This is just a sample application, but I can easily imagine a real world scenario which could mimic this. What woudl be a best practice in such a scenario?
Thanks in advance.
Cheers..
As #Peppe suggested, you should move the logic to the server somehow. The main rule for Meteor security (and web security in general) is
You cannot trust the client.
The reason for that is what you've already mentioned: if there is something a client can do, then there is no way to stop a rogue user to do the same thing from the browser console, or even to write his own malicious client that will exploit the leak.
In your case, that means that if client is able to add points to scores, then the user is able to do so as well, regardless on what security measures you employ. You can make this more or less difficult, but your system has a designed leak which cannot be completely closed.
Thus, the only bulletproof solution is to make the server decide on when to assign points. I assume that in a quiz app user gets points when he choose the right answer to a question. So instead of checking that on the client, create a server–side method that will receive the question ID, answer ID, and increase user scores if the answer is correct. Then make sure user cannot just call this method with all possible answer, with a way that corresponds to your quiz design – for example give negative points if wrong answer is chosen, or allow to answer the same question only once in a period of time.
Finally, make sure the client doesn't just get the correct answer ID in the data it receives.
In a nutshell, there are 2 common soloutions to your problem:
if you're using a Meteor.method dont pass any arguments in the Meteor.call, the server can and should gather the data it plans to insert/update on the server side.
you can add a validation function to the collection using the collection "allow" method to verify any updates from the client, in that case you don't need the Meteor.method and can just update from the client and validate it server-side.
Security (insert/update/delete operations) in meteor works in the same way as security in any other framework: before executing an action taken by the user, make sure the user has the rights to perform it. Security may appear as a weakness in Meteor, but it does not suffer from it any more than other frameworks (though, it's easier to exploit it in Meteor through the console).
The best way to solve it probably varies from case to case, but here's an example: if a user posts a post, the user should gain 5 points. Here's a bad way to solve it:
if(Meteor.isClient){
// Insert the post and increase points.
Posts.insert({userId: Meteor.userId(), post: "The post."})
Meteor.users.update(Meteor.userId(), {$inc: {'profile.points': 5}})
}
if(Meteor.isServer){
Posts.allow({
insert: function(userId, doc){
check(doc, {
_id: String,
userId: String,
post: String
})
// You must be yourself.
if(doc.userId != userId){
return false
}
return true
}
})
Meteor.users.allow({
update: function(userId, doc, fieldNames, modifier){
check(modifier, {
$inc: {
'profile.points': Number
}
})
if(modifier.$inc['profile.points'] != 5){
return false
}
return true
}
})
}
What makes it bad? The user can increase his points without posting a post. Here's a better solution:
if(Meteor.isClient){
// Insert the post and increase points.
Method.call('postAndIncrease', {userId: Meteor.userId(), post: "The post."})
}
if(Meteor.isServer){
Meteor.methods({
postAndIncrease: function(post){
check(post, {
userId: String,
post: String
})
// You must be yourself.
if(post.userId != this.userId){
return false
}
Posts.insert(post)
Meteor.users.update(this.userId, {$inc: {'profile.points': 5}})
}
})
}
Better, but still bad. Why? Because of the latency (the post is created on the server, not the client). Here's a better solution:
if(Meteor.isClient){
// Insert the post and increase points.
Posts.insert({userId: Meteor.userId(), post: "The post."})
}
if(Meteor.isServer){
Posts.allow({
insert: function(userId, doc){
check(doc, {
_id: String,
userId: String,
post: String
})
// You must be yourself.
if(doc.userId != userId){
return false
}
return true
}
})
Posts.find().observe({
added: function(post){
// When new posts are added, the user gain the points.
Meteor.users.update(post.userId, {$inc: {'profile.points': 5}})
}
})
}
The only disadvantage this solution suffers from is the latency of the increment of the points, but it is something we must live with (at least at the moment). Using observe on the server may also be a disadvantage, but I think you can get pass it by using the package collection hooks instead.
We are building a chat application and are currently working on a system to see all the users in a given room.
We have a Mongo Document set up with an array of active_users where we will push and pull user names to in order to keep track of the online users. We have come to the conclusion that realizing a user has connected to a given room is fairly simple. All we need to do is in the router, when a user accesses the page, we push that user's name into the document.
Now the tricky part is realizing when that user has left that given page? Obviously jQuery isn't a reliable option, so how do we know when a user's connection to a specific page is broken?
You could do this:
Meteor.publish("page", function() {
this._session.socket.on("close", function() {
//Change your active users here
});
});
and for your page that you track
Meteor.subscribe('page');
I use this in the analytics package on atmosphere
There's an Atmosphere package called Presence that does exactly what you need.
Some extra details from the README about keeping track of custom states...
State functions
If you want to track more than just users' online state, you can set a custom state function. (The default state function returns just 'online'):
// Setup the state function on the client
Presence.state = function() {
return {
online: true,
currentRoomId: Session.get('currentRoomId')
};
}
Now we can simply query the collection to find all other users that share the same currentRoomId
Presences.find({ state: { online: true, currentRoomId: Session.get('currentRoomId') } })
Of course, presence will call your function reactively, so everyone will know as soon as things change.
Meteor has connection hooks so you can run a function when the user disconnects from the server. Setting the onClose() callback inside a method called by the client will allow you to close the userId in the function.
Code on the server could be like this:
Meteor.methods({
joinRoom: function( roomId ){
var self = this;
Rooms.update( {roomId: roomId}, {$push:{userId: self.userId}});
self.connection.onClose( function(){
Rooms.update( {roomId: roomId}, {$pull:{userId: self.userId}})
});
}
});
Having the following code on the server:
Meteor.publish(null, function(){
// Return some cursors.
})
will according to the documentation have the following effect: the record set is automatically sent to all connected clients.
How can I on the client side determine if all the documents published by this function has been received? If I would use a subscription instead, it would provide me with a ready callback, letting me know when all the documents been received. What's the matching way here? Or are the documents already received at the client when my client side code starts to execute?
I'm afraid there's no way to have a ready callback for so called universal subscriptions you mentioned above. Just have a look at this part of Meteor's code, where the publish and subscription logic is defined on the server. For convenience I'm copy/pasting the code below:
ready: function () {
var self = this;
if (self._isDeactivated())
return;
if (!self._subscriptionId)
return; // unnecessary but ignored for universal sub
if (!self._ready) {
self._session.sendReady([self._subscriptionId]);
self._ready = true;
}
}
The _subscriptionId is only given to named subscriptions, which are those that you would manually define using Meteor.subscribe method. The subscriptions corresponding to null publish functions, doesn't have their own _subscriptionId, so as you can see from the code above, the server is not event trying to send the ready message to the client.