Giter VIP home page Giter VIP logo

invisible_captcha's Introduction

Invisible Captcha

Gem Build Status codecov

Complete and flexible spam protection solution for Rails applications.

Invisible Captcha provides different techniques to protect your application against spambots.

The main protection is a solution based on the honeypot principle, which provides a better user experience since there are no extra steps for real users, only for the bots.

Essentially, the strategy consists on adding an input field ๐Ÿฏ into the form that:

  • shouldn't be visible by the real users
  • should be left empty by the real users
  • will most likely be filled by spam bots

It also comes with:

  • a time-sensitive โŒ› form submission
  • an IP based ๐Ÿ” spinner validation

Installation

Invisible Captcha is tested against Rails >= 5.2 and Ruby >= 2.7.

Add this line to your Gemfile and then execute bundle install:

gem 'invisible_captcha'

Usage

View code:

<%= form_for(@topic) do |f| %>
  <%= f.invisible_captcha :subtitle %>
  <!-- or -->
  <%= invisible_captcha :subtitle, :topic %>
<% end %>

Controller code:

class TopicsController < ApplicationController
  invisible_captcha only: [:create, :update], honeypot: :subtitle
end

This method will act as a before_action that triggers when spam is detected (honeypot field has some value). By default, it responds with no content (only headers: head(200)). This is a good default, since the bot will surely read the response code and will think that it has achieved to submit the form properly. But, anyway, you can define your own callback by passing a method to the on_spam option:

class TopicsController < ApplicationController
  invisible_captcha only: [:create, :update], on_spam: :your_spam_callback_method

  private

  def your_spam_callback_method
    redirect_to root_path
  end
end

You should not name your method on_spam, as this will collide with an internal method of the same name.

Note that it is not mandatory to specify a honeypot attribute (neither in the view nor in the controller). In this case, the engine will take a random field from InvisibleCaptcha.honeypots. So, if you're integrating it following this path, in your form:

<%= form_tag(new_contact_path) do |f| %>
  <%= invisible_captcha %>
<% end %>

In your controller:

invisible_captcha only: [:new_contact]

invisible_captcha sends all messages to flash[:error]. For messages to appear on your pages, add <%= flash[:error] %> to app/views/layouts/application.html.erb (somewhere near the top of your <body> element):

<!DOCTYPE html>
<html>
<head>
  <title>Yet another Rails app</title>
  <%= stylesheet_link_tag    "application", media: "all" %>
  <%= javascript_include_tag "application" %>
  <%= csrf_meta_tags %>
</head>
<body>
  <%= flash[:error] %>
  <%= yield %>
</body>
</html>

You can place <%= flash[:error] %> next to :alert and :notice message types, if you have them in your app/views/layouts/application.html.erb.

NOTE: This gem relies on data set by the backend, so in order to properly work, your forms should be rendered by Rails. Forms generated via JavaScript are not going to work well.

Options and customization

This section contains a description of all plugin options and customizations.

Plugin options:

You can customize:

  • sentence_for_humans: text for real users if input field was visible. By default, it uses I18n (see below).
  • honeypots: collection of default honeypots. Used by the view helper, called with no args, to generate a random honeypot field name. By default, a random collection is already generated. As the random collection is stored in memory, it will not work if you are running multiple Rails instances behind a load balancer (see Multiple Rails instances). Beware that Chrome may ignore autocomplete="off". Thus, consider not to use field names, which would be autocompleted, like for example name, country.
  • visual_honeypots: make honeypots visible, also useful to test/debug your implementation.
  • timestamp_threshold: fastest time (in seconds) to expect a human to submit the form (see original article by Yoav Aner outlining the idea). By default, 4 seconds. NOTE: It's recommended to deactivate the autocomplete feature to avoid false positives (autocomplete="off").
  • timestamp_enabled: option to disable the time threshold check at application level. Could be useful, for example, on some testing scenarios. By default, true.
  • 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.
  • 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):

InvisibleCaptcha.setup do |config|
  # config.honeypots           << ['more', 'fake', 'attribute', 'names']
  # config.visual_honeypots    = false
  # config.timestamp_threshold = 2
  # config.timestamp_enabled   = true
  # config.injectable_styles   = false
  # config.spinner_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'
  # config.timestamp_error_message = 'Sorry, that was too quick! Please resubmit.'
end

Multiple Rails instances

If you have multiple Rails instances running behind a load balancer, you have to share the same honeypots collection between the instances.

Either use a fixed collection or share them between the instances using Rails.cache:

