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?
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 }
});
}
}
},