diff --git a/.env b/.env index b863316b..d852c2e4 100644 --- a/.env +++ b/.env @@ -1,2 +1,4 @@ export DEVISE_SECRET_KEY=3f4915489bd10fdbacb4f22dbf772a4be6e2d2d1616a1af7913f0f6645784ddffd6a71ab9f69a99ed7941f16d3092e1872bc4bc9add5a0615c630c90f94fc032 export RAILS_SECRET_KEY=4944cf251e3dbf309ed71ebcd8990a1479d793011cd4011761e3fbea9ecc59edefd0cb49a0ed9b5c0261ab0dda841962bb7dd28fd3f99579bfa2beec26329961 +export RECAPTCHA_SITE_KEY=6Le_W5gUAAAAAJFOELNu2LkSR2E6sXYIVZrMe6V0 +export RECAPTCHA_SECRET_KEY=6Le_W5gUAAAAABZpnGQtfVaQdfluuLrf8wihooeo diff --git a/app/controllers/contacts_controller.rb b/app/controllers/contacts_controller.rb index 137b9f1c..36719daf 100644 --- a/app/controllers/contacts_controller.rb +++ b/app/controllers/contacts_controller.rb @@ -1,3 +1,5 @@ +require_relative '../helpers/recaptcha_helper' + class ContactsController < ApplicationController def new @contact = Contact.new(restroom_id: params['restroom_id'], restroom_name: params['restroom_name']) @@ -5,13 +7,23 @@ def new def create @contact = Contact.new(params[:contact]) + unless @contact.valid? + flash.now[:error] = I18n.t('contacts.submitted.cannot-send') + render :new + return + end + + # Verify recaptcha code + recaptcha_response = params['g-recaptcha-response'] + unless RecaptchaHelper.valid_token? recaptcha_response + flash.now[:error] = I18n.t('helpers.reCAPTCHA.failed') + render :new + return + end + @contact.request = request - if @contact.deliver - flash.now[:error] = nil - flash.now[:notice] = I18n.t('contacts.submitted.thank-you-exclamation') - else - flash.now[:error] = I18n.t('contacts.submitted.cannot-send') - render :new - end + @contact.deliver + flash.now[:error] = nil + flash.now[:notice] = I18n.t('contacts.submitted.thank-you-exclamation') end end diff --git a/app/controllers/restrooms_controller.rb b/app/controllers/restrooms_controller.rb index 9834b596..3139d53e 100644 --- a/app/controllers/restrooms_controller.rb +++ b/app/controllers/restrooms_controller.rb @@ -1,3 +1,5 @@ +require_relative '../helpers/recaptcha_helper' + class RestroomsController < ApplicationController respond_to :html, :json @@ -27,8 +29,17 @@ def new end def create - restroom = Restroom.new(permitted_params) - @restroom = SaveRestroom.new(restroom).call + @restroom = Restroom.new(permitted_params) + + # Verify recaptcha code + recaptcha_response = params['g-recaptcha-response'] + unless RecaptchaHelper.valid_token? recaptcha_response + flash.now[:error] = I18n.t('helpers.reCAPTCHA.failed') + render 'new' + return + end + + @restroom = SaveRestroom.new(@restroom).call if @restroom.errors.empty? if @restroom.approved? diff --git a/app/helpers/recaptcha_helper.rb b/app/helpers/recaptcha_helper.rb new file mode 100644 index 00000000..fb026f2d --- /dev/null +++ b/app/helpers/recaptcha_helper.rb @@ -0,0 +1,25 @@ +require 'net/http' +require 'uri' +require 'json' + +module RecaptchaHelper + def self.valid_token?(token) + # Get secret from env + secret = ENV['RECAPTCHA_SECRET_KEY'] + + uri = URI('https://www.google.com/recaptcha/api/siteverify') + request = Net::HTTP::Post.new(uri) + request.set_form_data('secret' => secret, 'response' => token) + response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |https| + https.request(request) + end + + # Check response + json_body = JSON.parse(response.body) + if json_body['success'] + true + else + false + end + end +end diff --git a/app/views/contacts/new.html.haml b/app/views/contacts/new.html.haml index 5412c587..398ffda7 100644 --- a/app/views/contacts/new.html.haml +++ b/app/views/contacts/new.html.haml @@ -11,5 +11,7 @@ = f.hidden_field :restroom_id, :required => false = f.hidden_field :restroom_name, :required => false = f.hidden_field :nickname, :hint => t('.leave-this-field-blank') + .form-group + .g-recaptcha{'data-sitekey' => "#{ENV['RECAPTCHA_SITE_KEY']}"} .form-actions = f.button :submit, :class=> "linkbutton" diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index a27cff28..5bd29b94 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -7,6 +7,7 @@ %meta{:content => "initial-scale=1, maximum-scale=1", :name => "viewport"}/ %script{:src => "https://maps.googleapis.com/maps/api/js?v=quarterly&libraries=places&key=AIzaSyBXcCrqzMlm-ZmtNQve7AuipNdE4vySUF0"} += javascript_include_tag "https://www.google.com/recaptcha/api.js", :async => true, :defer => true = stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true = javascript_pack_tag "application", "data-turbolinks-track": "reload" = csrf_meta_tags diff --git a/app/views/restrooms/_formsubmit.html.haml b/app/views/restrooms/_formsubmit.html.haml index f8c9cdaf..64f2e0aa 100644 --- a/app/views/restrooms/_formsubmit.html.haml +++ b/app/views/restrooms/_formsubmit.html.haml @@ -28,6 +28,9 @@ = f.input :directions, :placeholder => t('restroom.directions_hint'), :as => :text, :required => false, :input_html => { :class => "span6" } = f.input :comment, :placeholder => t('restroom.comments_hint'), :as => :text, :required => false, :input_html => { :class => "span6" } + .form-group + .g-recaptcha{'data-sitekey' => "#{ENV['RECAPTCHA_SITE_KEY']}"} + / Click to preview location %button{type: 'button', class: 'preview-btn linkbutton'} = t('restroom.preview') diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb index d99e5cd9..025f6387 100644 --- a/config/initializers/simple_form.rb +++ b/config/initializers/simple_form.rb @@ -111,7 +111,7 @@ # in this configuration, which is recommended due to some quirks from different browsers. # To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations, # change this configuration to true. - config.browser_validations = false + config.browser_validations = true # Collection of methods to detect if a file type was given. # config.file_methods = [ :mounted_as, :file?, :public_filename ] diff --git a/config/locales/en/simple_form.en.yml b/config/locales/en/simple_form.en.yml index c6a9e1b1..142b453a 100644 --- a/config/locales/en/simple_form.en.yml +++ b/config/locales/en/simple_form.en.yml @@ -34,6 +34,8 @@ en: message: 'Message' helpers: + reCAPTCHA: + failed: 'reCAPTCHA required.' submit: contact: # This is the "Send message" button on the "Contact" page. diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb index 88aec250..2fdbfad3 100644 --- a/spec/support/rspec.rb +++ b/spec/support/rspec.rb @@ -1,6 +1,7 @@ require 'capybara/poltergeist' require 'capybara/rspec' require 'rspec/rails' +require 'json' # spec/spec_helper.rb # @@ -25,6 +26,11 @@ .with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}) .to_return(:status => 200, :body => File.new("#{Rails.root}/spec/fixtures/guess_in_oakland.json"), :headers => {}) + recaptcha_response = {'success' => true} + stub_request(:post, 'https://www.google.com/recaptcha/api/siteverify') + .to_return(status: 200, body: recaptcha_response.to_json, + headers: {'Content-Type' => 'application/json'}) + # Akismet response for spam stub_request(:post, /.*.rest.akismet.com\/1.1\/comment-check/). with(body: /^.*Spam.*$/).