InvisibleCaptcha.setup do |config|
  config.honeypots = Rails.cache.fetch('invisible_captcha_honeypots') do
    (1..20).map { InvisibleCaptcha.generate_random_honeypot }
  end
end

Be careful also with the secret setting. Since it will be stored in-memory, if you are running this setup, the best idea is to provide the environment variable (ENV['INVISIBLE_CAPTCHA_SECRET']) from your infrastructure.

Controller method options:

The invisible_captcha method accepts some options:

  • only: apply to given controller actions.
  • except: exclude to given controller actions.
  • honeypot: name of custom honeypot.
  • scope: name of scope, ie: 'topic[subtitle]' -> 'topic' is the scope. By default, it's inferred from the controller_name.
  • on_spam: custom callback to be called on spam detection.
  • timestamp_enabled: enable/disable this technique at action level.
  • on_timestamp_spam: custom callback to be called when form submitted too quickly. The default action redirects to :back printing a warning in flash[:error].
  • timestamp_threshold: custom threshold per controller/action. Overrides the global value for InvisibleCaptcha.timestamp_threshold.
  • prepend: the spam detection will run in a prepend_before_action if prepend: true, otherwise will run in a before_action.

View helpers options:

Using the view/form helper you can override some defaults for the given instance. Actually, it allows to change:

  • sentence_for_humans
<%= form_for(@topic) do |f| %>
  <%= f.invisible_captcha :subtitle, sentence_for_humans: "hey! leave this input empty!" %>
<% end %>
  • visual_honeypots
<%= form_for(@topic) do |f| %>
  <%= f.invisible_captcha :subtitle, visual_honeypots: true %>
<% end %>

You can also pass html options to the input:

<%= invisible_captcha :subtitle, :topic, id: "your_id", class: "your_class" %>

Spam detection notifications

In addition to the on_spam controller callback, you can use the Active Support Instrumentation API to set up a global event handler that fires whenever spam is detected. This is useful for advanced logging, background processing, etc.

To set up a global event handler, subscribe to the invisible_captcha.spam_detected event in an initializer:

# config/initializers/invisible_captcha.rb

ActiveSupport::Notifications.subscribe('invisible_captcha.spam_detected') do |*args, data|
  AwesomeLogger.warn(data[:message], data) # Log to an external logging service.
  SpamRequest.create(data)                 # Record the blocked request in your database.
end

The data passed to the subscriber is hash containing information about the request that was detected as spam. For example:

{
  message: "Honeypot param 'subtitle' was present.",
  remote_ip: '127.0.0.1',
  user_agent: 'Chrome 77',
  controller: 'users',
  action: 'create',
  url: 'http://example.com/users',
  params: {
    topic: { subtitle: 'foo' },
    controller: 'users',
    action: 'create'
  }
}

NOTE: params will be filtered according to your Rails.application.config.filter_parameters configuration, making them (probably) safe for logging. But always double-check that you're not inadvertently logging sensitive form data, like passwords and credit cards.

Content Security Policy

If you're using a Content Security Policy (CSP) in your Rails app, you will need to generate a nonce on the server, and pass nonce: true attribute to the view helper. Uncomment the following lines in your config/initializers/content_security_policy.rb file:

# Be sure to restart your server when you modify this file.

# If you are using UJS then enable automatic nonce generation
Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }

# Set the nonce only to specific directives
Rails.application.config.content_security_policy_nonce_directives = %w(style-src)

Note that if you are already generating nonce for scripts, you'd have to include script-src to content_security_policy_nonce_directives as well:

Rails.application.config.content_security_policy_nonce_directives = %w(script-src style-src)

And in your view helper, you need to pass nonce: true to the invisible_captcha helper:

<%= invisible_captcha nonce: true %>

NOTE: Content Security Policy can break your site! If you already run a website with third-party scripts, styles, images, and fonts, it is highly recommended to enable CSP in report-only mode and observe warnings as they appear. Learn more at MDN:

I18n

invisible_captcha tries to use I18n when it's available by default. The keys it looks for are the following:

en:
  invisible_captcha:
    sentence_for_humans: "If you are human, ignore this field"
    timestamp_error_message: "Sorry, that was too quick! Please resubmit."

You can override the English ones in your i18n config files as well as add new ones for other locales.

If you intend to use I18n with invisible_captcha, you must not set sentence_for_humans or timestamp_error_message to strings in the setup phase.

Testing your controllers

