invisible_captcha 1.1.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d931568dd6707074ae2b5f48f6a5552283d482a4d87f27b1c72852dfce3d9cb
4
- data.tar.gz: 4e78f33d0a7c4be1a774de1e98e55d7fd2f7ff84e1a407d66a565c6e99779fa9
3
+ metadata.gz: f15e5223696c06e82c8ab6182a4396a9e4e4bf3acd6e425296e60cc6b49cb225
4
+ data.tar.gz: e35b51231012ae92b236f81eb1966a16ea3c7b02862d03977bf23478d968538d
5
5
  SHA512:
6
- metadata.gz: 3fbe8b6755bbc31fb26fbf9d71f01b7c9dbe919feaaaf12f78f86f7e53e93bb9f04f52c83469ea59840714908d834abf84625a542e7f55de2b7f0cd1d877986c
7
- data.tar.gz: 380701e4ddf138445faafb293ed94fe368d58eb7d995d44b3f871362550d5c2bb27d537d00c04cdf89501bc951ab227d3fe7c721389ba1f163327f4d8093ced4
6
+ metadata.gz: ccc4299595595e513fa8f8eb472233b83fa334608b3cd4fcba3d1316b8f1819d83e7cc3d2678dce0ac954c52a4b90ecf0643424d0358cd8dd3412c4aa04ac391
7
+ data.tar.gz: 1fcceba58cb21d931e6d8f7f49a3a1649230628442e18f3664a80d5ac983dbec0bc5caeeda17cacc748bfbfabce93bd189f695bc0d123d4bd2ef0d94a20cdb3e
@@ -0,0 +1,35 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ name: CI
8
+ runs-on: ubuntu-latest
9
+ env:
10
+ BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ ruby: ["2.7", "3.0", "3.1", "3.2"]
15
+ gemfile: [rails_6.0, rails_6.1, rails_7.0]
16
+ exclude:
17
+ - ruby: "3.1"
18
+ gemfile: rails_6.0
19
+ - ruby: "3.2"
20
+ gemfile: rails_6.0
21
+ include:
22
+ - ruby: "2.7"
23
+ gemfile: rails_5.2
24
+ - ruby: head
25
+ gemfile: rails_7.0
26
+ steps:
27
+ - uses: actions/checkout@v3
28
+ - uses: ruby/setup-ruby@v1
29
+ with:
30
+ ruby-version: ${{ matrix.ruby }}
31
+ bundler-cache: true
32
+ - run: bundle exec rspec
33
+ continue-on-error: ${{ endsWith(matrix.ruby, 'head') }}
34
+ - name: Upload coverage reports to Codecov
35
+ uses: codecov/codecov-action@v3
data/Appraisals CHANGED
@@ -1,19 +1,10 @@
1
- appraise "rails-6.0" do
2
- gem "rails", "~> 6.0.0"
3
- end
4
-
5
- appraise "rails-5.2" do
6
- gem "rails", "~> 5.2.0"
7
- end
8
-
9
- appraise "rails-5.1" do
10
- gem "rails", "~> 5.1.0"
11
- end
12
-
13
- appraise "rails-5.0" do
14
- gem "rails", "~> 5.0.0"
15
- end
16
-
17
- appraise "rails-4.2" do
18
- gem "rails", "~> 4.2.0"
1
+ %w(
2
+ 7.0
3
+ 6.1
4
+ 6.0
5
+ 5.2
6
+ ).each do |version|
7
+ appraise "rails-#{version}" do
8
+ gem "rails", "~> #{version}.0"
9
+ end
19
10
  end
