invisible_captcha 0.12.2 → 2.0.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: 5011fee9db065c86faf1507c11e0d3000ad61e19d05e7102e8bcf3750abcd52f
4
- data.tar.gz: bd104ffe4aeb3436c1d365a293c66f80d9d2e42c543b009ea596ac83d9a84db2
3
+ metadata.gz: 86acfe71e903568702c63c261bfd32a066dbc947d1c2fa2b4956178a7de591db
4
+ data.tar.gz: a9a8768c5bcfb9a7656e0638dde4bf40e169719385bf947dace7fa4d34f03656
5
5
  SHA512:
6
- metadata.gz: e1c5608b8671bfef9edfec01531f575fe3a83c12a00dd72090a772686f2fef56ab6436e323dc7ca7daa1b82d30764ca03a2f89d6727145f593d377d1c057fe51
7
- data.tar.gz: 811827be3f2a2bd4a18a1004377605c8db09c1279658371d2191e27226afd6557235c8747a5c39ec82a65bc443bb4590dcced9fff96d8be4fba946d21a07a50d
6
+ metadata.gz: ec1ed1b9f7bef2e753f7b736dbce8124b5cd4c699e4075cbd15fdade99e6f332025e3088f0754315b18f8bde48b7e883c3470f840a535a3631f2e5c659c415df
7
+ data.tar.gz: 3698ed54f31c8f87730dcd92e14c2076e10aaa98a14a765e7d3be8bfd0e259385572b5d3252b8328fb4a539634bb8e086270b2e7e34117ab76a15ab4f42c095b
data/.gitignore CHANGED
@@ -8,3 +8,4 @@ Gemfile.lock
8
8
  spec/dummy/log/*.log
9
9
  spec/dummy/tmp/
10
10
  .byebug_history
11
+ .DS_Store
data/.travis.yml CHANGED
@@ -1,40 +1,18 @@
1
1
  language: ruby
2
2
  cache: bundler
3
- sudo: false
4
3
  rvm:
5
4
  - ruby-head
6
- - 2.6.2
7
- - 2.5.5
8
- - 2.4.5
9
- - 2.3.8
10
- - 2.2.10
5
+ - 3.0.0
6
+ - 2.7.2
7
+ - 2.6.5
8
+ - 2.5.8
11
9
  gemfile:
10
+ - gemfiles/rails_6.1.gemfile
12
11
  - gemfiles/rails_6.0.gemfile
13
12
  - gemfiles/rails_5.2.gemfile
14
- - gemfiles/rails_5.1.gemfile
15
- - gemfiles/rails_5.0.gemfile
16
- - gemfiles/rails_4.2.gemfile
17
- - gemfiles/rails_3.2.gemfile
18
- before_install:
19
- # Rails 4.x requires Bundler version < 2.0.
20
- - "find /home/travis/.rvm/rubies -wholename '*default/bundler-*.gemspec' -delete"
21
- - rvm @global do gem uninstall bundler -a -x
22
- - rvm @global do yes | gem install bundler -v '< 2'
23
13
  matrix:
24
14
  exclude:
25
- - rvm: 2.4.5
26
- gemfile: gemfiles/rails_6.0.gemfile
27
- - rvm: 2.3.8
28
- gemfile: gemfiles/rails_6.0.gemfile
29
- - rvm: 2.2.10
30
- gemfile: gemfiles/rails_6.0.gemfile
31
- - rvm: ruby-head
32
- gemfile: gemfiles/rails_3.2.gemfile
33
- - rvm: 2.6.2
34
- gemfile: gemfiles/rails_3.2.gemfile
35
- - rvm: 2.5.5
36
- gemfile: gemfiles/rails_3.2.gemfile
37
- - rvm: 2.4.5
38
- gemfile: gemfiles/rails_3.2.gemfile
15
+ - rvm: 3.0.0
16
+ gemfile: gemfiles/rails_5.2.gemfile
39
17
  allow_failures:
40
- - rvm: ruby-head
18
+ - rvm: ruby-head
data/Appraisals CHANGED
@@ -1,3 +1,7 @@
1
+ appraise "rails-6.1" do
2
+ gem "rails", "~> 6.1.0"
3
+ end
4
+
1
5
  appraise "rails-6.0" do
2
6
  gem "rails", "~> 6.0.0"
3
7
  end
@@ -5,19 +9,3 @@ end
5
9
  appraise "rails-5.2" do
6
10
  gem "rails", "~> 5.2.0"
7
11
  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"
19
- end
20
-
21
- appraise "rails-3.2" do
22
- gem "rails", "~> 3.2.0"
23
- end
data/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [2.0.0]
6
+
7
+ - New spinner, IP based, validation check (#89)
8
+ - Drop official support for unmaintained Rails versions: 5.1, 5.0 and 4.2 (#86)
9
+ - Drop official support for EOL Rubies: 2.4 and 2.3 (#86)
10
+
11
+ ## [1.1.0]
12
+
13
+ - New option `prepend: true` for the controller macro (#77)
14
+
15
+ ## [1.0.1]
16
+
17
+ - Fix naming issue with Ruby 2.7 (#65)
18
+
19
+ ## [1.0.0]
20
+
21
+ - Remove Ruby 2.2 and Rails 3.2 support
22
+ - Add Instrumentation event (#62)
23
+
24
+ ## [0.13.0]
25
+
26
+ - Add support for the Content Security Policy nonce (#61)
27
+ - Freeze all strings (#60)
28
+
5
29
  ## [0.12.2]
6
30
 
7
31
  - Allow new timestamp to be set during `on_timestamp_spam` callback (#53)
@@ -101,6 +125,11 @@ All notable changes to this project will be documented in this file.
101
125
 
102
126
  - First version of controller filters
103
127
 
128
+ [2.0.0]: https://github.com/markets/invisible_captcha/compare/v1.1.0...v2.0.0
129
+ [1.1.0]: https://github.com/markets/invisible_captcha/compare/v1.0.1...v1.1.0
130
+ [1.0.1]: https://github.com/markets/invisible_captcha/compare/v1.0.0...v1.0.1
131
+ [1.0.0]: https://github.com/markets/invisible_captcha/compare/v0.13.0...v1.0.0
132
+ [0.13.0]: https://github.com/markets/invisible_captcha/compare/v0.12.2...v0.13.0
104
133
  [0.12.2]: https://github.com/markets/invisible_captcha/compare/v0.12.1...v0.12.2
105
134
  [0.12.1]: https://github.com/markets/invisible_captcha/compare/v0.12.0...v0.12.1
106
135
  [0.12.0]: https://github.com/markets/invisible_captcha/compare/v0.11.0...v0.12.0
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gemspec
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2012-2017 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,25 +1,27 @@
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://travis-ci.com/markets/invisible_captcha.svg?branch=master)](https://travis-ci.com/markets/invisible_captcha)
5
5
 
6
- > Simple and flexible spam protection solution for Rails applications.
6
+ > Complete and flexible spam protection solution for Rails applications.
7
7
 
8
8
  Invisible Captcha provides different techniques to protect your application against spambots.
9
9
 
10
- The main protection is a solution based on the `honeypot` principle, which provides a better user experience, since there is no extra steps for real users, but for the bots.
10
+ 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.
11
11
 
12
12
  Essentially, the strategy consists on adding an input field :honey_pot: into the form that:
13
13
 
14
14
  - shouldn't be visible by the real users
15
15
  - should be left empty by the real users
16
- - will most be filled by spam bots
16
+ - will most likely be filled by spam bots
17
17
 
18
- It also comes with a time-sensitive :hourglass: form submission.
18
+ It also comes with:
19
+ - a time-sensitive :hourglass: form submission
20
+ - an IP based :mag: spinner validation
19
21
 
20
22
  ## Installation
21
23
 
22
- Invisible Captcha is tested against Rails `>= 3.2` and Ruby `>= 2.2`.
24
+ Invisible Captcha is tested against Rails `>= 5.2` and Ruby `>= 2.5`.
23
25
 
24
26
  Add this line to your Gemfile and then execute `bundle install`:
25
27
 
@@ -47,7 +49,7 @@ class TopicsController < ApplicationController
47
49
  end
48
50
  ```
49
51
 
50
- 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 are able to define your own callback by passing a method to the `on_spam` option:
52
+ 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:
51
53
 
52
54
  ```ruby
53
55
  class TopicsController < ApplicationController
@@ -61,7 +63,7 @@ class TopicsController < ApplicationController
61
63
  end
62
64
  ```
63
65
 
64
- Note that is not mandatory to specify a `honeypot` attribute (nor 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:
66
+ 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
67
 
66
68
  ```erb
67
69
  <%= form_tag(new_contact_path) do |f| %>
@@ -69,12 +71,32 @@ Note that is not mandatory to specify a `honeypot` attribute (nor in the view, n
69
71
  <% end %>
70
72
  ```
71
73
 
72
- In you controller:
74
+ In your controller:
73
75
 
74
76
  ```
75
77
  invisible_captcha only: [:new_contact]
76
78
  ```
77
79
 
80
+ `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):
81
+
82
+ ```erb
83
+ <!DOCTYPE html>
84
+ <html>
85
+ <head>
86
+ <title>Yet another Rails app</title>
87
+ <%= stylesheet_link_tag "application", media: "all" %>
88
+ <%= javascript_include_tag "application" %>
89
+ <%= csrf_meta_tags %>
90
+ </head>
91
+ <body>
92
+ <%= flash[:error] %>
93
+ <%= yield %>
94
+ </body>
95
+ </html>
96
+ ```
97
+
98
+ You can place `<%= flash[:error] %>` next to `:alert` and `:notice` message types, if you have them in your `app/views/layouts/application.html.erb`.
99
+
78
100
  ## Options and customization
79
101
 
80
102
  This section contains a description of all plugin options and customizations.
@@ -84,12 +106,14 @@ This section contains a description of all plugin options and customizations.
84
106
  You can customize:
85
107
 
86
108
  - `sentence_for_humans`: text for real users if input field was visible. By default, it uses I18n (see below).
87
- - `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.
109
+ - `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).
88
110
  - `visual_honeypots`: make honeypots visible, also useful to test/debug your implementation.
89
111
  - `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"`).
90
112
  - `timestamp_enabled`: option to disable the time threshold check at application level. Could be useful, for example, on some testing scenarios. By default, true.
91
113
  - `timestamp_error_message`: flash error message thrown when form submitted quicker than the `timestamp_threshold` value. It uses I18n by default.
92
114
  - `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.
115
+ - `spinner_enabled`: option to disable the IP spinner validation.
116
+ - `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.
93
117
 
94
118
  To change these defaults, add the following to an initializer (recommended `config/initializers/invisible_captcha.rb`):
95
119
 
@@ -97,9 +121,10 @@ To change these defaults, add the following to an initializer (recommended `conf
97
121
  InvisibleCaptcha.setup do |config|
98
122
  # config.honeypots << ['more', 'fake', 'attribute', 'names']
99
123
  # config.visual_honeypots = false
100
- # config.timestamp_threshold = 4
124
+ # config.timestamp_threshold = 2
101
125
  # config.timestamp_enabled = true
102
126
  # config.injectable_styles = false
127
+ # config.spinner_enabled = true
103
128
 
104
129
  # Leave these unset if you want to use I18n (see below)
105
130
  # config.sentence_for_humans = 'If you are a human, ignore this field'
@@ -107,6 +132,22 @@ InvisibleCaptcha.setup do |config|
107
132
  end
108
133
  ```
109
134
 
135
+ #### Multiple Rails instances
136
+
137
+ If you have multiple Rails instances running behind a load balancer, you have to share the same honeypots collection between the instances.
138
+
139
+ Either use a fixed collection or share them between the instances using `Rails.cache`:
140
+
141
+ ```ruby
142
+ InvisibleCaptcha.setup do |config|
143
+ config.honeypots = Rails.cache.fetch('invisible_captcha_honeypots') do
144
+ (1..20).map { InvisibleCaptcha.generate_random_honeypot }
145
+ end
146
+ end
147
+ ```
148
+
149
+ 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.
150
+
110
151
  ### Controller method options:
111
152
 
112
153
  The `invisible_captcha` method accepts some options:
@@ -114,11 +155,12 @@ The `invisible_captcha` method accepts some options:
114
155
  - `only`: apply to given controller actions.
115
156
  - `except`: exclude to given controller actions.
116
157
  - `honeypot`: name of custom honeypot.
117
- - `scope`: name of scope, ie: 'topic[subtitle]' -> 'topic' is the scope.
158
+ - `scope`: name of scope, ie: 'topic[subtitle]' -> 'topic' is the scope. By default, it's inferred from the `controller_name`.
118
159
  - `on_spam`: custom callback to be called on spam detection.
119
160
  - `timestamp_enabled`: enable/disable this technique at action level.
120
161
  - `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]`.
121
162
  - `timestamp_threshold`: custom threshold per controller/action. Overrides the global value for `InvisibleCaptcha.timestamp_threshold`.
163
+ - `prepend`: the spam detection will run in a `prepend_before_action` if `prepend: true`, otherwise will run in a `before_action`.
122
164
 
123
165
  ### View helpers options:
124
166
 
@@ -145,6 +187,71 @@ You can also pass html options to the input:
145
187
  <%= invisible_captcha :subtitle, :topic, id: "your_id", class: "your_class" %>
146
188
  ```
147
189
 
190
+ ### Spam detection notifications
191
+
192
+ In addition to the `on_spam` controller callback, you can use the [Active Support Instrumentation API](https://guides.rubyonrails.org/active_support_instrumentation.html) to set up a global event handler that fires whenever spam is detected. This is useful for advanced logging, background processing, etc.
193
+
194
+ To set up a global event handler, [subscribe](https://guides.rubyonrails.org/active_support_instrumentation.html#subscribing-to-an-event) to the `invisible_captcha.spam_detected` event in an initializer:
195
+
196
+ ```ruby
197
+ # config/initializers/invisible_captcha.rb
198
+
199
+ ActiveSupport::Notifications.subscribe('invisible_captcha.spam_detected') do |*args, data|
200
+ AwesomeLogger.warn(data[:message], data) # Log to an external logging service.
201
+ SpamRequest.create(data) # Record the blocked request in your database.
202
+ end
203
+ ```
204
+
205
+ The `data` passed to the subscriber is hash containing information about the request that was detected as spam. For example:
206
+
207
+ ```ruby
208
+ {
209
+ message: "Invisible Captcha honeypot param 'subtitle' was present.",
210
+ remote_ip: '127.0.0.1',
211
+ user_agent: 'Chrome 77',
212
+ controller: 'users',
213
+ action: 'create',
214
+ url: 'http://example.com/users',
215
+ params: {
216
+ topic: { subtitle: 'foo' },
217
+ controller: 'users',
218
+ action: 'create'
219
+ }
220
+ }
221
+ ```
222
+
223
+ _**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._
224
+
225
+ ### Content Security Policy
226
+
227
+ 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:
228
+
229
+ ```ruby
230
+ # Be sure to restart your server when you modify this file.
231
+
232
+ # If you are using UJS then enable automatic nonce generation
233
+ Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
234
+
235
+ # Set the nonce only to specific directives
236
+ Rails.application.config.content_security_policy_nonce_directives = %w(style-src)
237
+ ```
238
+ 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:
239
+
240
+ ```ruby
241
+ Rails.application.config.content_security_policy_nonce_directives = %w(script-src style-src)
242
+ ```
243
+
244
+ And in your view helper, you need to pass `nonce: true` to the `invisible_captcha` helper:
245
+
246
+ ```erb
247
+ <%= invisible_captcha nonce: true %>
248
+ ```
249
+
250
+ **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:
251
+
252
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
253
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
254
+
148
255
  ### I18n
149
256
 
150
257
  `invisible_captcha` tries to use I18n when it's available by default. The keys it looks for are the following:
@@ -156,17 +263,20 @@ en:
156
263
  timestamp_error_message: "Sorry, that was too quick! Please resubmit."
157
264
  ```
158
265
 
159
- You can override the english ones in your own i18n config files as well as add new ones for other locales.
266
+ You can override the English ones in your i18n config files as well as add new ones for other locales.
160
267
 
161
268
  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.
162
269
 
163
270
  ## Testing your controllers
164
271
 
165
- 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:
272
+ 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:
166
273
 
167
274
  ```ruby
168
- # test/test_helper.rb, spec/rails_helper.rb, ...
169
- InvisibleCaptcha.timestamp_enabled = false
275
+ # Be sure to restart your server when you modify this file.
276
+
277
+ InvisibleCaptcha.setup do |config|
278
+ config.timestamp_enabled = !Rails.env.test?
279
+ end
170
280
  ```
171
281
 
172
282
  Another option is to wait for the timestamp check to be valid:
@@ -204,7 +314,7 @@ $ bundle exec appraisal rspec
204
314
  Run specs against specific version:
205
315
 
206
316
  ```
207
- $ bundle exec appraisal rails-5.2 rspec
317
+ $ bundle exec appraisal rails-6.0 rspec
208
318
  ```
209
319
 
210
320
  ### Demo
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require 'rspec/core/rake_task'
3
5
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "~> 3.2.0"
5
+ gem "rails", "~> 6.1.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,11 +15,8 @@ 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', '>= 3.2.0'
18
+ spec.add_dependency 'rails', '>= 5.0'
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 'test-unit', '~> 3.0'
23
- spec.add_development_dependency 'byebug'
24
22
  end
25
-
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'invisible_captcha/version'
2
4
  require 'invisible_captcha/controller_ext'
3
5
  require 'invisible_captcha/view_helpers'
@@ -13,7 +15,9 @@ module InvisibleCaptcha
13
15
  :timestamp_threshold,
14
16
  :timestamp_enabled,
15
17
  :visual_honeypots,
16
- :injectable_styles
18
+ :injectable_styles,
19
+ :spinner_enabled,
20
+ :secret
17
21
 
18
22
  def init!
19
23
  # Default sentence for real users if text field was visible
@@ -31,9 +35,15 @@ module InvisibleCaptcha
31
35
  # Make honeypots visibles
32
36
  self.visual_honeypots = false
33
37
 
34
- # If enabled, you should call anywhere in of your layout the following helper, to inject the honeypot styles:
35
- # <%= 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 %>
36
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)
37
47
  end
38
48
 
39
49
  def sentence_for_humans
@@ -68,6 +78,10 @@ module InvisibleCaptcha
68
78
  ].sample
69
79
  end
70
80
 
81
+ def encode(value)
82
+ Digest::MD5.hexdigest("#{self.secret}-#{value}")
83
+ end
84
+
71
85
  private
72
86
 
73
87
  def call_lambda_or_return(obj)
@@ -1,13 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module InvisibleCaptcha
2
4
  module ControllerExt
3
5
  module ClassMethods
4
6
  def invisible_captcha(options = {})
5
- if respond_to?(:before_action)
6
- before_action(options) do
7
+ if options.key?(:prepend)
8
+ prepend_before_action(options) do
7
9
  detect_spam(options)
8
10
  end
9
11
  else
10
- before_filter(options) do
12
+ before_action(options) do
11
13
  detect_spam(options)
12
14
  end
13
15
  end
@@ -19,7 +21,7 @@ module InvisibleCaptcha
19
21
  def detect_spam(options = {})
20
22
  if timestamp_spam?(options)
21
23
  on_timestamp_spam(options)
22
- elsif honeypot_spam?(options)
24
+ elsif honeypot_spam?(options) || spinner_spam?
23
25
  on_spam(options)
24
26
  end
25
27
  end
@@ -28,11 +30,7 @@ module InvisibleCaptcha
28
30
  if action = options[:on_timestamp_spam]
29
31
  send(action)
30
32
  else
31
- if respond_to?(:redirect_back)
32
- redirect_back(fallback_location: root_path, flash: { error: InvisibleCaptcha.timestamp_error_message })
33
- else
34
- redirect_to :back, flash: { error: InvisibleCaptcha.timestamp_error_message }
35
- end
33
+ redirect_back(fallback_location: root_path, flash: { error: InvisibleCaptcha.timestamp_error_message })
36
34
  end
37
35
  end
38
36
 
@@ -53,24 +51,33 @@ module InvisibleCaptcha
53
51
 
54
52
  return false unless enabled
55
53
 
56
- @invisible_captcha_timestamp ||= session.delete(:invisible_captcha_timestamp)
54
+ timestamp = session.delete(:invisible_captcha_timestamp)
57
55
 
58
56
  # Consider as spam if timestamp not in session, cause that means the form was not fetched at all
59
- unless @invisible_captcha_timestamp
60
- warn("Invisible Captcha timestamp not found in session.")
57
+ unless timestamp
58
+ warn_spam("Invisible Captcha timestamp not found in session.")
61
59
  return true
62
60
  end
63
61
 
64
- time_to_submit = Time.zone.now - DateTime.iso8601(@invisible_captcha_timestamp)
62
+ time_to_submit = Time.zone.now - DateTime.iso8601(timestamp)
65
63
  threshold = options[:timestamp_threshold] || InvisibleCaptcha.timestamp_threshold
66
64
 
67
65
  # Consider as spam if form submitted too quickly
68
66
  if time_to_submit < threshold
69
- warn("Invisible Captcha timestamp threshold not reached (took #{time_to_submit.to_i}s).")
67
+ warn_spam("Invisible Captcha timestamp threshold not reached (took #{time_to_submit.to_i}s).")
70
68
  return true
71
69
  end
72
70
 
73
- return false
71
+ false
72
+ end
73
+
74
+ def spinner_spam?
75
+ if InvisibleCaptcha.spinner_enabled && params[:spinner] != session[:invisible_captcha_spinner]
76
+ warn_spam("Invisible Captcha spinner value mismatch")
77
+ return true
78
+ end
79
+
80
+ false
74
81
  end
75
82
 
76
83
  def honeypot_spam?(options = {})
@@ -81,8 +88,8 @@ module InvisibleCaptcha
81
88
  # If honeypot is defined for this controller-action, search for:
82
89
  # - honeypot: params[:subtitle]
83
90
  # - honeypot with scope: params[:topic][:subtitle]
84
- if params[honeypot].present? || (params[scope] && params[scope][honeypot].present?)
85
- warn("Invisible Captcha honeypot param '#{honeypot}' was present.")
91
+ if params[honeypot].present? || params.dig(scope, honeypot).present?
92
+ warn_spam("Invisible Captcha honeypot param '#{honeypot}' was present.")
86
93
  return true
87
94
  else
88
95
  # No honeypot spam detected, remove honeypot from params to avoid UnpermittedParameters exceptions
@@ -92,7 +99,7 @@ module InvisibleCaptcha
92
99
  else
93
100
  InvisibleCaptcha.honeypots.each do |default_honeypot|
94
101
  if params[default_honeypot].present?
95
- warn("Invisible Captcha honeypot param '#{default_honeypot}' was present.")
102
+ warn_spam("Invisible Captcha honeypot param '#{default_honeypot}' was present.")
96
103
  return true
97
104
  end
98
105
  end
@@ -101,8 +108,19 @@ module InvisibleCaptcha
101
108
  false
102
109
  end
103
110
 
104
- def warn(message)
111
+ def warn_spam(message)
105
112
  logger.warn("Potential spam detected for IP #{request.remote_ip}. #{message}")
113
+
114
+ ActiveSupport::Notifications.instrument(
115
+ 'invisible_captcha.spam_detected',
116
+ message: message,
117
+ remote_ip: request.remote_ip,
118
+ user_agent: request.user_agent,
119
+ controller: params[:controller],
120
+ action: params[:action],
121
+ url: request.url,
122
+ params: request.filtered_parameters
123
+ )
106
124
  end
107
125
  end
108
126
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module InvisibleCaptcha
2
4
  module FormHelpers
3
5
  def invisible_captcha(honeypot, options = {})
4
6
  @template.invisible_captcha(honeypot, self.object_name, options)
5
7
  end
6
8
  end
7
- end
9
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module InvisibleCaptcha
2
4
  class Railtie < Rails::Railtie
3
5
  initializer 'invisible_captcha.rails_integration' do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module InvisibleCaptcha
2
- VERSION = "0.12.2"
4
+ VERSION = "2.0.0"
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module InvisibleCaptcha
2
4
  module ViewHelpers
3
5
  # Builds the honeypot html
@@ -8,9 +10,17 @@ module InvisibleCaptcha
8
10
  #
9
11
  # @return [String] the generated html
10
12
  def invisible_captcha(honeypot = nil, scope = nil, options = {})
11
- 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
12
17
  session[:invisible_captcha_timestamp] = Time.zone.now.iso8601
13
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
+
14
24
  build_invisible_captcha(honeypot, scope, options)
15
25
  end
16
26
 
@@ -22,6 +32,10 @@ module InvisibleCaptcha
22
32
 
23
33
  private
24
34
 
35
+ def current_request
36
+ @request ||= request
37
+ end
38
+
25
39
  def build_invisible_captcha(honeypot = nil, scope = nil, options = {})
26
40
  if honeypot.is_a?(Hash)
27
41
  options = honeypot
@@ -42,6 +56,9 @@ module InvisibleCaptcha
42
56
  concat styles unless InvisibleCaptcha.injectable_styles
43
57
  concat label_tag(build_label_name(honeypot, scope), label)
44
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])
61
+ end
45
62
  end
46
63
  end
47
64
 
@@ -54,7 +71,9 @@ module InvisibleCaptcha
54
71
 
55
72
  return if visible
56
73
 
57
- content_tag(:style, media: 'screen') do
74
+ nonce = content_security_policy_nonce if options[:nonce]
75
+
76
+ content_tag(:style, media: 'screen', nonce: nonce) do
58
77
  ".#{css_class} {#{InvisibleCaptcha.css_strategy}}"
59
78
  end
60
79
  end
@@ -1,39 +1,27 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
2
4
  render_views
3
5
 
4
- def switchable_post(action, params = {})
5
- if ::Rails::VERSION::STRING > '5'
6
- post action, params: params
7
- else
8
- post action, params
9
- end
10
- end
11
-
12
- def switchable_put(action, params = {})
13
- if ::Rails::VERSION::STRING > '5'
14
- put action, params: params
15
- else
16
- put action, params
17
- end
18
- end
19
-
20
6
  before(:each) do
21
7
  @controller = TopicsController.new
22
8
  request.env['HTTP_REFERER'] = 'http://test.host/topics'
9
+
23
10
  InvisibleCaptcha.init!
24
11
  InvisibleCaptcha.timestamp_threshold = 1
12
+ InvisibleCaptcha.spinner_enabled = false
25
13
  end
26
14
 
27
15
  context 'without invisible_captcha_timestamp in session' do
28
16
  it 'fails like if it was submitted too fast' do
29
- switchable_post :create, topic: { title: 'foo' }
17
+ post :create, params: { topic: { title: 'foo' } }
30
18
 
31
19
  expect(response).to redirect_to 'http://test.host/topics'
32
20
  expect(flash[:error]).to eq(InvisibleCaptcha.timestamp_error_message)
33
21
  end
34
22
 
35
23
  it 'passes if disabled at action level' do
36
- switchable_post :copy, topic: { title: 'foo' }
24
+ post :copy, params: { topic: { title: 'foo' } }
37
25
 
38
26
  expect(flash[:error]).not_to be_present
39
27
  expect(response.body).to be_present
@@ -41,7 +29,8 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
41
29
 
42
30
  it 'passes if disabled at app level' do
43
31
  InvisibleCaptcha.timestamp_enabled = false
44
- switchable_post :create, topic: { title: 'foo' }
32
+
33
+ post :create, params: { topic: { title: 'foo' } }
45
34
 
46
35
  expect(flash[:error]).not_to be_present
47
36
  expect(response.body).to be_present
@@ -54,7 +43,7 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
54
43
  end
55
44
 
56
45
  it 'fails if submission before timestamp_threshold' do
57
- switchable_post :create, topic: { title: 'foo' }
46
+ post :create, params: { topic: { title: 'foo' } }
58
47
 
59
48
  expect(response).to redirect_to 'http://test.host/topics'
60
49
  expect(flash[:error]).to eq(InvisibleCaptcha.timestamp_error_message)
@@ -64,7 +53,7 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
64
53
  end
65
54
 
66
55
  it 'allows a custom on_timestamp_spam callback' do
67
- switchable_put :update, id: 1, topic: { title: 'bar' }
56
+ put :update, params: { id: 1, topic: { title: 'bar' } }
68
57
 
69
58
  expect(response.status).to eq(204)
70
59
  end
@@ -77,7 +66,7 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
77
66
  end
78
67
  end
79
68
 
80
- expect { switchable_put :update, id: 1, topic: { title: 'bar' } }
69
+ expect { put :update, params: { id: 1, topic: { title: 'bar' } } }
81
70
  .to change { session[:invisible_captcha_timestamp] }
82
71
  .to be_present
83
72
  end
@@ -86,10 +75,12 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
86
75
  it 'passes if submission on or after timestamp_threshold' do
87
76
  sleep InvisibleCaptcha.timestamp_threshold
88
77
 
89
- switchable_post :create, topic: {
90
- title: 'foobar',
91
- author: 'author',
92
- body: 'body that passes validation'
78
+ post :create, params: {
79
+ topic: {
80
+ title: 'foobar',
81
+ author: 'author',
82
+ body: 'body that passes validation'
83
+ }
93
84
  }
94
85
 
95
86
  expect(flash[:error]).not_to be_present
@@ -102,7 +93,7 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
102
93
  it 'allow to set a custom timestamp_threshold per action' do
103
94
  sleep 2 # custom threshold
104
95
 
105
- switchable_post :publish, id: 1
96
+ post :publish, params: { id: 1 }
106
97
 
107
98
  expect(flash[:error]).not_to be_present
108
99
  expect(response.body).to be_present
@@ -113,33 +104,90 @@ RSpec.describe InvisibleCaptcha::ControllerExt, type: :controller do
113
104
  context 'honeypot attribute' do
114
105
  before(:each) do
115
106
  session[:invisible_captcha_timestamp] = Time.zone.now.iso8601
107
+
116
108
  # Wait for valid submission
117
109
  sleep InvisibleCaptcha.timestamp_threshold
118
110
  end
119
111
 
120
112
  it 'fails with spam' do
121
- switchable_post :create, topic: { subtitle: 'foo' }
113
+ post :create, params: { topic: { subtitle: 'foo' } }
122
114
 
123
115
  expect(response.body).to be_blank
124
116
  end
125
117
 
126
118
  it 'passes with no spam' do
127
- switchable_post :create, topic: { title: 'foo' }
119
+ post :create, params: { topic: { title: 'foo' } }
128
120
 
129
121
  expect(response.body).to be_present
130
122
  end
131
123
 
132
124
  it 'allow a custom on_spam callback' do
133
- switchable_put :update, id: 1, topic: { subtitle: 'foo' }
125
+ put :update, params: { id: 1, topic: { subtitle: 'foo' } }
134
126
 
135
127
  expect(response.body).to redirect_to(new_topic_path)
136
128
  end
137
129
 
138
130
  it 'honeypot is removed from params if you use a custom honeypot' do
139
- switchable_post :create, topic: { title: 'foo', subtitle: '' }
131
+ post :create, params: { topic: { title: 'foo', subtitle: '' } }
140
132
 
141
133
  expect(flash[:error]).not_to be_present
142
134
  expect(@controller.params[:topic].key?(:subtitle)).to eq(false)
143
135
  end
136
+
137
+ describe 'ActiveSupport::Notifications' do
138
+ let(:dummy_handler) { double(handle_event: nil) }
139
+
140
+ let!(:subscriber) do
141
+ subscriber = ActiveSupport::Notifications.subscribe('invisible_captcha.spam_detected') do |*args, data|
142
+ dummy_handler.handle_event(data)
143
+ end
144
+
145
+ subscriber
146
+ end
147
+
148
+ after { ActiveSupport::Notifications.unsubscribe(subscriber) }
149
+
150
+ it 'dispatches an `invisible_captcha.spam_detected` event' do
151
+ expect(dummy_handler).to receive(:handle_event).once.with(
152
+ message: "Invisible Captcha honeypot param 'subtitle' was present.",
153
+ remote_ip: '0.0.0.0',
154
+ user_agent: 'Rails Testing',
155
+ controller: 'topics',
156
+ action: 'create',
157
+ url: 'http://test.host/topics',
158
+ params: {
159
+ topic: { subtitle: "foo"},
160
+ controller: 'topics',
161
+ action: 'create'
162
+ }
163
+ )
164
+
165
+ post :create, params: { topic: { subtitle: 'foo' } }
166
+ end
167
+ end
168
+ end
169
+
170
+ context 'spinner attribute' do
171
+ before(:each) do
172
+ InvisibleCaptcha.spinner_enabled = true
173
+ InvisibleCaptcha.secret = 'secret'
174
+ session[:invisible_captcha_timestamp] = Time.zone.now.iso8601
175
+ session[:invisible_captcha_spinner] = '32ab649161f9f6faeeb323746de1a25d'
176
+
177
+ # Wait for valid submission
178
+ sleep InvisibleCaptcha.timestamp_threshold
179
+ end
180
+
181
+ it 'fails with no spam, but mismatch of spinner' do
182
+ post :create, params: { topic: { title: 'foo' }, spinner: 'mismatch' }
183
+
184
+ expect(response.body).to be_blank
185
+ end
186
+
187
+ it 'passes with no spam and spinner match' do
188
+ post :create, params: { topic: { title: 'foo' }, spinner: '32ab649161f9f6faeeb323746de1a25d' }
189
+
190
+ expect(response.body).to be_present
191
+ end
144
192
  end
145
193
  end
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts .js
2
+ //= link_directory ../stylesheets .css
@@ -14,13 +14,8 @@ Dummy::Application.configure do
14
14
 
15
15
  # Disable serving static files from the `/public` folder by default since
16
16
  # Apache or NGINX already handles this.
17
- if Rails.version >= "5.0.0"
18
- config.public_file_server.enabled = true
19
- config.public_file_server.headers = {'Cache-Control' => 'public, max-age=3600'}
20
- else
21
- config.serve_static_files = true
22
- config.static_cache_control = "public, max-age=3600"
23
- end
17
+ config.public_file_server.enabled = true
18
+ config.public_file_server.headers = {'Cache-Control' => 'public, max-age=3600'}
24
19
 
25
20
  # Show full error reports and disable caching.
26
21
  config.consider_all_requests_local = true
@@ -39,4 +34,4 @@ Dummy::Application.configure do
39
34
 
40
35
  # Print deprecation notices to the stderr.
41
36
  config.active_support.deprecation = :stderr
42
- end
37
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe InvisibleCaptcha do
2
4
  it 'initialize with defaults' do
3
5
  InvisibleCaptcha.init!
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV['RAILS_ENV'] = 'test'
2
4
 
3
5
  require File.expand_path("../dummy/config/environment.rb", __FILE__)
@@ -5,6 +7,7 @@ require 'rspec/rails'
5
7
  require 'invisible_captcha'
6
8
 
7
9
  RSpec.configure do |config|
10
+ config.include ActionDispatch::ContentSecurityPolicy::Request, type: :helper
8
11
  config.disable_monkey_patching!
9
12
  config.order = :random
10
13
  config.expect_with :rspec
@@ -12,17 +15,3 @@ RSpec.configure do |config|
12
15
  mocks.verify_partial_doubles = true
13
16
  end
14
17
  end
15
-
16
- # Rails 4.2 call `initialize` inside `recycle!`. However Ruby 2.6 doesn't allow calling `initialize` twice.
17
- # More info: https://github.com/rails/rails/issues/34790
18
- if RUBY_VERSION >= "2.6.0" && Rails.version < "5"
19
- module ActionController
20
- class TestResponse < ActionDispatch::TestResponse
21
- def recycle!
22
- @mon_mutex_owner_object_id = nil
23
- @mon_mutex = nil
24
- initialize
25
- end
26
- end
27
- end
28
- end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe InvisibleCaptcha::ViewHelpers, type: :helper do
2
4
  before(:each) do
3
- allow(Time.zone).to receive(:now).and_return(Time.zone.parse('Feb 19 1986'))
4
5
  allow(InvisibleCaptcha).to receive(:css_strategy).and_return("display:none;")
6
+ allow_any_instance_of(ActionDispatch::ContentSecurityPolicy::Request).to receive(:content_security_policy_nonce).and_return('123')
5
7
 
6
8
  # to test content_for and provide
7
9
  @view_flow = ActionView::OutputFlow.new
@@ -26,6 +28,10 @@ RSpec.describe InvisibleCaptcha::ViewHelpers, type: :helper do
26
28
  expect(invisible_captcha(:subtitle, :topic, { class: 'foo_class' })).to match(/class="foo_class"/)
27
29
  end
28
30
 
31
+ it 'with CSP nonce' do
32
+ expect(invisible_captcha(:subtitle, :topic, { nonce: true })).to match(/nonce="123"/)
33
+ end
34
+
29
35
  it 'generated html + styles' do
30
36
  InvisibleCaptcha.honeypots = [:foo_id]
31
37
  output = invisible_captcha.gsub("\"", "'")
@@ -52,6 +58,18 @@ RSpec.describe InvisibleCaptcha::ViewHelpers, type: :helper do
52
58
  end
53
59
  end
54
60
 
61
+ context "should have spinner field" do
62
+ it 'that exists by default, spinner_enabled is true' do
63
+ InvisibleCaptcha.spinner_enabled = true
64
+ expect(invisible_captcha).to match(/spinner/)
65
+ end
66
+
67
+ it 'that does not exist if spinner_enabled is false' do
68
+ InvisibleCaptcha.spinner_enabled = false
69
+ expect(invisible_captcha).not_to match(/spinner/)
70
+ end
71
+ end
72
+
55
73
  it 'should set spam timestamp' do
56
74
  invisible_captcha
57
75
  expect(session[:invisible_captcha_timestamp]).to eq(Time.zone.now.iso8601)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: invisible_captcha
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.2
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc Anguera Insa
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-27 00:00:00.000000000 Z
11
+ date: 2021-02-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,30 +16,16 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 3.2.0
19
+ version: '5.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 3.2.0
26
+ version: '5.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec-rails
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '3.1'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '3.1'
41
- - !ruby/object:Gem::Dependency
42
- name: appraisal
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
31
  - - ">="
@@ -53,21 +39,7 @@ dependencies:
53
39
  - !ruby/object:Gem::Version
54
40
  version: '0'
55
41
  - !ruby/object:Gem::Dependency
56
- name: test-unit
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '3.0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '3.0'
69
- - !ruby/object:Gem::Dependency
70
- name: byebug
42
+ name: appraisal
71
43
  requirement: !ruby/object:Gem::Requirement
72
44
  requirements:
73
45
  - - ">="
@@ -80,7 +52,7 @@ dependencies:
80
52
  - - ">="
81
53
  - !ruby/object:Gem::Version
82
54
  version: '0'
83
- description: Unobtrusive, flexible and simple spam protection for Rails applications
55
+ description: Unobtrusive, flexible and complete spam protection for Rails applications
84
56
  using honeypot strategy for better user experience.
85
57
  email:
86
58
  - srmarc.ai@gmail.com
@@ -97,12 +69,9 @@ files:
97
69
  - LICENSE
98
70
  - README.md
99
71
  - Rakefile
100
- - gemfiles/rails_3.2.gemfile
101
- - gemfiles/rails_4.2.gemfile
102
- - gemfiles/rails_5.0.gemfile
103
- - gemfiles/rails_5.1.gemfile
104
72
  - gemfiles/rails_5.2.gemfile
105
73
  - gemfiles/rails_6.0.gemfile
74
+ - gemfiles/rails_6.1.gemfile
106
75
  - invisible_captcha.gemspec
107
76
  - lib/invisible_captcha.rb
108
77
  - lib/invisible_captcha/controller_ext.rb
@@ -113,6 +82,7 @@ files:
113
82
  - spec/controllers_spec.rb
114
83
  - spec/dummy/README.md
115
84
  - spec/dummy/Rakefile
85
+ - spec/dummy/app/assets/config/manifest.js
116
86
  - spec/dummy/app/assets/javascripts/application.js
117
87
  - spec/dummy/app/assets/stylesheets/application.css
118
88
  - spec/dummy/app/controllers/application_controller.rb
@@ -175,11 +145,12 @@ requirements: []
175
145
  rubygems_version: 3.0.3
176
146
  signing_key:
177
147
  specification_version: 4
178
- summary: Simple honeypot protection for RoR apps
148
+ summary: Honeypot spam protection for Rails
179
149
  test_files:
180
150
  - spec/controllers_spec.rb
181
151
  - spec/dummy/README.md
182
152
  - spec/dummy/Rakefile
153
+ - spec/dummy/app/assets/config/manifest.js
183
154
  - spec/dummy/app/assets/javascripts/application.js
184
155
  - spec/dummy/app/assets/stylesheets/application.css
185
156
  - spec/dummy/app/controllers/application_controller.rb
@@ -1,7 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "rails", "~> 4.2.0"
6
-
7
- gemspec path: "../"
@@ -1,7 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "rails", "~> 5.0.0"
6
-
7
- gemspec path: "../"
@@ -1,7 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "rails", "~> 5.1.0"
6
-
7
- gemspec path: "../"