If you're encountering unexpected behaviour while testing controllers that use the invisible_captcha action filter, you may want to disable timestamp check for the test environment. Add the following snippet to the config/initializers/invisible_captcha.rb file:

# Be sure to restart your server when you modify this file.

InvisibleCaptcha.setup do |config|
  config.timestamp_enabled = !Rails.env.test?
end

Another option is to wait for the timestamp check to be valid:

# Maybe inside a before block
InvisibleCaptcha.init!
InvisibleCaptcha.timestamp_threshold = 1

# Before testing your controller action
sleep InvisibleCaptcha.timestamp_threshold

If you're using the "random honeypot" approach, you may want to set a known honeypot:

config.honeypots = ['my_honeypot_field'] if Rails.env.test?

And for the "spinner validation" check, you may want to disable it:

config.spinner_enabled = !Rails.env.test?

Or alternativelly, you should send a valid spinner value along your requests:

# RSpec example
session[:invisible_captcha_spinner] = '32ab649161f9f6faeeb323746de1a25d'
post :create,  params: { topic: { title: 'foo' }, spinner: '32ab649161f9f6faeeb323746de1a25d' }

Contribute

Any kind of idea, feedback or bug report are welcome! Open an issue or send a pull request.

Development

Clone/fork this repository, start to hack on it and send a pull request.

Run the test suite:

$ bundle exec rspec

Run the test suite against all supported versions:

$ bundle exec appraisal install
$ bundle exec appraisal rspec

Run specs against specific version:

$ bundle exec appraisal rails-6.0 rspec

Demo

Start a sample Rails app (source code) with InvisibleCaptcha integrated:

$ bundle exec rake web # PORT=4000 (default: 3000)

License

Copyright (c) Marc Anguera. Invisible Captcha is released under the MIT License.

invisible_captcha's People

Contributors

aiomaster avatar amnesia7 avatar bb avatar j-kasberger avatar jankoegel avatar jjb avatar jmstfv avatar junichiito avatar kikito avatar kylefox avatar lakim avatar markets avatar maxveldink avatar njakobsen avatar petergoldstein avatar rdlugosz avatar roseandres avatar schugabe avatar smntb avatar summera avatar tmaier avatar toommz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

invisible_captcha's Issues

Timestamp detection triggers after validation fail re-render

I've encountered the following issue with default timestamp threshold. I tried to submit a record and forgot to tick off the required "I accept T&C" checkbox. I then just checked it and clicked "Submit" again. This actually triggered captcha because I managed to resubmit within the timestamp threshold.

I've read this issue, but couldn't figure what I should do in this case:
Timestamp detection #6

What do you recommend?

Should I disable the timestamp threshold? Should I set it to lower value?

Thanks!

Here's the repro case:
vlado/earthquake-croatia#66

Replace Rails dependency with only which Rails gems are actually needed

Instead of requiring rails can it be more specific and require just actionpack / activesupport for example? So it's not pulling in all of rails when that's not needed for this gem to work?

This has come up as an issue during the downstream issue regarding the marcel dependency (a dependency of activestorage) mimemagic. Even if we're not using activestorage, we cannot remove it as invisible_captcha depends on Rails in its entirety and pulls it in.

[SimpleForm integration] f.input passes validation, when it should not

Using the example from the docs:

<%= f.invisible_captcha :subtitle %>
<%= invisible_captcha :subtitle, :user %>

Filling in the honeypot field when attaching to a model appears to pass validation. When using <%= invisible_captcha :subtitle %> it does not pass validation and returns the 200 HEAD response. The form in this example is generated through the SimpleForm gem.

# registrations/new.html.erb
<%= simple_form_for(@user, as: :user, url: registration_path(:user), wrapper: :inline_form) do |f| %>
  <%= f.invisible_captcha :subtitle %>
  <%= f.input :email, input_html: { placeholder: '[email protected]' } %>
<% end %>
class Users::RegistrationsController < Devise::RegistrationsController
  invisible_captcha only: [:create], honeypot: :subtitle
  ...
end

Unhandled NoMethodError if invisible_captcha_timestamp not in session

This Line throws
NoMethodError: undefined method -@' for nil:NilClassbecause you can not calculateTime.zone.now - timestamp` when timestamp is nil.
A possible solution could be to split the condition to:

    def invisible_captcha_timestamp?(options = {})
      timestamp = session[:invisible_captcha_timestamp]
      if timestamp
        time_to_submit  = Time.zone.now - timestamp

        # Consider as spam if form submitted too quickly
        if time_to_submit < InvisibleCaptcha.timestamp_threshold
          logger.warn("Potential spam detected for IP #{request.env['REMOTE_ADDR']}. Invisible Captcha timestamp threshold not reached (took #{time_to_submit.to_i}s).")
          return true
        end
      end
      false
    end

