Skip to content

Instantly share code, notes, and snippets.

@ben-gy
Last active April 23, 2019 03:11
Show Gist options
  • Save ben-gy/92bb5c872921830e26651647330478a6 to your computer and use it in GitHub Desktop.
Save ben-gy/92bb5c872921830e26651647330478a6 to your computer and use it in GitHub Desktop.
How I usually setup a new Rails app

New repo setup

Read the documentation and install each of the following gems after initialising a new rails application. I have provided some initialiser snippets from one of my Rails 5.2 projects with configuration that I'd like to be setup, but please read the documentation to verify the snippets are still constructed correctly based on current gem versions today.

  • install pg (remove sqlite)
  • install paper_trail
  • install act_as_paranoid
  • install rack-attack
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 blacklisting and
  # whitelisting). 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

  throttle("logins/ip", limit: 20, period: 1.hour) do |req|
    req.ip if req.post? && req.path.start_with?("/users/sign_in")
  end
end

ActiveSupport::Notifications.subscribe("rack.attack") do |name, start, finish, request_id, req|
  puts "Throttled #{req.env["rack.attack.match_discriminator"]}"
end
  • install simple_form
  • install sassc-rails
  • install normalise-rails
  • install autoprefixer-rails
  • install flexbox_rb
  • install autosize
  • install puma
  • install pundit
  • install groupify
  • install logstop
  • install ip_anonymizer
  • install ahoy_matey
class Ahoy::Store < Ahoy::DatabaseStore
end

# set to true for JavaScript tracking
Ahoy.api = false

# better user agent parsing
Ahoy.user_agent_parser = :device_detector

# GDPR compliance mask IP addresses
Ahoy.mask_ips = true

# GPDR compliance don't track cookies
Ahoy.cookies = false
  • install ahoy_email (setup for click tracking and email opening)
AhoyEmail.api = true

class EmailSubscriber
  def open(event)
    event[:controller].ahoy.track 'Email opened', message_id: event[:message].id
  end

  def click(event)
    event[:controller].ahoy.track 'Email clicked', message_id: event[:message].id, url: event[:url]
  end
end

AhoyEmail.subscribers << EmailSubscriber.new
  • install devise (deploy a user model)
  • install geocoder
  • install authtrail
AuthTrail::GeocodeJob.queue_as :low
  • install slim-rails
  • install notable
# Only run on production
Notable.enabled = Rails.env.production?

# GDPR compliance
Notable.mask_ips = true
  • install client_side_validations && client_side_validations-simple_form
# frozen_string_literal: true
# ClientSideValidations Initializer

# Disabled validators
# ClientSideValidations::Config.disabled_validators = []

# Uncomment to validate number format with current I18n locale
# ClientSideValidations::Config.number_format_with_locale = true

# Uncomment the following block if you want each input field to have the validation messages attached.
#
# Note: client_side_validation requires the error to be encapsulated within
# <label for="#{instance.send(:tag_id)}" class="message"></label>
#
ActionView::Base.field_error_proc = proc do |html_tag, instance|
  if html_tag =~ /^<label/
    %(<div class="field_with_errors">#{html_tag}</div>).html_safe
  else
    %(<div class="field_with_errors">#{html_tag}<label for="#{instance.send(:tag_id)}" class="message">#{instance.error_message.first}</label></div>).html_safe
  end
end

Heroku setup

  • setup Procfile
web:      bundle exec puma -C config/puma.rb
worker:   bundle exec rake jobs:work
release:  bundle exec rake db:migrate
  • deploy to Heroku

Addons & Env vars

  • setup sendgrid addon and install sendgrid-ruby
  • setup new relic addon and install new relic_rpm
  • setup logdna addon
  • setup sentry addon and install sentry-raven
Raven.configure do |config|
  config.environments = ['staging', 'production']
  config.current_environment = ENV['CURRENT_ENVIRONMENT']
end

Testing setup

  • setup Heroku CI automatic testing
  • setup Heroku CI review apps (but configure to not auto-create review apps)
  • setup auto deploy from master for staging server
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment