From f7e7820806fb0d87231ca8f6fab6b759c9882381 Mon Sep 17 00:00:00 2001 From: Adam Butler Date: Wed, 22 Oct 2014 11:59:41 +0100 Subject: [PATCH 1/3] setup redis --- config/initializers/throttle.rb | 4 ++++ config/redis.yml | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 config/initializers/throttle.rb create mode 100644 config/redis.yml diff --git a/config/initializers/throttle.rb b/config/initializers/throttle.rb new file mode 100644 index 0000000..98545d7 --- /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 From d1be88356253e31729df9b998edcaab67a59a259 Mon Sep 17 00:00:00 2001 From: Adam Butler Date: Wed, 22 Oct 2014 12:02:52 +0100 Subject: [PATCH 2/3] add redis gem --- Gemfile | 2 ++ Gemfile.lock | 2 ++ config/initializers/throttle.rb | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) 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/config/initializers/throttle.rb b/config/initializers/throttle.rb index 98545d7..f2372aa 100644 --- a/config/initializers/throttle.rb +++ b/config/initializers/throttle.rb @@ -1,4 +1,4 @@ require "redis" -redis_conf = YAML.load(File.join(Rails.root, "config", "redis.yml")) +redis_conf = YAML.load(File.join(Rails.root, "config", "redis.yml")) REDIS = Redis.new(:host => redis_conf["host"], :port => redis_conf["port"]) From 3bf699e8aed7a7c03e42f06d3e02989a81422f6b Mon Sep 17 00:00:00 2001 From: Adam Butler Date: Wed, 22 Oct 2014 13:12:43 +0100 Subject: [PATCH 3/3] Implement throttling --- app/controllers/votes_controller.rb | 38 +++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) 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