Maybe there are other suggestions?

Content Security Policy

@markets ๐Ÿ‘‹

I have enabled a Content Security Policy but that breaks invisible captcha since inlined CSS changes every second. I can make it work by setting style-src to unsafe-inline but that's more of a hack.

Is there a way to insert a nonce to a generated<style> tag?

Compare params[honeypot] with blank string instead of .present?

Hi,

if params[honeypot].present? || params.dig(scope, honeypot).present?

This code is now using .present? to check if it was spam or not.
But in some cases, my system receivedย some honeypot with \n\t\r values that did not make .present? return true asย expected.
https://api.rubyonrails.org/classes/String.html#method-i-blank-3F

Instead of using params[honeypot].present?, why do we not just compare it with "", like params[honeypot] == ''
What do you think?

Best practices for testing controllers?

I'm skipping invisible_captcha for my controller tests, eg:

# some_controller.rb
if !Rails.env.test?
  invisible_captcha only: [:create], on_spam: :on_spam_detected
end

but I don't like the duplication in each controller, perhaps we could add some kind of noop flag for tests?

Changelog?

Hi! Is there a changelog somewhere? Would be very helpful for upgrading existing projects.

css_strategy

The two css strategies that simply position things offscreen or use overflow to hide them are not accessible for blind users (and accessibility is a legal requirement in the US). A screen reader will read it out and invite the user to fill it in. The display:none one should be fine, though.

timestamp stored as a string in the session

I am getting the following error when using the timestamp function in 0.8.1:

NoMethodError:
       undefined method `-@' for "2016-04-25T12:53:49.173+02:00":String
     # /Users/kikito/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.6/lib/active_support/core_ext/time/calculations.rb:142:in `ago'
     # /Users/kikito/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.6/lib/active_support/time_with_zone.rb:265:in `rescue in -'
     # /Users/kikito/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.6/lib/active_support/time_with_zone.rb:265:in `-'
     # /Users/kikito/.rvm/gems/ruby-2.2.3/gems/invisible_captcha-0.8.1/lib/invisible_captcha/controller_ext.rb:41:in `invisible_captcha_timestamp?'

I believe that the problem is that rails stores the values in session as strings; so even when invisible_captcha does session[:invisible_captcha_timestamp] ||= Time.zone.now, the value is later returned as a string ("2016-04-25T12:53:49.173+02:00").

Let me see if I can prepare a PR to fix this

New feature: Spam-Filtering on Post requests only

Hi Marc,

thank you for this precious gem. I want to suggest a feature to run the spam detection only on POST-requests.

Imagine the following code within the Login-Controller:

def login
  if request.post? 
    # do all the login-stuff
  else
    # show the login screen (with invisible captcha)
  end
end

With this setup you'll get an infinite loop when accessing the Login-Screen.

I could imagine two possible solutions:

  • Add a new option "skip_on_get" to the invisible_captcha-command in the controller
  • Make the detect_spam method publically available, so that it can be used in the request.post?-branch above

What do you think?

KR, Stefan

Chrome ignoring autocomplete "off" leading to false positives

references #42

We recently updated the label text of the honeypot field to "country". Reviewing the invisible_captcha.spam_detected events we found some false positives where the country field had been autocompleted by Google Chrome.

Looking into this further we found that Chrome does not appear to respect the "off" setting for autocomplete, see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#Browser_compatibility and the linked bug report https://bugs.chromium.org/p/chromium/issues/detail?id=587466

Therefore, the current default of disabling autocompletion is "dangerous" as users might not be aware that changing the text label to a more "common" value might trigger the autocomplete behavior of Chrome.

I think it might make sense to at least add a warning next to the "sentence_for_humans" plugin option indicating that custom text labels might trigger autocompletion in Chrome and should be tested properly.

There are possible alternatives for autocomplete that can be used, see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete - but each might come w/ it's own disadvantages so they are probably not a sane default choice either.

Should generated fields be marked 'autocomplete=off'?

Just ran into an issue where a legit user had filled an invisible_captcha field... I assume it was an auto-fill situation because we had named the field "name". Apart from my surprise that a browser would autofill an invisible field (maybe some extension did it?), I'm curious if it would make sense to have the generated tag automatically include an autocomplete=off directive.

