diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f20616..e139fe4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## [Unreleased] + +- New `honeypot_enabled` config option (default true). + ## [2.3.0] - Run honeypot + spinner checks and their callback also if timestamp triggers but passes through (#132) diff --git a/README.md b/README.md index 2b12cd7..d855424 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ You can customize: - `timestamp_error_message`: flash error message thrown when form submitted quicker than the `timestamp_threshold` value. It uses I18n by default. - `injectable_styles`: if enabled, you should call anywhere in your layout the following helper `<%= invisible_captcha_styles %>`. This allows you to inject styles, for example, in ``. False by default, styles are injected inline with the honeypot. - `spinner_enabled`: option to disable the IP spinner validation. By default, true. +- `honeypot_enabled`: option to disable the honeypot field at the application level. By default, true. - `secret`: customize the secret key to encode some internal values. By default, it reads the environment variable `ENV['INVISIBLE_CAPTCHA_SECRET']` and fallbacks to random value. Be careful, if you are running multiple Rails instances behind a load balancer, use always the same value via the environment variable. To change these defaults, add the following to an initializer (recommended `config/initializers/invisible_captcha.rb`): @@ -130,6 +131,7 @@ InvisibleCaptcha.setup do |config| # config.timestamp_enabled = true # config.injectable_styles = false # config.spinner_enabled = true + # config.honeypot_enabled = true # Leave these unset if you want to use I18n (see below) # config.sentence_for_humans = 'If you are a human, ignore this field' diff --git a/lib/invisible_captcha.rb b/lib/invisible_captcha.rb index 6d2b324..e447108 100644 --- a/lib/invisible_captcha.rb +++ b/lib/invisible_captcha.rb @@ -17,6 +17,7 @@ class << self :visual_honeypots, :injectable_styles, :spinner_enabled, + :honeypot_enabled, :secret def init! @@ -42,6 +43,9 @@ def init! # Spinner check enabled by default self.spinner_enabled = true + # Honeypot check enabled by default + self.honeypot_enabled = true + # A secret key to encode some internal values self.secret = ENV['INVISIBLE_CAPTCHA_SECRET'] || SecureRandom.hex(64) end diff --git a/lib/invisible_captcha/controller_ext.rb b/lib/invisible_captcha/controller_ext.rb index a816991..038b2b1 100644 --- a/lib/invisible_captcha/controller_ext.rb +++ b/lib/invisible_captcha/controller_ext.rb @@ -85,6 +85,8 @@ def spinner_spam? end def honeypot_spam?(options = {}) + return false unless InvisibleCaptcha.honeypot_enabled + honeypot = options[:honeypot] scope = options[:scope] || controller_name.singularize diff --git a/lib/invisible_captcha/view_helpers.rb b/lib/invisible_captcha/view_helpers.rb index bbd7981..73cac5f 100644 --- a/lib/invisible_captcha/view_helpers.rb +++ b/lib/invisible_captcha/view_helpers.rb @@ -33,14 +33,17 @@ def invisible_captcha_styles private def build_invisible_captcha(honeypot = nil, scope = nil, options = {}) - if honeypot.is_a?(Hash) - options = honeypot - honeypot = nil - end + return ''.html_safe unless InvisibleCaptcha.honeypot_enabled || InvisibleCaptcha.spinner_enabled - honeypot = honeypot ? honeypot.to_s : InvisibleCaptcha.get_honeypot - label = options.delete(:sentence_for_humans) || InvisibleCaptcha.sentence_for_humans - css_class = "#{honeypot}_#{Time.zone.now.to_i}" + if InvisibleCaptcha.honeypot_enabled + if honeypot.is_a?(Hash) + options = honeypot + honeypot = nil + end + honeypot = honeypot ? honeypot.to_s : InvisibleCaptcha.get_honeypot + label = options.delete(:sentence_for_humans) || InvisibleCaptcha.sentence_for_humans + css_class = "#{honeypot}_#{Time.zone.now.to_i}" + end styles = visibility_css(css_class, options) @@ -50,8 +53,10 @@ def build_invisible_captcha(honeypot = nil, scope = nil, options = {}) content_tag(:div, class: css_class) do concat styles unless InvisibleCaptcha.injectable_styles - concat label_tag(build_label_name(honeypot, scope), label) - concat text_field_tag(build_input_name(honeypot, scope), nil, default_honeypot_options.merge(options)) + if InvisibleCaptcha.honeypot_enabled + concat label_tag(build_label_name(honeypot, scope), label) + concat text_field_tag(build_input_name(honeypot, scope), nil, default_honeypot_options.merge(options)) + end if InvisibleCaptcha.spinner_enabled concat hidden_field_tag("spinner", session[:invisible_captcha_spinner], id: nil) end diff --git a/spec/controllers_spec.rb b/spec/controllers_spec.rb index dad7ecb..7649cfb 100644 --- a/spec/controllers_spec.rb +++ b/spec/controllers_spec.rb @@ -223,6 +223,24 @@ def custom_timestamp_callback end end + context 'with honeypot_enabled = false' do + before(:each) do + InvisibleCaptcha.honeypot_enabled = false + session[:invisible_captcha_timestamp] = Time.zone.now.iso8601 + + # Wait for valid submission + sleep InvisibleCaptcha.timestamp_threshold + end + + after(:each) { InvisibleCaptcha.honeypot_enabled = true } + + it 'passes even when the honeypot field is filled' do + post :create, params: { topic: { subtitle: 'spam-value', title: 'foo' } } + + expect(response.body).to be_present + end + end + context 'spinner attribute' do before(:each) do InvisibleCaptcha.spinner_enabled = true diff --git a/spec/view_helpers_spec.rb b/spec/view_helpers_spec.rb index 278bdb3..90f11ec 100644 --- a/spec/view_helpers_spec.rb +++ b/spec/view_helpers_spec.rb @@ -75,6 +75,31 @@ end end + context "should have honeypot field" do + it 'that exists by default, honeypot_enabled is true' do + InvisibleCaptcha.honeypot_enabled = true + InvisibleCaptcha.honeypots = [:foo_id] + expect(invisible_captcha).to match(/name="foo_id"/) + end + + it 'that does not exist if honeypot_enabled is false' do + InvisibleCaptcha.honeypot_enabled = false + expect(invisible_captcha).not_to match(/type="text"/) + end + + it 'still renders the spinner if honeypot_enabled is false and spinner_enabled is true' do + InvisibleCaptcha.honeypot_enabled = false + InvisibleCaptcha.spinner_enabled = true + expect(invisible_captcha).to match(/name="spinner"/) + end + + it 'renders nothing if both honeypot_enabled and spinner_enabled are false' do + InvisibleCaptcha.honeypot_enabled = false + InvisibleCaptcha.spinner_enabled = false + expect(invisible_captcha).to eq('') + end + end + it 'should set spam timestamp' do invisible_captcha expect(session[:invisible_captcha_timestamp]).to eq(Time.zone.now.iso8601)