Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<head>`. 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`):
Expand All @@ -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'
Expand Down
4 changes: 4 additions & 0 deletions lib/invisible_captcha.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class << self
:visual_honeypots,
:injectable_styles,
:spinner_enabled,
:honeypot_enabled,
:secret

def init!
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions lib/invisible_captcha/controller_ext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
23 changes: 14 additions & 9 deletions lib/invisible_captcha/view_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment on lines +44 to 48

Expand All @@ -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
Expand Down
18 changes: 18 additions & 0 deletions spec/controllers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions spec/view_helpers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +90 to +94

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)
Expand Down
Loading