Any thoughts one way or another? If you think it'd be a safe addition (meaning that it would not reduce effectiveness of the captcha) I'll code up a PR.

allow to deactivate timestamp_threshold

For rspec tests I set timestamp_threshold=0 to disable the timestamp check.
But the check for a timestamp in the session is still executed and so all the rspec tests fail (because the timestamp in the session is never set).

I'd suggest to skip this check completely if the threshold is set to zero.

doesn't work with devise ?

I have tried this gem with devise so far and it seems not working, in the registration form I added

<%= f.invisible_captcha :usernote %>

Then in my custom RegistrationsController I added

invisible_captcha only: [:create], honeypot: :usernote, on_spam: :spam_callback_method

private

    def spam_callback_method
        throw "spaaaaam"
    end

When I submit the form and I add value="something" to the hidden captcha field , the registration work fine and nothing happen ! I thought maybe something is wrong with the callback, so I just added

before_action :spam_callback_method, only: :create

and the callback seems working fine :) so I think maybe there is something going wrong there!

IP spinner validation when forms are added with JS

If there had been a form on a page (comment form, for example) and the same form is loaded again (reply on a comment) the spinner validation fails for "older" form as session[:invisible_captcha_spinner] is being reset

if InvisibleCaptcha.spinner_enabled && @captcha_ocurrences == 1
  session[:invisible_captcha_spinner] = InvisibleCaptcha.encode("#{session[:invisible_captcha_timestamp]}-#{current_request.remote_ip}")
end

Adding recaptcha to handle the chrome auto fill bug

Trying to add a secondary recaptcha which is shown upon failing the invisible captcha, this allows our users a "second chance" to fill out a normal captcha to avoid having their web message on our system blocked and falsely marked as spam, the only issue we're having is that we're losing the parameters for our web message upon allowing it, we've got the honeypot set before the create action and are hoping that failing the honeypot but passing the normal captcha will allow the message to go through, this is failing however due to us losing our parameters(which I believe is due to the honeypot's handling), I'm fairly new to Rails and am an apprentice so it would be appreciated on any guidance ๐Ÿ˜…

Chrome sometimes autofilling "subtitle" honeypot

We use invisible_captcha in our customer support form. The past couple of weeks, several customers have told us that they've been unable to reach us via the form. To my knowledge, we've never received similar feedback until now.

Based on the customers' description of the problem and my own testing, my hunch is that Chrome's autofill feature is sometimes filling in the honeypot's value. We use the readme's suggested subtitle honeypot.

Here's a video demonstrating the issue. After I selected an email from the dropdown, I used the console to check the honeypot, which has been filled with the U.S. state I live in (weird!).

Screen.Recording.2021-06-16.at.2.27.25.PM.mov

I don't believe changing the name of the honeypot matters. I played around with assigning different names to the text field, and Chrome continued to autofill the field regardless.

My solution for now is to redirect "spammers" back to the form and ask them to resubmit their ticket. By pre-filling the form with the previous submission's content (name, email, message), autofill shouldn't get used again, and the resubmission should succeed.

Screen Shot 2021-06-16 at 3 04 29 PM

Still, not an ideal experience. I'm not sure what a better solution would look like, but I thought I would bring this issue up in case others are receiving similar head-scratching feedback from users.

By the way, I am using version 1.1.0. I didn't think upgrading to 2.0 would make a difference.

i18n support

I would like to be able to use this gem in a multi-lingual environment.

I can give it a try at making a PR integrating it with i18n if you think it is worth it.

redirect_to :back Can Result in No HTTP_REFERER Error

Often times I get the following error when a bot has filled out the form:

An ActionController::RedirectBackError occurred in registrations#create:

  No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].

I have no stacktrace with these errors but I think it's coming from here.

I don't think Rails 4.2 supports fallback_location as an options to redirect_to so one must manually check for a referer.

Issues when submitting form and conflict with Bugsnag gem

invisible_captcha (1.0.1)
ruby 2.6.4p104
rails (6.0.3.2)
bugsnag (6.12.1)

Been having issues with submitting the form with the invisible captcha. The invisible captcha gem was working at one point but then an error started appearing. For testing, I submit the form and fill in the hidden field to act as a bot.

Here's the console return

Started POST "/contact-us" for ::1 at 2020-08-31 16:44:19 -0700
Processing by PublicController#contact_form as JS
  Parameters: {"authenticity_token"=>"redacted", "contact"=>{"name"=>"asdf asdf", "email"=>"[email protected]", "phone"=>"17145551234", "message"=>"asdf"}, "subtitle"=>"This is spam", "commit"=>"Send"}