data/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [2.1.0]
6
+
7
+ - Drop official support for EOL Rubies: 2.5 and 2.6
8
+ - Allow random honeypots to be scoped (#117)
9
+
10
+ ## [2.0.0]
11
+
12
+ - New spinner, IP based, validation check (#89)
13
+ - Drop official support for unmaintained Rails versions: 5.1, 5.0 and 4.2 (#86)
14
+ - Drop official support for EOL Rubies: 2.4 and 2.3 (#86)
15
+
5
16
  ## [1.1.0]
6
17
 
7
18
  - New option `prepend: true` for the controller macro (#77)
@@ -119,6 +130,8 @@ All notable changes to this project will be documented in this file.
119
130
 
120
131
  - First version of controller filters
121
132
 
133
+ [2.1.0]: https://github.com/markets/invisible_captcha/compare/v2.0.0...v2.1.0
134
+ [2.0.0]: https://github.com/markets/invisible_captcha/compare/v1.1.0...v2.0.0
122
135
  [1.1.0]: https://github.com/markets/invisible_captcha/compare/v1.0.1...v1.1.0
123
136
  [1.0.1]: https://github.com/markets/invisible_captcha/compare/v1.0.0...v1.0.1
124
137
  [1.0.0]: https://github.com/markets/invisible_captcha/compare/v0.13.0...v1.0.0
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2012-2019 Marc Anguera Insa
1
+ Copyright 2012-2021 Marc Anguera Insa
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # Invisible Captcha
2
2
 
3
3
  [![Gem](https://img.shields.io/gem/v/invisible_captcha.svg?style=flat-square)](https://rubygems.org/gems/invisible_captcha)
4
- [![Build Status](https://travis-ci.org/markets/invisible_captcha.svg)](https://travis-ci.org/markets/invisible_captcha)
4
+ [![Build Status](https://github.com/markets/invisible_captcha/workflows/CI/badge.svg)](https://github.com/markets/invisible_captcha/actions)
5
+ [![codecov](https://codecov.io/gh/markets/invisible_captcha/branch/master/graph/badge.svg?token=nADSa6rbhM)](https://codecov.io/gh/markets/invisible_captcha)
5
6
 
6
- > Simple and flexible spam protection solution for Rails applications.
7
+ > Complete and flexible spam protection solution for Rails applications.
7
8
 
8
9
  Invisible Captcha provides different techniques to protect your application against spambots.
9
10
 
@@ -15,11 +16,13 @@ Essentially, the strategy consists on adding an input field :honey_pot: into the
15
16
  - should be left empty by the real users
16
17
  - will most likely be filled by spam bots
17
18
 
18
- It also comes with a time-sensitive :hourglass: form submission.
19
+ It also comes with:
20
+ - a time-sensitive :hourglass: form submission
21
+ - an IP based :mag: spinner validation
19
22
 
20
23
  ## Installation
21
24
 
22
- Invisible Captcha is tested against Rails `>= 4.2` and Ruby `>= 2.3`.
25
+ Invisible Captcha is tested against Rails `>= 5.2` and Ruby `>= 2.7`.
23
26
 
24
27
  Add this line to your Gemfile and then execute `bundle install`:
25
28
 
@@ -61,6 +64,8 @@ class TopicsController < ApplicationController
61
64
  end
62
65
  ```
63
66
 
67
+ You should _not_ name your method `on_spam`, as this will collide with an internal method of the same name.
68
+
64
69
  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:
65
70
 
66
71
  ```erb
@@ -95,6 +100,8 @@ invisible_captcha only: [:new_contact]
95
100
 
96
101
  You can place `<%= flash[:error] %>` next to `:alert` and `:notice` message types, if you have them in your `app/views/layouts/application.html.erb`.
97
102
 
103
+ **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.
104
+
98
105
  ## Options and customization
99
106
 
100
107
  This section contains a description of all plugin options and customizations.
@@ -104,12 +111,14 @@ This section contains a description of all plugin options and customizations.
104
111
  You can customize:
105
112
 
106
113
  - `sentence_for_humans`: text for real users if input field was visible. By default, it uses I18n (see below).
107
- - `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 are running multiple Rails instances behind a load balancer. See [Multiple Rails instances](#multiple-rails-instances).
114
+ - `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](#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`.
108
115
  - `visual_honeypots`: make honeypots visible, also useful to test/debug your implementation.
109
116
  - `timestamp_threshold`: fastest time (in seconds) to expect a human to submit the form (see [original article by Yoav Aner](https://blog.gingerlime.com/2012/simple-detection-of-comment-spam-in-rails/) outlining the idea). By default, 4 seconds. **NOTE:** It's recommended to deactivate the autocomplete feature to avoid false positives (`autocomplete="off"`).
110
117
  - `timestamp_enabled`: option to disable the time threshold check at application level. Could be useful, for example, on some testing scenarios. By default, true.
111
118
  - `timestamp_error_message`: flash error message thrown when form submitted quicker than the `timestamp_threshold` value. It uses I18n by default.
112
119
  - `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.
120
+ - `spinner_enabled`: option to disable the IP spinner validation. By default, true.
121
+ - `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.
113
122
 
114
123
  To change these defaults, add the following to an initializer (recommended `config/initializers/invisible_captcha.rb`):
115
124
 
@@ -117,9 +126,10 @@ To change these defaults, add the following to an initializer (recommended `conf
117
126
  InvisibleCaptcha.setup do |config|
118
127
  # config.honeypots << ['more', 'fake', 'attribute', 'names']
119
128
  # config.visual_honeypots = false
120
- # config.timestamp_threshold = 4
129
+ # config.timestamp_threshold = 2
121
130
  # config.timestamp_enabled = true
122
131
  # config.injectable_styles = false
132
+ # config.spinner_enabled = true
123
133
 
124
134
  # Leave these unset if you want to use I18n (see below)
125
135
  # config.sentence_for_humans = 'If you are a human, ignore this field'
@@ -141,6 +151,8 @@ InvisibleCaptcha.setup do |config|
141
151
  end
142
152
  ```
143
153
 
154
+ 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.
155
+
144
156
  ### Controller method options:
145
157
 
146
158
  The `invisible_captcha` method accepts some options:
@@ -148,7 +160,7 @@ The `invisible_captcha` method accepts some options:
148
160
  - `only`: apply to given controller actions.
149
161
  - `except`: exclude to given controller actions.
150
162
  - `honeypot`: name of custom honeypot.
151
- - `scope`: name of scope, ie: 'topic[subtitle]' -> 'topic' is the scope.
163
+ - `scope`: name of scope, ie: 'topic[subtitle]' -> 'topic' is the scope. By default, it's inferred from the `controller_name`.
152
164
  - `on_spam`: custom callback to be called on spam detection.
153
165
  - `timestamp_enabled`: enable/disable this technique at action level.
154
166
  - `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]`.
@@ -190,8 +202,8 @@ To set up a global event handler, [subscribe](https://guides.rubyonrails.org/act
190
202
  # config/initializers/invisible_captcha.rb
191
203
 
192
204
  ActiveSupport::Notifications.subscribe('invisible_captcha.spam_detected') do |*args, data|
193
- AwesomeLogger.warn(data[:message], data) # Log to an external logging service.
194
- SpamRequest.create(data) # Record the blocked request in your database.
205
+ AwesomeLogger.warn(data[:message], data) # Log to an external logging service.
206
+ SpamRequest.create(data) # Record the blocked request in your database.
195
207
  end
196
208
  ```
197
209
 
@@ -199,7 +211,7 @@ The `data` passed to the subscriber is hash containing information about the req
199
211
 
200
212
  ```ruby
201
213
  {
202
- message: "Invisible Captcha honeypot param 'subtitle' was present.",
214
+ message: "Honeypot param 'subtitle' was present.",
203
215
  remote_ip: '127.0.0.1',
204
216
  user_agent: 'Chrome 77',
205
217
  controller: 'users',
@@ -213,7 +225,7 @@ The `data` passed to the subscriber is hash containing information about the req
213
225
  }
214
226
  ```
215
227
 
216
- _**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._
228
+ **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.
217
229
 
218
230
  ### Content Security Policy
219
231
 
@@ -240,13 +252,11 @@ And in your view helper, you need to pass `nonce: true` to the `invisible_captch
240
252
  <%= invisible_captcha nonce: true %>
241
253
  ```
242
254
 
243
- **WARNING:** 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:
255
+ **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:
244
256
 
245
257
  * https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
246
258
  * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
247
259
 
248
- Note that Content Security Policy only works on Rails 5.2 and up.
249
-
250
260
  ### I18n
251
261
 
252
262
  `invisible_captcha` tries to use I18n when it's available by default. The keys it looks for are the following:
@@ -309,7 +319,7 @@ $ bundle exec appraisal rspec
309
319
  Run specs against specific version:
310
320
 
311
321
  ```
312
- $ bundle exec appraisal rails-5.2 rspec
322
+ $ bundle exec appraisal rails-6.0 rspec
313
323
  ```
314
324
 
315
325
  ### Demo
data/Rakefile CHANGED
@@ -1,11 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
- require 'rspec/core/rake_task'
5
-
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
- task :default => :spec
9
4
 
10
5
  desc 'Start development Rails app'
11
6
  task :web do
@@ -16,4 +11,4 @@ task :web do
16
11
 
17
12
  Dir.chdir(app_path)
18
13
  exec("rails s -p #{port}")
19
- end
14
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "~> 4.2.0"
5
+ gem "rails", "~> 6.1.0"
6
6
 
7
7
  gemspec path: "../"
@@ -2,6 +2,6 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "~> 5.0.0"
5
+ gem "rails", "~> 7.0.0"
6
6
 
7
7
  gemspec path: "../"
@@ -5,8 +5,8 @@ Gem::Specification.new do |spec|
5
5
  spec.version = InvisibleCaptcha::VERSION
6
6
  spec.authors = ["Marc Anguera Insa"]
7
7
  spec.email = ["srmarc.ai@gmail.com"]
8
- spec.description = "Unobtrusive, flexible and simple spam protection for Rails applications using honeypot strategy for better user experience."
9
- spec.summary = "Simple honeypot protection for RoR apps"
8
+ spec.description = "Unobtrusive, flexible and complete spam protection for Rails applications using honeypot strategy for better user experience."
9
+ spec.summary = "Honeypot spam protection for Rails"
10
10
  spec.homepage = "https://github.com/markets/invisible_captcha"
11
11
  spec.license = "MIT"
12
12
 
@@ -15,8 +15,11 @@ Gem::Specification.new do |spec|
15
15
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
16
16
  spec.require_paths = ["lib"]
17
17
 
18
- spec.add_dependency 'rails', '>= 4.2'
18
+ spec.add_dependency 'rails', '>= 5.2'
19
19
 
20
- spec.add_development_dependency 'rspec-rails', '~> 3.1'
20
+ spec.add_development_dependency 'rspec-rails'
21
21
  spec.add_development_dependency 'appraisal'
22
+ spec.add_development_dependency 'webrick'
23
+ spec.add_development_dependency 'simplecov'
24
+ spec.add_development_dependency 'simplecov-cobertura'
22
25
  end
@@ -21,7 +21,7 @@ module InvisibleCaptcha
21
21
  def detect_spam(options = {})
22
22
  if timestamp_spam?(options)
23
23
  on_timestamp_spam(options)
24
- elsif honeypot_spam?(options)
24
+ elsif honeypot_spam?(options) || spinner_spam?
25
25
  on_spam(options)
26
26
  end
27
27
  end
@@ -30,11 +30,7 @@ module InvisibleCaptcha
30
30
  if action = options[:on_timestamp_spam]
31
31
  send(action)
32
32
  else
33
- if respond_to?(:redirect_back)
34
- redirect_back(fallback_location: root_path, flash: { error: InvisibleCaptcha.timestamp_error_message })
35
- else
36
- redirect_to :back, flash: { error: InvisibleCaptcha.timestamp_error_message }
37
- end
33
+ redirect_back(fallback_location: root_path, flash: { error: InvisibleCaptcha.timestamp_error_message })
38
34
  end
39
35
  end
40
36
 
@@ -55,24 +51,33 @@ module InvisibleCaptcha
55
51
 
56
52
  return false unless enabled
57
53
 
58
- @invisible_captcha_timestamp ||= session.delete(:invisible_captcha_timestamp)
54
+ timestamp = session.delete(:invisible_captcha_timestamp)
59
55
 
60
56
  # Consider as spam if timestamp not in session, cause that means the form was not fetched at all
61
- unless @invisible_captcha_timestamp
62
- warn_spam("Invisible Captcha timestamp not found in session.")
57
+ unless timestamp
58
+ warn_spam("Timestamp not found in session.")
63
59
  return true
64
60
  end
65
61
 
66
- time_to_submit = Time.zone.now - DateTime.iso8601(@invisible_captcha_timestamp)
62
+ time_to_submit = Time.zone.now - DateTime.iso8601(timestamp)
67
63
  threshold = options[:timestamp_threshold] || InvisibleCaptcha.timestamp_threshold
68
64
 
69
65
  # Consider as spam if form submitted too quickly
70
66
  if time_to_submit < threshold
71
- warn_spam("Invisible Captcha timestamp threshold not reached (took #{time_to_submit.to_i}s).")
67
+ warn_spam("Timestamp threshold not reached (took #{time_to_submit.to_i}s).")
68
+ return true
69
+ end
70
+
71
+ false
72
+ end
73
+
74
+ def spinner_spam?
75
+ if InvisibleCaptcha.spinner_enabled && params[:spinner] != session[:invisible_captcha_spinner]
76
+ warn_spam("Spinner value mismatch")
72
77
  return true
73
78
  end
74
79
 
75
- return false
80
+ false
76
81
  end
77
82
 
78
83
  def honeypot_spam?(options = {})
@@ -83,8 +88,8 @@ module InvisibleCaptcha
83
88
  # If honeypot is defined for this controller-action, search for:
84
89
  # - honeypot: params[:subtitle]
85
90
  # - honeypot with scope: params[:topic][:subtitle]
86
- if params[honeypot].present? || (params[scope] && params[scope][honeypot].present?)
87
- warn_spam("Invisible Captcha honeypot param '#{honeypot}' was present.")
91
+ if params[honeypot].present? || params.dig(scope, honeypot).present?
92
+ warn_spam("Honeypot param '#{honeypot}' was present.")
88
93
  return true
89
94
  else
90
95
  # No honeypot spam detected, remove honeypot from params to avoid UnpermittedParameters exceptions
@@ -93,8 +98,8 @@ module InvisibleCaptcha
93
98
  end
94
99
  else
95
100
  InvisibleCaptcha.honeypots.each do |default_honeypot|
96
- if params[default_honeypot].present?
97
- warn_spam("Invisible Captcha honeypot param '#{default_honeypot}' was present.")
101
+ if params[default_honeypot].present? || params.dig(scope, default_honeypot).present?
102
+ warn_spam("Honeypot param '#{scope}.#{default_honeypot}' was present.")
98
103
  return true
99
104
  end
100
105
  end
@@ -104,7 +109,9 @@ module InvisibleCaptcha
104
109
  end
105
110
 
106
111
  def warn_spam(message)
107
- logger.warn("Potential spam detected for IP #{request.remote_ip}. #{message}")
112
+ message = "[Invisible Captcha] Potential spam detected for IP #{request.remote_ip}. #{message}"
113
+
114
+ logger.warn(message)
108
115
 
109
116
  ActiveSupport::Notifications.instrument(
110
117
  'invisible_captcha.spam_detected',
@@ -2,7 +2,7 @@
2
2
 
3
3
  module InvisibleCaptcha
4
4
  module FormHelpers
5
- def invisible_captcha(honeypot, options = {})
5
+ def invisible_captcha(honeypot = nil, options = {})
6
6
  @template.invisible_captcha(honeypot, self.object_name, options)
7
7
  end
8
8
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module InvisibleCaptcha
4
- VERSION = "1.1.0"
4
+ VERSION = "2.1.0"
5
5
  end
@@ -10,9 +10,17 @@ module InvisibleCaptcha
10
10
  #
11
11
  # @return [String] the generated html
12
12
  def invisible_captcha(honeypot = nil, scope = nil, options = {})
13
- if InvisibleCaptcha.timestamp_enabled
13
+ @captcha_ocurrences = 0 unless defined?(@captcha_ocurrences)
14
+ @captcha_ocurrences += 1
15
+
16
+ if InvisibleCaptcha.timestamp_enabled || InvisibleCaptcha.spinner_enabled
14
17
  session[:invisible_captcha_timestamp] = Time.zone.now.iso8601
15
18
  end
19
+
20
+ if InvisibleCaptcha.spinner_enabled && @captcha_ocurrences == 1
21
+ session[:invisible_captcha_spinner] = InvisibleCaptcha.encode("#{session[:invisible_captcha_timestamp]}-#{current_request.remote_ip}")
22
+ end
23
+
16
24
  build_invisible_captcha(honeypot, scope, options)
17
25
  end
18
26
 
@@ -24,6 +32,10 @@ module InvisibleCaptcha
24
32
 
25
33
  private
26
34
 
35
+ def current_request
36
+ @request ||= request
37
+ end
38
+
27
39
  def build_invisible_captcha(honeypot = nil, scope = nil, options = {})
28
40
  if honeypot.is_a?(Hash)
29
41
  options = honeypot
@@ -44,6 +56,9 @@ module InvisibleCaptcha
44
56
  concat styles unless InvisibleCaptcha.injectable_styles
45
57
  concat label_tag(build_label_name(honeypot, scope), label)
46
58
  concat text_field_tag(build_input_name(honeypot, scope), nil, default_honeypot_options.merge(options))
59
+ if InvisibleCaptcha.spinner_enabled
60
+ concat hidden_field_tag("spinner", session[:invisible_captcha_spinner], id: nil)
61
+ end
47
62
  end
48
63
  end
49
64
 
@@ -56,11 +71,7 @@ module InvisibleCaptcha
56
71
 
57
72
  return if visible
58
73
 
59
- nonce = if Rails.version >= '5.2'
60
- content_security_policy_nonce if options[:nonce]
61
- else
62
- nil
63
- end
74
+ nonce = content_security_policy_nonce if options[:nonce]
64
75
 
65
76
  content_tag(:style, media: 'screen', nonce: nonce) do
66
77
  ".#{css_class} {#{InvisibleCaptcha.css_strategy}}"
@@ -15,7 +15,9 @@ module InvisibleCaptcha
15
15
  :timestamp_threshold,
16
16
  :timestamp_enabled,
17
17
  :visual_honeypots,
18
- :injectable_styles
18
+ :injectable_styles,
19
+ :spinner_enabled,
20
+ :secret
19
21
 
20
22
  def init!
21
23
  # Default sentence for real users if text field was visible
@@ -33,9 +35,15 @@ module InvisibleCaptcha
33
35
  # Make honeypots visibles
34
36
  self.visual_honeypots = false
35
37
 
36
- # If enabled, you should call anywhere in of your layout the following helper, to inject the honeypot styles:
37
- # <%= invisible_captcha_styles %>
38
+ # If enabled, you should call anywhere in your layout the following helper, to inject the honeypot styles:
39
+ # <%= invisible_captcha_styles %>
38
40
  self.injectable_styles = false
41
+
42
+ # Spinner check enabled by default
43
+ self.spinner_enabled = true
44
+
45
+ # A secret key to encode some internal values
46
+ self.secret = ENV['INVISIBLE_CAPTCHA_SECRET'] || SecureRandom.hex(64)
39
47
  end
40
48
 
41
49
  def sentence_for_humans
@@ -70,6 +78,10 @@ module InvisibleCaptcha
70
78
  ].sample
71
79
  end
72
80
 
81
+ def encode(value)
82
+ Digest::MD5.hexdigest("#{self.secret}-#{value}")
83
+ end
84
+
73
85
  private
74
86
 
75
87
  def call_lambda_or_return(obj)