Reputation: 101
I am trying to implement a voting system which is secure and cant be changed with a Meteor.call() from the client console. Everyone is able to vote some posts up and down if they are logged in. But just once a time for every user and post.
Ony my client side ive got something like this:
Template.postArgument.events({
'click .yes':function() {
if(Meteor.user()) {
var postId = Arguments.findOne({_id:this._id})
var currentArgumentId = this._id;
if($.inArray(Meteor.userId(), postId.votedUp) ===-1) {
if($.inArray(Meteor.userId(), postId.votedDown) !==-1) {
Meteor.call('argumentVoteYes',currentArgumentId);
} else {
Meteor.call('argumentVoteYesElse',currentArgumentId);
}
} else {
Meteor.call('argumentVoteYesElseElse',currentArgumentId);
}
}
}}
)};
On my Server:
Meteor.methods({
'argumentVoteYes':function(currentArgumentId){
Arguments.update(currentArgumentId, {
$pull: {votedDown: Meteor.userId()},
$inc: {score: 2 },
$addToSet: {votedUp: Meteor.userId() }
});
},
'argumentVoteYesElse':function(currentArgumentId){
Arguments.update(currentArgumentId, {
$inc: {score: 1 },
$addToSet: {votedUp: Meteor.userId() }
});
},
'argumentVoteYesElseElse':function(currentArgumentId){
Arguments.update(currentArgumentId, {
$inc: {score: -1 },
$pull: {votedUp: Meteor.userId()}
});
}
'argumentVoteNo':function(currentArgumentId){
Arguments.update(currentArgumentId, {
$pull: {votedUp: Meteor.userId()},
$inc: {score: -2 },
$addToSet: {votedDown: Meteor.userId() },
});
},
'argumentVoteNoElse':function(currentArgumentId){
Arguments.update(currentArgumentId, {
$inc: {score: -1 },
$addToSet: {votedDown: Meteor.userId() },
});
},
'argumentVoteNoElseElse':function(currentArgumentId){
Arguments.update(currentArgumentId, {
$inc: {score: 1 },
$pull: {votedDown: Meteor.userId()}
});
},
});
The question is how do i get this secure for example if someone calls a Meteor.call('argumentvoteYes', "someID" , {$inc: {score:2}});
It will increment the score of 2. If a user is calling this twice the vote will increment 4. Is there any way doing this a secure way?
Upvotes: 0
Views: 73
Reputation: 1178
I thought the same thing before and I found the solution in just using an array of userId's which you don't pass from client side (you just save current user's ID) and count total IDs in the array. It doesn't add the same ID twice.
From my code (you can do the rest of the checks like if the user is in disliker array, do something etc.):
likeActivity: function (activityId) {
Activities.update(activityId,{
$addToSet: {
likers: this.userId,
}
});
},
unlikeActivity: function (activityId) {
Activities.update(activityId,{
$pull: {
likers: this.userId,
}
})
}
In my helper:
likeCount: function() {
return this.likers.length
}
Upvotes: 0
Reputation: 20256
You don't have to worry about an extra increment coming in to the method since your methods only accept a single parameter. However you do need to guard against other hacks:
Let's first extend the Match
object so that we can check that an _id
is just that:
Match._id = Match.Where(function(id){
check(id, String); // first make sure we're dealing with a string
// then grep for an exactly 17 character alphanumeric string
return /^[a-zA-Z0-9]{17,17}/.test(id);
});
Now let's take your first method:
Meteor.methods({
'argumentVoteYes':function(currentArgumentId){
check(currentArgumentId,Match._id); // will error if not an _id
var post = Arguments.findOne({ _id: currentArgumentId });
var userId = Meteor.userId(); // the current user
if ( post && userId ){ // make sure a real user is operating on an actual document
// only update if no votes have been recorded OR
// user hasn't already voted
if ( !post.votedUp || post.votedUp.indexOf(userId) === -1 ){
Arguments.update(currentArgumentId, {
$pull: { votedDown: userId },
$inc: { score: 2 },
$addToSet: {votedUp: userId }
});
}
}
},
Upvotes: 1
Reputation: 4049
You need to check for whether the user is in the votedUp/down arrays on the server. You have the logic right client side, so just apply the same logic before you do the actual update on the server.
Upvotes: 1