Jack Moore

Email: jack(at)jmoore53.com
Project Updates

Rails First Party Analytics and Rack::Attack

09 Jun 2020 » rails, ruby, analytics, tracking, first party tracking, kickstarter

I need first party analytics.. Ahoy is right there making it easy, and Rack::Attack is better.

Rack Attack Preventing DDOS Attacks

config/initializers/rack_attack.rb

class Rack::Attack

    ### Configure Cache ###
  
    # If you don't want to use Rails.cache (Rack::Attack's default), then
    # configure it here.
    #
    # Note: The store is only used for throttling (not blocklisting and
    # safelisting). It must implement .increment and .write like
    # ActiveSupport::Cache::Store
  
    # Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new 
  
    ### Throttle Spammy Clients ###
  
    # If any single client IP is making tons of requests, then they're
    # probably malicious or a poorly-configured scraper. Either way, they
    # don't deserve to hog all of the app server's CPU. Cut them off!
    #
    # Note: If you're serving assets through rack, those requests may be
    # counted by rack-attack and this throttle may be activated too
    # quickly. If so, enable the condition to exclude them from tracking.
  
    # Throttle all requests by IP (60rpm)
    #
    # Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}"
    throttle('req/ip', limit: 300, period: 5.minutes) do |req|
      req.ip # unless req.path.start_with?('/assets')
    end
  
    ### Prevent Brute-Force Login Attacks ###
  
    # The most common brute-force login attack is a brute-force password
    # attack where an attacker simply tries a large number of emails and
    # passwords to see if any credentials match.
    #
    # Another common method of attack is to use a swarm of computers with
    # different IPs to try brute-forcing a password for a specific account.
  
    # Throttle POST requests to /login by IP address
    #
    # Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}"
    throttle('logins/ip', limit: 5, period: 20.seconds) do |req|
      if req.path == '/login' && req.post?
        req.ip
      end
    end
  
    # Throttle POST requests to /login by email param
    #
    # Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{req.email}"
    #
    # Note: This creates a problem where a malicious user could intentionally
    # throttle logins for another user and force their login requests to be
    # denied, but that's not very common and shouldn't happen to you. (Knock
    # on wood!)
    throttle("logins/email", limit: 5, period: 20.seconds) do |req|
      if req.path == '/login' && req.post?
        # return the email if present, nil otherwise
        req.params['email'].presence
      end
    end
  
    ### Custom Throttle Response ###
  
    # By default, Rack::Attack returns an HTTP 429 for throttled responses,
    # which is just fine.
    #
    # If you want to return 503 so that the attacker might be fooled into
    # believing that they've successfully broken your app (or you just want to
    # customize the response), then uncomment these lines.
    # self.throttled_response = lambda do |env|
    #  [ 503,  # status
    #    {},   # headers
    #    ['']] # body
    # end
end

Enabling rails cache to test Rack::Attack

rails dev:cache

Bash Script to run against localhost and test rack attack:

x=0
while [ $x -le 350 ]
do
  curl http://localhost:3000/commandcenter
  x=$(( $x + 1))
  echo "X is: $x"
done

Essentially this bash script should run against localhost and cut you off at the 300th request.

Ahoy Tracking Visits

Add Gem to Gemfile:

gem 'ahoy_matey'

Bundle install and run installing to the gem

bundle install
rails generate ahoy:install
rails:db:migrate

Tracking Visits by default

class ApplicationController < ActionController::Base
    after_action :track_action

    protected
  
    def track_action
      ahoy.track "Ran action", request.filtered_parameters.to_json
    end
end

List Events that are part of a visit from rails c

Ahoy::Visit.last.events.all.each do |f|
    puts JSON.parse(f.properties)["controller"]
end
© Jack Moore