diff --git a/Gemfile b/Gemfile index 4dbf8249a..3377bc729 100644 --- a/Gemfile +++ b/Gemfile @@ -47,6 +47,7 @@ gem 'i18n_data' gem 'i18n-tasks', require: false gem 'iban-tools' gem 'image_processing' +gem 'invisible_captcha' gem 'json_schemer' gem 'jwt' gem 'kaminari', '1.2.1' # Pagination diff --git a/Gemfile.lock b/Gemfile.lock index 6cba2450d..809cb6fd6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -369,6 +369,8 @@ GEM image_processing (1.12.1) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) + invisible_captcha (2.0.0) + rails (>= 5.0) ipaddress (0.8.3) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) @@ -838,6 +840,7 @@ DEPENDENCIES i18n_data iban-tools image_processing + invisible_captcha json_schemer jwt kaminari (= 1.2.1) diff --git a/app/controllers/support_controller.rb b/app/controllers/support_controller.rb index a83b5e438..b46d7183c 100644 --- a/app/controllers/support_controller.rb +++ b/app/controllers/support_controller.rb @@ -1,4 +1,6 @@ class SupportController < ApplicationController + invisible_captcha only: [:create], on_spam: :redirect_to_root + def index setup_context end @@ -92,4 +94,8 @@ class SupportController < ApplicationController def email current_user&.email || params[:email] end + + def redirect_to_root + redirect_to root_path, alert: t('invisible_captcha.custom_message') + end end diff --git a/app/views/support/index.html.haml b/app/views/support/index.html.haml index 3a48e767a..5c7eda7d2 100644 --- a/app/views/support/index.html.haml +++ b/app/views/support/index.html.haml @@ -25,6 +25,9 @@ = label_tag :type do = t('.your_question') = hidden_field_tag :type, params[:type] + + = invisible_captcha + %dl - @options.each do |(question, question_type, link)| %dt diff --git a/config/env.example b/config/env.example index d00111dd7..43a72d01d 100644 --- a/config/env.example +++ b/config/env.example @@ -122,3 +122,5 @@ API_EDUCATION_URL="https://data.education.gouv.fr/api/records/1.0" # Clé de chriffrement des données sensibles en base ENCRYPTION_SERVICE_SALT="" + +INVISIBLE_CAPTCHA_SECRET="kikooloool" diff --git a/config/initializers/invisible_captcha.rb b/config/initializers/invisible_captcha.rb new file mode 100644 index 000000000..141f247fb --- /dev/null +++ b/config/initializers/invisible_captcha.rb @@ -0,0 +1,12 @@ +InvisibleCaptcha.setup do |config| + # config.honeypots << ['more', 'fake', 'attribute', 'names'] + # config.visual_honeypots = false + # config.timestamp_threshold = 2 + config.timestamp_enabled = !Rails.env.test? + # config.injectable_styles = false + config.spinner_enabled = !Rails.env.test? + + # Leave these unset if you want to use I18n (see below) + # config.sentence_for_humans = 'If you are a human, ignore this field' + # config.timestamp_error_message = 'Sorry, that was too quick! Please resubmit.' +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 19faf01e7..e8d25670f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -30,6 +30,9 @@ # available at http://guides.rubyonrails.org/i18n.html. en: + invisible_captcha: + custom_message: 'If you are a human, ignore this field' + help: 'Help' utils: 'yes': Yes diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 4f61afc34..e5947ea20 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -20,6 +20,9 @@ # available at http://guides.rubyonrails.org/i18n.html. fr: + invisible_captcha: + custom_message: 'Si vous êtes un humain, veuillez ignorer ce champs' + help: 'Aide' utils: 'yes': Oui diff --git a/spec/controllers/support_controller_spec.rb b/spec/controllers/support_controller_spec.rb index 72ebfcff0..1b7737469 100644 --- a/spec/controllers/support_controller_spec.rb +++ b/spec/controllers/support_controller_spec.rb @@ -51,30 +51,11 @@ describe SupportController, type: :controller do describe "send form" do subject do - post :create, params: { subject: 'bonjour', text: 'un message' } + post :create, params: params end - it 'creates a conversation on HelpScout' do - expect_any_instance_of(Helpscout::FormAdapter).to receive(:send_form).and_return(true) - - expect { subject }.to change(Commentaire, :count).by(0) - - expect(flash[:notice]).to match('Votre message a été envoyé.') - expect(response).to redirect_to root_path(formulaire_contact_general_submitted: true) - end - - context 'when a drafted dossier is mentionned' do - let(:dossier) { create(:dossier) } - let(:user) { dossier.user } - - subject do - post :create, params: { - dossier_id: dossier.id, - type: Helpscout::FormAdapter::TYPE_INSTRUCTION, - subject: 'bonjour', - text: 'un message' - } - end + context "when invisible captcha is ignored" do + let(:params) { { subject: 'bonjour', text: 'un message' } } it 'creates a conversation on HelpScout' do expect_any_instance_of(Helpscout::FormAdapter).to receive(:send_form).and_return(true) @@ -84,33 +65,64 @@ describe SupportController, type: :controller do expect(flash[:notice]).to match('Votre message a été envoyé.') expect(response).to redirect_to root_path(formulaire_contact_general_submitted: true) end - end - context 'when a submitted dossier is mentionned' do - let(:dossier) { create(:dossier, :en_construction) } - let(:user) { dossier.user } + context 'when a drafted dossier is mentionned' do + let(:dossier) { create(:dossier) } + let(:user) { dossier.user } - subject do - post :create, params: { - dossier_id: dossier.id, - type: Helpscout::FormAdapter::TYPE_INSTRUCTION, - subject: 'bonjour', - text: 'un message' - } + subject do + post :create, params: { + dossier_id: dossier.id, + type: Helpscout::FormAdapter::TYPE_INSTRUCTION, + subject: 'bonjour', + text: 'un message' + } + end + + it 'creates a conversation on HelpScout' do + expect_any_instance_of(Helpscout::FormAdapter).to receive(:send_form).and_return(true) + + expect { subject }.to change(Commentaire, :count).by(0) + + expect(flash[:notice]).to match('Votre message a été envoyé.') + expect(response).to redirect_to root_path(formulaire_contact_general_submitted: true) + end end - it 'posts the message to the dossier messagerie' do - expect_any_instance_of(Helpscout::FormAdapter).not_to receive(:send_form) + context 'when a submitted dossier is mentionned' do + let(:dossier) { create(:dossier, :en_construction) } + let(:user) { dossier.user } - expect { subject }.to change(Commentaire, :count).by(1) + subject do + post :create, params: { + dossier_id: dossier.id, + type: Helpscout::FormAdapter::TYPE_INSTRUCTION, + subject: 'bonjour', + text: 'un message' + } + end - expect(Commentaire.last.email).to eq(user.email) - expect(Commentaire.last.dossier).to eq(dossier) - expect(Commentaire.last.body).to include('[bonjour]') - expect(Commentaire.last.body).to include('un message') + it 'posts the message to the dossier messagerie' do + expect_any_instance_of(Helpscout::FormAdapter).not_to receive(:send_form) - expect(flash[:notice]).to match('Votre message a été envoyé sur la messagerie de votre dossier.') - expect(response).to redirect_to messagerie_dossier_path(dossier) + expect { subject }.to change(Commentaire, :count).by(1) + + expect(Commentaire.last.email).to eq(user.email) + expect(Commentaire.last.dossier).to eq(dossier) + expect(Commentaire.last.body).to include('[bonjour]') + expect(Commentaire.last.body).to include('un message') + + expect(flash[:notice]).to match('Votre message a été envoyé sur la messagerie de votre dossier.') + expect(response).to redirect_to messagerie_dossier_path(dossier) + end + end + end + + context "when invisible captcha is filled" do + let(:params) { { subject: 'bonjour', text: 'un message', InvisibleCaptcha.honeypots.sample => 'boom' } } + it 'does not create a conversation on HelpScout' do + expect { subject }.not_to change(Commentaire, :count) + expect(flash[:alert]).to eq(I18n.t('invisible_captcha.custom_message')) end end end