Potential spam detected for IP ::1. Invisible Captcha honeypot param 'subtitle' was present.
[SPAM] {:message=>"Invisible Captcha honeypot param 'subtitle' was present.", :remote_ip=>"::1", :user_agent=>"redacted", :controller=>"public", :action=>"contact_form", :url=>"http://localhost:3000/contact-us", :params=>{"authenticity_token"=>"redacted==", "contact"=>{"name"=>"Test Name", "email"=>"[email protected]", "phone"=>"15553334444", "message"=>"test message"}, "subtitle"=>"This is spam", "commit"=>"Send", "controller"=>"public", "action"=>"contact_form"}}
Breadcrumb Controller halted via callback meta_data filter:Proc has been dropped for having an invalid data type
Filter chain halted as #<Proc:0x00007ffa1c687d10@/Users/spencermandzik/.rbenv/versions/2.6.4/lib/ruby/gems/2.6.0/gems/invisible_captcha-1.0.1/lib/invisible_captcha/controller_ext.rb:7> rendered or redirected
Completed 200 OK in 1ms (ActiveRecord: 0.0ms | Allocations: 527)

Looks like in Bugsnag this is where the output is coming from:
https://github.com/bugsnag/bugsnag-ruby/blob/master/lib/bugsnag/breadcrumbs/validator.rb#L23

I'll also contact Bugsnag to see if this is also something on there end as well.

honeypot detection failed on String with only spaces eg: " "

By looking up the source code, honeypot detection by using following codes

if params[honeypot].present? || (params[scope] && params[scope][honeypot].present?)

if I pass " " (String with only spaces) in honeypot parameter, then spam detection will be failed.

Undefined method remote_ip

Setup:
Rails 6.1.4
Ruby 2.7.1

# MessagesController.rb
class MessagesController < ApplicationController
  invisible_captcha only: [:send_contact, :send_request_sample]
  
  def send_contact
    # validating params and send email or render partial(with js)
  end

  def send_request_sample
    # validating params and send email or render partial(with js)
  end
end
# _contact_form.html.haml
= form_tag(send_contact_path, method: :get, remote: true) do
  = text_field_tag :name
  # some more fields
  = invisible_captcha
  = submit_tag 'Send'
# _request_sample_form.html.haml
= form_tag(send_request_sample_path, method: :get, remote: true) do
  = text_field_tag :name
  # some more fields
  = invisible_captcha
  = submit_tag 'Send'

