diff --git a/Gemfile b/Gemfile index 2534e5a..4e28baa 100644 --- a/Gemfile +++ b/Gemfile @@ -41,6 +41,8 @@ gem "autoprefixer-rails" gem 'rails-assets-angular', "1.3.0.rc.4" gem 'pusher' +gem 'redis' + group :production, :staging do gem 'pg' gem 'rails_12factor' diff --git a/Gemfile.lock b/Gemfile.lock index 9eaced5..557f02e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -101,6 +101,7 @@ GEM rake (10.3.2) rdoc (4.1.2) json (~> 1.4) + redis (3.1.0) sass (3.2.19) sass-rails (4.0.3) railties (>= 4.0.0, < 5.0) @@ -149,6 +150,7 @@ DEPENDENCIES rails (= 4.1.6) rails-assets-angular (= 1.3.0.rc.4) rails_12factor + redis sass-rails (~> 4.0.3) sdoc (~> 0.4.0) spring diff --git a/app/controllers/votes_controller.rb b/app/controllers/votes_controller.rb index 1e854ff..096c54b 100644 --- a/app/controllers/votes_controller.rb +++ b/app/controllers/votes_controller.rb @@ -1,17 +1,18 @@ class VotesController < ApplicationController before_filter :set_question, only: [:show] + before_filter :set_question_id, only: [:update] + before_filter :set_vote_id, only: [:update] + before_filter :throttle, only: [:update] def update - question_id = params[:vote][:question_id] - vote_id = cookies["vote_#{question_id}"] - vote = Vote.where({secret: vote_id}).first_or_initialize + vote = Vote.where({secret: @vote_id}).first_or_initialize - question = Question.where({secret: question_id}).first + question = Question.where({secret: @question_id}).first vote.secret = SecureRandom.urlsafe_base64(nil, false) unless vote.secret vote.question_id = question.id unless vote.question_id vote.option_id = Option.find(params[:vote][:option_id]).id - Pusher[question_id].trigger("vote", {}) + Pusher[@question_id].trigger("vote", {}) if vote.save! cookies.permanent["vote_#{question.secret}"] = vote.secret @@ -26,6 +27,25 @@ def update def show end + def throttle + unless Vote.where({secret: @vote_id}).exists? + client_ip = env["REMOTE_ADDR"] + agent_hash = Digest::MD5.hexdigest(env["HTTP_USER_AGENT"]) + key = "vote:#{client_ip}-#{agent_hash}" + count = REDIS.get(key) + unless count + REDIS.set(key, 0) + REDIS.expire(key, 10) + end + + if count.to_i >= 1 + render nothing: true, status: 429 + else + REDIS.incr(key) + end + end + end + private def set_question @@ -35,4 +55,12 @@ def set_question def question_params params.require(:question).permit(:title) end + + def set_question_id + @question_id = params[:vote][:question_id] + end + + def set_vote_id + @vote_id = cookies["vote_#{@question_id}"] + end end diff --git a/config/initializers/throttle.rb b/config/initializers/throttle.rb new file mode 100644 index 0000000..f2372aa --- /dev/null +++ b/config/initializers/throttle.rb @@ -0,0 +1,4 @@ +require "redis" + +redis_conf = YAML.load(File.join(Rails.root, "config", "redis.yml")) +REDIS = Redis.new(:host => redis_conf["host"], :port => redis_conf["port"]) diff --git a/config/redis.yml b/config/redis.yml new file mode 100644 index 0000000..8bc241a --- /dev/null +++ b/config/redis.yml @@ -0,0 +1,2 @@ +host: localhost +port: 6379