diff --git a/Gemfile b/Gemfile index d8ad95f6f4..8f2d9ec486 100644 --- a/Gemfile +++ b/Gemfile @@ -142,6 +142,8 @@ gem "private_address_check" gem 'newrelic_rpm' +gem 'invisible_captcha' + group :production, :staging do gem 'sd_notify' # For better Systemd process management. Used by Puma. end diff --git a/Gemfile.lock b/Gemfile.lock index c91ce3153c..d1e54ff573 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -344,6 +344,8 @@ GEM ruby-vips (>= 2.0.17, < 3) immigrant (0.3.6) activerecord (>= 3.0) + invisible_captcha (2.1.0) + rails (>= 5.2) io-console (0.7.1) ipaddress (0.8.3) irb (1.11.0) @@ -862,6 +864,7 @@ DEPENDENCIES i18n-js (~> 3.9.0) image_processing immigrant + invisible_captcha jquery-rails (= 4.4.0) jquery-ui-rails (~> 4.2) json diff --git a/app/controllers/spree/users_controller.rb b/app/controllers/spree/users_controller.rb index 6fdcebe89b..60b7b33e26 100644 --- a/app/controllers/spree/users_controller.rb +++ b/app/controllers/spree/users_controller.rb @@ -8,6 +8,7 @@ module Spree layout 'darkswarm' + invisible_captcha only: [:create], on_timestamp_spam: :render_alert_timestamp_error_message skip_before_action :set_current_order, only: :show prepend_before_action :load_object, only: [:show, :edit, :update] prepend_before_action :authorize_actions, only: :new @@ -101,5 +102,16 @@ module Spree def user_params ::PermittedAttributes::User.new(params).call end + + def render_alert_timestamp_error_message + render cable_ready: cable_car.inner_html( + "#signup-feedback", + partial("layouts/alert", + locals: { + type: "alert", + message: InvisibleCaptcha.timestamp_error_message + }) + ) + end end end diff --git a/app/views/layouts/_signup_tab.html.haml b/app/views/layouts/_signup_tab.html.haml index 5d2af646e5..f25bf2024d 100644 --- a/app/views/layouts/_signup_tab.html.haml +++ b/app/views/layouts/_signup_tab.html.haml @@ -23,3 +23,4 @@ .row .large-12.columns = form.submit t(:action_signup), { class: "button primary", tabindex: 4 } + = form.invisible_captcha diff --git a/config/locales/en.yml b/config/locales/en.yml index 63b9b07969..6f3341ee7d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4787,3 +4787,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using pagination: next: Next previous: Previous + + + # Gem to prevent bot form submissions + invisible_captcha: + sentence_for_humans: "Please leave empty" + timestamp_error_message: "Please try again after 5 seconds." diff --git a/spec/base_spec_helper.rb b/spec/base_spec_helper.rb index f1d4cc7818..c875249ab7 100644 --- a/spec/base_spec_helper.rb +++ b/spec/base_spec_helper.rb @@ -55,6 +55,9 @@ I18n.exception_handler = proc do |exception, *_| raise exception.to_exception end +# Disable timestamp check for test environment +InvisibleCaptcha.timestamp_enabled = false + RSpec.configure do |config| # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures config.fixture_path = "#{Rails.root.join('spec/fixtures')}" diff --git a/spec/controllers/spree/users_controller_spec.rb b/spec/controllers/spree/users_controller_spec.rb index 3837cf8b71..8f6455055c 100644 --- a/spec/controllers/spree/users_controller_spec.rb +++ b/spec/controllers/spree/users_controller_spec.rb @@ -7,7 +7,7 @@ describe Spree::UsersController, type: :controller do include AuthenticationHelper - describe "show" do + describe "#show" do let!(:u1) { create(:user) } let!(:u2) { create(:user) } let!(:distributor1) { create(:distributor_enterprise) } @@ -55,7 +55,7 @@ describe Spree::UsersController, type: :controller do end end - describe "registered_email" do + describe "#registered_email" do routes { Openfoodnetwork::Application.routes } let!(:user) { create(:user) } @@ -71,16 +71,16 @@ describe Spree::UsersController, type: :controller do end end - context '#load_object' do - it 'should redirect to signup path if user is not found' do + describe '#load_object' do + it 'redirects to signup path if user is not found' do allow(controller).to receive_messages(spree_current_user: nil) put :update, params: { user: { email: 'foobar@example.com' } } expect(response).to redirect_to('/login') end end - context '#create' do - it 'should create a new user' do + describe '#create' do + it 'creates a new user' do post :create, params: { user: { email: 'foobar@example.com', password: 'foobar123', password_confirmation: 'foobar123', locale: 'es' } } diff --git a/spec/system/consumer/authentication_spec.rb b/spec/system/consumer/authentication_spec.rb index 7951704440..7062d99a2e 100644 --- a/spec/system/consumer/authentication_spec.rb +++ b/spec/system/consumer/authentication_spec.rb @@ -102,7 +102,7 @@ describe "Authentication" do end it "Failing to sign up because password confirmation doesn't match or is blank" do - fill_in "Your email", with: user.email + fill_in "Your email", with: "test@foo.com" fill_in "Choose a password", with: "ForgotToRetype" click_signup_button expect(page).to have_content "doesn't match" @@ -120,6 +120,35 @@ describe "Authentication" do 'your account.' end.to enqueue_job ActionMailer::MailDeliveryJob end + + describe "invisible_captcha gem" do + around do |example| + InvisibleCaptcha.timestamp_enabled = true + InvisibleCaptcha.timestamp_threshold = 30 + example.run + InvisibleCaptcha.timestamp_enabled = false + end + + it "Failing to sign up because the user is too quick" do + fill_in "Your email", with: "test@foo.com" + fill_in "Choose a password", with: "test12345" + fill_in "Confirm password", with: "test12345" + click_signup_button + + expect(page).to have_content "Please try again after 5 seconds." + end + + it "succeeding after time threshold" do + Timecop.travel(30.seconds.from_now) do + fill_in "Your email", with: "test@foo.com" + fill_in "Choose a password", with: "test12345" + fill_in "Confirm password", with: "test12345" + click_signup_button + + expect(page).to have_content 'A message with a confirmation link has been sent' + end + end + end end describe "forgetting passwords" do diff --git a/spec/system/consumer/multilingual_spec.rb b/spec/system/consumer/multilingual_spec.rb index 87886017ca..1020d81601 100644 --- a/spec/system/consumer/multilingual_spec.rb +++ b/spec/system/consumer/multilingual_spec.rb @@ -27,7 +27,7 @@ describe 'Multilingual' do visit root_path expect(get_i18n_locale).to eq 'en' expect(get_i18n_translation('label_shops')).to eq 'Shops' - expect(cookies).to be_empty + expect(cookies_name).not_to include('locale') expect(page).to have_content 'SHOPS' visit root_path(locale: 'es')