When submitting the request_sample_form I get:
ActionView::Template::Error (undefined method `remote_ip' for #Message:0x00007f3c3c977960 Did you mean? remote_byebug)

The contact_form is working fine. Any idea how to solve this?

The default timestamp action doesn't cancel the form submission

Currently the default action taken when a timestamp is detected only adds an error message to the flash, and adds a message on the log. But critically, the "fast action" does not get cancelled - despite the flash message and the logged error, the form is saved unless on_timestamp_spam is set to something which does a render or a redirect.

I think it is odd that the default behavior is "leave the fast requests in" (and log/flash instead).

Most of the time the desirable action will be either a render :new or a render :edit, but this won't work all the time. I suggest adding a redirect_to root_path on the default timestamp action to prevent this. This works all the time and will shield the app against SPAM by default.

What do you think?

Why is one of my forms suddenly visible on live website?

I've got a method like this in my Rails 5.2.3 app:

  def guest_account_link
      form_with :url => guest_accounts_path, :local => true do |f|
        concat f.invisible_captcha :title
        concat f.submit "Try now", :data => { :disable_with => false }
      end
  end

class GuestAccountsController < ApplicationController
  invisible_captcha :only               => [:create],
                    :honeypot           => :title,
                    :on_timestamp_spam  => :please_resubmit

The guest_account_link is called from two different locations in the frontend of my app.

Unfortunately, one of them stopped working all of a sudden today (on the live website) and I can't figure out why.

One of the forms still works as expected. In the HTML source code I can see a div with a class title_1588597614. There's also a CSS attribute .title_1588597614 which hides the div.

The other form, however, is completely visible ("If you are a human, ignore this field") and has a class of title_1588598020. When I inspect the source code, I can see that a CSS attribute .title_1588598020 has not been generated and that's why the form is visible and looks so ugly.

What am I missing here and how can this be fixed?

The code used to work for months and hasn't been changed for many weeks, so I am puzzled as to how this could happen.

Timestamp validation when forms are submitted with JS

The consecutive submits of the form fail as timestamp is deleted from the session after validation
timestamp = session.delete(:invisible_captcha_timestamp)
No form is rendered - no timestamp is added - validation fail on next submit

[Ruby 2.7.0] warn method overrides existing method

I've recently updated an app to Ruby 2.7.0. Since then I've run into an issue I believe to be caused in part by invisible_captcha.

At a point in a controller, my code requires 'open-uri' and then runs sitemap = open("#{bucket_path}/index.xml"). This prompts Ruby to call a warning method like so warn('calling URI.open via Kernel#open is deprecated, call URI.open directly or use URI#open', uplevel: 1).

For more context, you can find this line on this page, line 18: https://github.com/ruby/ruby/blob/ruby_2_7/lib/open-uri.rb

Where this causes an issue is that the "warn" method defined in invisible_captcha ends up being the one that is called, and seeing as it can only accept 2 arguments, it causes a 500 error.

Yes, I can prevent "warn" from being called by changing my code to sitemap = URI.open("#{bucket_path}/index.xml") (which I intend to do anyway), but I'm concerned that this overriding of warn may cause issues when other warnings are raised for other reasons.

Clearing session invisible_captcha_timestamp

Looking through the code, it doesn't look like invisible_captcha is clearing the session timestamp in an after filter. Is there a reason for this? Might be a good thing to add in so it's not sitting around in the session.

Stale timestamps aren't reset

During some testing, repeatedly refreshing a form I want to use the timestamp captcha on, if the application does not generate a completely new session for each form render from the same browser then the session[:invisible_captcha_timestamp] gets fixated on the time of the initial page load.

If a bot loads the form repeatedly using the same session, the timestamp will quickly pass beyond the spam threshold and the throttling will disappear.

By using I18n.with_locale(..) in app_controller action is not called

In my sewing I use the (recommended) ' I18n.with_locale(locale, &action)' code to reliable set the user language got by the request.

This is the last call, before any parameters are processed from a method (create in my case). Do I something wrong?
Any recommendations for this?
Bildschirmfoto 2022-03-14 um 08 13 24

Double Render Error

With the current gem version I am get a double render error.

In my create action I used detect_spam() instead of the before filter as I dynamically set the scope. The before_action method prevents the double render error.

def create
    detect_spam(honeypot: 'foxtrot', scope: underscore_class_type)  # manually calling invisible_captacha
end

Would you mind adding an "and return" to the below methods? I do not think it would cause an issue, but it would solve my issue.

If there is another way to fix my issue, please advise.

 def on_timestamp_spam(options = {})
      if action = options[:on_timestamp_spam]
        send(action)
      else
        redirect_back(fallback_location: root_path, flash: { error: InvisibleCaptcha.timestamp_error_message }) and return
      end
    end

    def on_spam(options = {})
      if action = options[:on_spam]
        send(action)
      else
        head(200) and return
      end
    end

UPDATE:

Solution found..

def create
    detect_spam(honeypot: 'foxtrot', scope: underscore_class_type)  # manually calling invisible_captacha
    return if performed?
end

Tips when used together with Sorcery gem.

Background: Rails 7 introduces a redirect_back_or_to method which is used by redirect_back under the hood. Sorcery has a same name method redirect_back_or_to since ... a long time ago.

Because invisible_captcha will use redirect_back when a on_timestamp_spam is triggered, if Sorcery is also used in the project, it will overwrite the Rails redirect_back_or_to method, so invisible_captcha will redirect the user to root url, instead of displaying a "Sorry, that was too quick" alert.

The same name method issue is known to the Sorcery maintainer, to temporarily solve the issue, check this: #296

nonce: true inserts nonce into script-src instead of style-src

As suggested in the docs I have this in my view:

<%= invisible_captcha nonce: true %>

Which adds nonce=whatever to the inserted <style> tag.

And I have this in my content_security_policy.rb:

Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
Rails.application.config.content_security_policy_nonce_directives = %w(style-src script-src)

When I check the header, the nonce that's created for the style is actually present under script-src. This results in a broken site. Shouldn't this nonce be under style-src?

first doc example missing :scope param?

Couldn't figure out why it wasn't working for my standard Devise implementation. My form with user object needed scope: :user in the controller call. This is not clear in the docs. Should the example be the following or am I missing something? Happy to submit PR if so.

class TopicsController < ApplicationController
  invisible_captcha only: [:create, :update], honeypot: :subtitle, scope: :topic
end

Session timestamp is cleared if `render` instead of `redirect`

If we render :new instead of redirect_to in the timestamp response, the timestamp stored in the session will be cleared after the form is re-rendered, leaving it blank in the next attempt at submitting.

This is a problem for me in the case where a user fills out everything but gets a validation error, then corrects the error and submits in less than the threshold time. In this case, since the session does not contain a timestamp, the request is deemed to have failed the timestamp check.

It would be great if we could do something to avoid avoid clearing the timestamp in these custom scenarios, e.g.

  def timestamp_spam_response
    InvisibleCaptcha.record_timestamp
    render :new
  end

What's strange about this issue is that the form we render happens after we delete the timestamp from the session, so it should put it back in.

Occurs in Rails 4.2.0.

Can't seem to make honeypots work properly

I've been trying to use honeypots in order to trick bots into using appropriate data for the honeypot field.

However, as I was adding the specific honeypot :title into my views and controllers a problem came to surface. The forms are just simply being submitted as if there were no inputs in honeypots.

This is how views look like

<%= f.invisible_captcha :title, nonce: true, visual_honeypots: true %>
Nonce is true for Rails 6, honeypot is named :title, and visual_honeypots are set to true to be debugged as of this moment.

This is what controllers look like

invisible_captcha only: [:create_message, :sign_up], scope: ['pages_fan', 'pages_contact'], honeypot: :title, on_timestamp_spam: :spam_callback, on_spam: :spam_callback, timestamp_threshold: 8

This is how one of the forms that are being submitted is writtenin html

<input class="string optional input is-medium" autocomplete="off" placeholder="E-mail" type="text" name="pages_fan[email]" id="pages_fan_email">

And this is the honeypot input:

<input type="text" name="pages_fan[title]" id="pages_fan_title" autocomplete="off" tabindex="-1" nonce="true">

And this is how Rails is generating parameters:

Parameters: {"authenticity_token"=>"iik9w3MTjfme4fYf0CruqlBfHWhkT+TznNOJEELAX61Ou7s7Fr4SgKOLFaQcNPAEPp+zgO2NF73xdmZmkodoBg==", "pages_fan"=>{"title"=>"mat", "email"=>"[email protected]"}, "commit"=>"Assinar"}

It seems that the :title attribute is not being read by invisible_captacha and hence why I am having problems preventing spam.

Version details:

invisible_captcha, latest gem version
Rails, version 6.0.2.2
Ruby, version 2.6.1

Captcha and ugly styling text showing on my form

In my view I have the following code

<%= invisible_captcha %>

within a form_for.
I am seeing the following on my form page:
image

As you can see, ugly CSS styling code and the field itself are being displayed. When I change the initialiser as per #3 to intentionally show the form field, then the form field is shown, but without all the ugly css styling.

This is the actual HTML that is being sent to the browser:

<div class="hbonzfutadwgvkq-y_1549290936"><style media="screen">.hbonzfutadwgvkq-y_1549290936 {position:absolute!important;top:-9999px;left:-9999px;}</style><label for="hbonzfutadwgvkq-y">If you are a human, ignore this field</label><input type="text" name="hbonzfutadwgvkq-y" id="hbonzfutadwgvkq-y" autocomplete="off" tabindex="-1" /></div>

My controller code has this in it:

invisible_captcha only: [:create]

although I think that is irrelevant to this particular issue. What could I be doing wrong?

Potential spam detected for IP Invisible Captcha timestamp not found in session.

Hi, thanks for your gem. I've got an problem. I did insert <%= invisible_captcha %> to login.html.erb, and invisible_captcha in account controller: invisible_captcha only: [:login], on_spam: :redirect_to_signin. And when i try to go to the /login, im getting an error,
`Potential spam detected for IP 10.68.33.1. Invisible Captcha timestamp not found in session.
Completed 500 Internal Server Error in 97ms (ActiveRecord: 65.7ms)

NameError (undefined local variable or method root_path' for #<AccountController:0x0000000009890ea0> Did you mean? robots_path role_path roles_path):

however if i add callback on_timestamp_spam: :noop, the error is gone. Could you help me, what am i doing wrong? Thanks

Autofill passing in Production

I'm using gem 'invisible_captcha', '~> 1.1.0' since the App is Rails 4.2.

I went simple just putting =invisible_captcha on the forms where it was required and the basic invisible_captcha only: [:create] in the controller.

It seems to work in development, but in Production, it doesn't have the same effect.

If I...
Autofill the form, then change the email address, it works as intended.
But if I...
Autofill the form and submit it just sends.

Any tips?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.