invisible_captcha 0.9.3 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +10 -20
- data/README.md +29 -14
- data/Rakefile +1 -1
- data/invisible_captcha.gemspec +1 -1
- data/lib/invisible_captcha.rb +25 -15
- data/lib/invisible_captcha/controller_ext.rb +29 -20
- data/lib/invisible_captcha/version.rb +1 -1
- data/lib/invisible_captcha/view_helpers.rb +28 -16
- data/spec/controllers_spec.rb +23 -13
- data/spec/dummy/README.md +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +1 -15
- data/spec/dummy/app/assets/stylesheets/application.css +31 -13
- data/spec/dummy/app/controllers/topics_controller.rb +19 -1
- data/spec/dummy/app/models/topic.rb +4 -2
- data/spec/dummy/app/views/layouts/application.html.erb +16 -12
- data/spec/dummy/app/views/topics/new.html.erb +14 -11
- data/spec/dummy/config/application.rb +1 -0
- data/spec/dummy/config/environments/development.rb +3 -0
- data/spec/dummy/config/routes.rb +3 -4
- data/spec/invisible_captcha_spec.rb +2 -7
- data/spec/view_helpers_spec.rb +38 -44
- metadata +8 -12
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/config/initializers/invisible_captcha.rb +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b8877b6b9e63a8e3469df1b1e4d564c323798f4
|
4
|
+
data.tar.gz: aa93c8cd7004683493f06ba0513bc4cce274f261
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 32dca9e9c96528181b6665854d07b2edba9441390613824975617f26d263e747475688979224f09d6b582c3c762fa4f438517924534f361ffc7d266ca78d3d39
|
7
|
+
data.tar.gz: f8a13ba2c4885e921668617788d751050ae47a86714c6537a645fbb40071996634a603f31f4a6ff1cdbe723bfc2dbf940a9f82874306baaf98728ea1f8c23cba
|
data/.travis.yml
CHANGED
@@ -1,40 +1,30 @@
|
|
1
1
|
language: ruby
|
2
|
-
|
3
2
|
cache: bundler
|
4
|
-
|
5
3
|
sudo: false
|
6
|
-
|
7
4
|
rvm:
|
8
5
|
- ruby-head
|
9
|
-
- 2.4.
|
10
|
-
- 2.3.
|
11
|
-
- 2.2.
|
12
|
-
- 2.1
|
13
|
-
- 1.9.3
|
14
|
-
|
6
|
+
- 2.4.2
|
7
|
+
- 2.3.5
|
8
|
+
- 2.2.8
|
9
|
+
- 2.1.10
|
15
10
|
gemfile:
|
16
11
|
- gemfiles/rails_5.1.gemfile
|
17
12
|
- gemfiles/rails_5.0.gemfile
|
18
13
|
- gemfiles/rails_4.2.gemfile
|
19
14
|
- gemfiles/rails_4.1.gemfile
|
20
15
|
- gemfiles/rails_3.2.gemfile
|
21
|
-
|
22
16
|
matrix:
|
23
17
|
exclude:
|
24
|
-
- rvm: 1.
|
25
|
-
gemfile: gemfiles/rails_4.2.gemfile
|
26
|
-
- rvm: 1.9.3
|
18
|
+
- rvm: 2.1.10
|
27
19
|
gemfile: gemfiles/rails_5.0.gemfile
|
28
|
-
- rvm: 1.
|
20
|
+
- rvm: 2.1.10
|
29
21
|
gemfile: gemfiles/rails_5.1.gemfile
|
30
|
-
- rvm: 2.
|
31
|
-
gemfile: gemfiles/rails_5.0.gemfile
|
32
|
-
- rvm: 2.1
|
33
|
-
gemfile: gemfiles/rails_5.1.gemfile
|
34
|
-
- rvm: 2.4.1
|
22
|
+
- rvm: 2.4.2
|
35
23
|
gemfile: gemfiles/rails_4.1.gemfile
|
36
|
-
- rvm: 2.4.
|
24
|
+
- rvm: 2.4.2
|
37
25
|
gemfile: gemfiles/rails_3.2.gemfile
|
26
|
+
- rvm: ruby-head
|
27
|
+
gemfile: gemfiles/rails_4.1.gemfile
|
38
28
|
- rvm: ruby-head
|
39
29
|
gemfile: gemfiles/rails_3.2.gemfile
|
40
30
|
allow_failures:
|
data/README.md
CHANGED
@@ -4,19 +4,21 @@
|
|
4
4
|
|
5
5
|
> Simple and flexible spam protection solution for Rails applications.
|
6
6
|
|
7
|
-
|
7
|
+
Invisible Captcha provides different techniques to protect your application against spambots.
|
8
8
|
|
9
|
-
|
9
|
+
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
10
|
|
11
|
-
|
11
|
+
Essentially, the strategy consists on adding an input field :honey_pot: into the form that:
|
12
12
|
|
13
13
|
* shouldn't be visible by the real users
|
14
14
|
* should be left empty by the real users
|
15
15
|
* will most be filled by spam bots
|
16
16
|
|
17
|
+
It also comes with a time-sensitive :hourglass: form submission.
|
18
|
+
|
17
19
|
## Installation
|
18
20
|
|
19
|
-
Invisible Captcha is tested against Rails `>= 3.2` and Ruby `>= 1
|
21
|
+
Invisible Captcha is tested against Rails `>= 3.2` and Ruby `>= 2.1`.
|
20
22
|
|
21
23
|
Add this line to you Gemfile:
|
22
24
|
|
@@ -64,7 +66,7 @@ class TopicsController < ApplicationController
|
|
64
66
|
end
|
65
67
|
```
|
66
68
|
|
67
|
-
Note that
|
69
|
+
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:
|
68
70
|
|
69
71
|
```erb
|
70
72
|
<%= form_tag(new_contact_path) do |f| %>
|
@@ -87,20 +89,23 @@ This section contains a description of all plugin options and customizations.
|
|
87
89
|
You can customize:
|
88
90
|
|
89
91
|
* `sentence_for_humans`: text for real users if input field was visible. By default, it uses I18n (see below).
|
90
|
-
* `honeypots`: collection of default honeypots. Used by the view helper, called with no args, to generate a random honeypot field name.
|
92
|
+
* `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.
|
91
93
|
* `visual_honeypots`: make honeypots visible, also useful to test/debug your implementation.
|
92
94
|
* `timestamp_threshold`: fastest time (in seconds) to expect a human to submit the form (see [original article by Yoav Aner](http://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"`).
|
93
95
|
* `timestamp_enabled`: option to disable the time threshold check at application level. Could be useful, for example, on some testing scenarios. By default, true.
|
94
96
|
* `timestamp_error_message`: flash error message thrown when form submitted quicker than the `timestamp_threshold` value. It uses I18n by default.
|
97
|
+
* `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.
|
95
98
|
|
96
99
|
To change these defaults, add the following to an initializer (recommended `config/initializers/invisible_captcha.rb`):
|
97
100
|
|
98
101
|
```ruby
|
99
102
|
InvisibleCaptcha.setup do |config|
|
100
|
-
config.honeypots << '
|
101
|
-
config.visual_honeypots = false
|
102
|
-
config.timestamp_threshold = 4
|
103
|
-
config.timestamp_enabled = true
|
103
|
+
# config.honeypots << ['more', 'fake', 'attribute', 'names']
|
104
|
+
# config.visual_honeypots = false
|
105
|
+
# config.timestamp_threshold = 4
|
106
|
+
# config.timestamp_enabled = true
|
107
|
+
# config.injectable_styles = false
|
108
|
+
|
104
109
|
# Leave these unset if you want to use I18n (see below)
|
105
110
|
# config.sentence_for_humans = 'If you are a human, ignore this field'
|
106
111
|
# config.timestamp_error_message = 'Sorry, that was too quick! Please resubmit.'
|
@@ -113,9 +118,10 @@ The `invisible_captcha` method accepts some options:
|
|
113
118
|
|
114
119
|
* `only`: apply to given controller actions.
|
115
120
|
* `except`: exclude to given controller actions.
|
116
|
-
* `honeypot`: name of honeypot.
|
121
|
+
* `honeypot`: name of custom honeypot.
|
117
122
|
* `scope`: name of scope, ie: 'topic[subtitle]' -> 'topic' is the scope.
|
118
123
|
* `on_spam`: custom callback to be called on spam detection.
|
124
|
+
* `timestamp_threshold`: enable/disable this technique at action level.
|
119
125
|
* `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]`.
|
120
126
|
* `timestamp_threshold`: custom threshold per controller/action. Overrides the global value for `InvisibleCaptcha.timestamp_threshold`.
|
121
127
|
|
@@ -125,12 +131,18 @@ Using the view/form helper you can override some defaults for the given instance
|
|
125
131
|
|
126
132
|
```erb
|
127
133
|
<%= form_for(@topic) do |f| %>
|
128
|
-
<%= f.invisible_captcha :subtitle, visual_honeypots: true, sentence_for_humans: "
|
134
|
+
<%= f.invisible_captcha :subtitle, visual_honeypots: true, sentence_for_humans: "hey! leave this input empty!" %>
|
129
135
|
<!-- or -->
|
130
|
-
<%= invisible_captcha visual_honeypots: true, sentence_for_humans: "
|
136
|
+
<%= invisible_captcha visual_honeypots: true, sentence_for_humans: "hey! leave this input empty!" %>
|
131
137
|
<% end %>
|
132
138
|
```
|
133
139
|
|
140
|
+
You can also pass html options to the input:
|
141
|
+
|
142
|
+
```erb
|
143
|
+
<%= invisible_captcha :subtitle, :topic, id: "your_id", class: "your_class" %>
|
144
|
+
```
|
145
|
+
|
134
146
|
### I18n
|
135
147
|
|
136
148
|
`invisible_captcha` tries to use I18n when it's available by default. The keys it looks for are the following:
|
@@ -163,9 +175,12 @@ $ bundle exec rspec
|
|
163
175
|
Run the test suite against all supported versions:
|
164
176
|
|
165
177
|
```
|
166
|
-
$ bundle exec appraisal
|
178
|
+
$ bundle exec appraisal install
|
179
|
+
$ bundle exec appraisal rspec
|
167
180
|
```
|
168
181
|
|
182
|
+
### Demo
|
183
|
+
|
169
184
|
Start a sample Rails app ([source code](spec/dummy)) with `InvisibleCaptcha` integrated:
|
170
185
|
|
171
186
|
```
|
data/Rakefile
CHANGED
data/invisible_captcha.gemspec
CHANGED
@@ -20,6 +20,6 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.add_development_dependency 'rspec-rails', '~> 3.1'
|
21
21
|
spec.add_development_dependency 'appraisal'
|
22
22
|
spec.add_development_dependency 'test-unit', '~> 3.0'
|
23
|
-
spec.add_development_dependency '
|
23
|
+
spec.add_development_dependency 'byebug'
|
24
24
|
end
|
25
25
|
|
data/lib/invisible_captcha.rb
CHANGED
@@ -7,45 +7,39 @@ require 'invisible_captcha/railtie'
|
|
7
7
|
module InvisibleCaptcha
|
8
8
|
class << self
|
9
9
|
attr_writer :sentence_for_humans,
|
10
|
-
:timestamp_error_message
|
11
|
-
:error_message
|
10
|
+
:timestamp_error_message
|
12
11
|
|
13
12
|
attr_accessor :honeypots,
|
14
13
|
:timestamp_threshold,
|
15
14
|
:timestamp_enabled,
|
16
|
-
:visual_honeypots
|
15
|
+
:visual_honeypots,
|
16
|
+
:injectable_styles
|
17
17
|
|
18
18
|
def init!
|
19
19
|
# Default sentence for real users if text field was visible
|
20
20
|
self.sentence_for_humans = -> { I18n.t('invisible_captcha.sentence_for_humans', default: 'If you are a human, ignore this field') }
|
21
21
|
|
22
|
-
#
|
23
|
-
self.
|
24
|
-
|
25
|
-
# Default fake fields for controller based workflow
|
26
|
-
self.honeypots = ['foo_id', 'bar_id', 'baz_id']
|
22
|
+
# Timestamp check enabled by default
|
23
|
+
self.timestamp_enabled = true
|
27
24
|
|
28
25
|
# Fastest time (in seconds) to expect a human to submit the form
|
29
26
|
self.timestamp_threshold = 4
|
30
27
|
|
31
|
-
# Timestamp check enabled by default
|
32
|
-
self.timestamp_enabled = true
|
33
|
-
|
34
28
|
# Default error message for validator when form submitted too quickly
|
35
29
|
self.timestamp_error_message = -> { I18n.t('invisible_captcha.timestamp_error_message', default: 'Sorry, that was too quick! Please resubmit.') }
|
36
30
|
|
37
31
|
# Make honeypots visibles
|
38
32
|
self.visual_honeypots = false
|
33
|
+
|
34
|
+
# If enabled, you should call anywhere in of your layout the following helper, to inject the honeypot styles:
|
35
|
+
# <%= invisible_captcha_styles %>
|
36
|
+
self.injectable_styles = false
|
39
37
|
end
|
40
38
|
|
41
39
|
def sentence_for_humans
|
42
40
|
call_lambda_or_return(@sentence_for_humans)
|
43
41
|
end
|
44
42
|
|
45
|
-
def error_message
|
46
|
-
call_lambda_or_return(@error_message)
|
47
|
-
end
|
48
|
-
|
49
43
|
def timestamp_error_message
|
50
44
|
call_lambda_or_return(@timestamp_error_message)
|
51
45
|
end
|
@@ -54,10 +48,26 @@ module InvisibleCaptcha
|
|
54
48
|
yield(self) if block_given?
|
55
49
|
end
|
56
50
|
|
51
|
+
def honeypots
|
52
|
+
@honeypots ||= (1..5).map { generate_random_honeypot }
|
53
|
+
end
|
54
|
+
|
55
|
+
def generate_random_honeypot
|
56
|
+
"abcdefghijkl-mnopqrstuvwxyz".chars.sample(rand(10..20)).join
|
57
|
+
end
|
58
|
+
|
57
59
|
def get_honeypot
|
58
60
|
honeypots.sample
|
59
61
|
end
|
60
62
|
|
63
|
+
def css_strategy
|
64
|
+
[
|
65
|
+
"display:none;",
|
66
|
+
"position:absolute!important;top:-9999px;left:-9999px;",
|
67
|
+
"position:absolute!important;height:1px;width:1px;overflow:hidden;"
|
68
|
+
].sample
|
69
|
+
end
|
70
|
+
|
61
71
|
private
|
62
72
|
|
63
73
|
def call_lambda_or_return(obj)
|
@@ -2,7 +2,7 @@ module InvisibleCaptcha
|
|
2
2
|
module ControllerExt
|
3
3
|
module ClassMethods
|
4
4
|
def invisible_captcha(options = {})
|
5
|
-
if respond_to?
|
5
|
+
if respond_to?(:before_action)
|
6
6
|
before_action(options) do
|
7
7
|
detect_spam(options)
|
8
8
|
end
|
@@ -14,15 +14,17 @@ module InvisibleCaptcha
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
private
|
18
|
+
|
17
19
|
def detect_spam(options = {})
|
18
|
-
if
|
19
|
-
|
20
|
-
elsif
|
21
|
-
|
20
|
+
if timestamp_spam?(options)
|
21
|
+
on_timestamp_spam(options)
|
22
|
+
elsif honeypot_spam?(options)
|
23
|
+
on_spam(options)
|
22
24
|
end
|
23
25
|
end
|
24
26
|
|
25
|
-
def
|
27
|
+
def on_timestamp_spam(options = {})
|
26
28
|
if action = options[:on_timestamp_spam]
|
27
29
|
send(action)
|
28
30
|
else
|
@@ -34,23 +36,23 @@ module InvisibleCaptcha
|
|
34
36
|
end
|
35
37
|
end
|
36
38
|
|
37
|
-
def
|
39
|
+
def on_spam(options = {})
|
38
40
|
if action = options[:on_spam]
|
39
41
|
send(action)
|
40
42
|
else
|
41
|
-
|
43
|
+
head(200)
|
42
44
|
end
|
43
45
|
end
|
44
46
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
unless InvisibleCaptcha.timestamp_enabled
|
51
|
-
return false
|
47
|
+
def timestamp_spam?(options = {})
|
48
|
+
enabled = if options.key?(:timestamp_enabled)
|
49
|
+
options[:timestamp_enabled]
|
50
|
+
else
|
51
|
+
InvisibleCaptcha.timestamp_enabled
|
52
52
|
end
|
53
53
|
|
54
|
+
return false unless enabled
|
55
|
+
|
54
56
|
timestamp = session[:invisible_captcha_timestamp]
|
55
57
|
|
56
58
|
# Consider as spam if timestamp not in session, cause that means the form was not fetched at all
|
@@ -60,31 +62,38 @@ module InvisibleCaptcha
|
|
60
62
|
end
|
61
63
|
|
62
64
|
time_to_submit = Time.zone.now - DateTime.iso8601(timestamp)
|
65
|
+
threshold = options[:timestamp_threshold] || InvisibleCaptcha.timestamp_threshold
|
63
66
|
|
64
67
|
# Consider as spam if form submitted too quickly
|
65
|
-
if time_to_submit <
|
68
|
+
if time_to_submit < threshold
|
66
69
|
logger.warn("Potential spam detected for IP #{request.env['REMOTE_ADDR']}. Invisible Captcha timestamp threshold not reached (took #{time_to_submit.to_i}s).")
|
67
70
|
return true
|
68
71
|
end
|
72
|
+
|
69
73
|
false
|
70
74
|
end
|
71
75
|
|
72
|
-
def
|
76
|
+
def honeypot_spam?(options = {})
|
73
77
|
honeypot = options[:honeypot]
|
74
78
|
scope = options[:scope] || controller_name.singularize
|
75
79
|
|
76
80
|
if honeypot
|
77
|
-
# If honeypot is
|
81
|
+
# If honeypot is defined for this controller-action, search for:
|
78
82
|
# - honeypot: params[:subtitle]
|
79
83
|
# - honeypot with scope: params[:topic][:subtitle]
|
80
84
|
if params[honeypot].present? || (params[scope] && params[scope][honeypot].present?)
|
81
85
|
return true
|
86
|
+
else
|
87
|
+
# No honeypot spam detected, remove honeypot from params to avoid UnpermittedParameters exceptions
|
88
|
+
params.delete(honeypot) if params.key?(honeypot)
|
89
|
+
params[scope].try(:delete, honeypot) if params.key?(scope)
|
82
90
|
end
|
83
91
|
else
|
84
|
-
InvisibleCaptcha.honeypots.each do |
|
85
|
-
return true if params[
|
92
|
+
InvisibleCaptcha.honeypots.each do |default_honeypot|
|
93
|
+
return true if params[default_honeypot].present?
|
86
94
|
end
|
87
95
|
end
|
96
|
+
|
88
97
|
false
|
89
98
|
end
|
90
99
|
end
|
@@ -4,14 +4,22 @@ module InvisibleCaptcha
|
|
4
4
|
#
|
5
5
|
# @param honeypot [Symbol] name of honeypot, ie: subtitle => input name: subtitle
|
6
6
|
# @param scope [Symbol] name of honeypot scope, ie: topic => input name: topic[subtitle]
|
7
|
+
# @param options [Hash] html_options for input and invisible_captcha options
|
8
|
+
#
|
7
9
|
# @return [String] the generated html
|
8
10
|
def invisible_captcha(honeypot = nil, scope = nil, options = {})
|
9
11
|
if InvisibleCaptcha.timestamp_enabled
|
10
|
-
session[:invisible_captcha_timestamp]
|
12
|
+
session[:invisible_captcha_timestamp] = Time.zone.now.iso8601
|
11
13
|
end
|
12
14
|
build_invisible_captcha(honeypot, scope, options)
|
13
15
|
end
|
14
16
|
|
17
|
+
def invisible_captcha_styles
|
18
|
+
if content_for?(:invisible_captcha_styles)
|
19
|
+
content_for(:invisible_captcha_styles)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
15
23
|
private
|
16
24
|
|
17
25
|
def build_invisible_captcha(honeypot = nil, scope = nil, options = {})
|
@@ -20,30 +28,34 @@ module InvisibleCaptcha
|
|
20
28
|
honeypot = nil
|
21
29
|
end
|
22
30
|
|
23
|
-
honeypot
|
24
|
-
label
|
25
|
-
|
31
|
+
honeypot = honeypot ? honeypot.to_s : InvisibleCaptcha.get_honeypot
|
32
|
+
label = options.delete(:sentence_for_humans) || InvisibleCaptcha.sentence_for_humans
|
33
|
+
css_class = "#{honeypot}_#{Time.zone.now.to_i}"
|
34
|
+
|
35
|
+
styles = visibility_css(css_class, options)
|
26
36
|
|
27
|
-
|
28
|
-
|
37
|
+
provide(:invisible_captcha_styles) do
|
38
|
+
styles
|
39
|
+
end if InvisibleCaptcha.injectable_styles
|
40
|
+
|
41
|
+
content_tag(:div, class: css_class) do
|
42
|
+
concat styles unless InvisibleCaptcha.injectable_styles
|
29
43
|
concat label_tag(build_label_name(honeypot, scope), label)
|
30
|
-
concat text_field_tag(build_text_field_name(honeypot, scope))
|
44
|
+
concat text_field_tag(build_text_field_name(honeypot, scope), nil, options.merge(tabindex: -1))
|
31
45
|
end
|
32
46
|
end
|
33
47
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
def visibility_css(container_id, options)
|
39
|
-
visibility = if options.key?(:visual_honeypots)
|
40
|
-
options[:visual_honeypots]
|
48
|
+
def visibility_css(css_class, options)
|
49
|
+
visible = if options.key?(:visual_honeypots)
|
50
|
+
options.delete(:visual_honeypots)
|
41
51
|
else
|
42
52
|
InvisibleCaptcha.visual_honeypots
|
43
53
|
end
|
44
54
|
|
45
|
-
|
46
|
-
|
55
|
+
return if visible
|
56
|
+
|
57
|
+
content_tag(:style, media: 'screen') do
|
58
|
+
".#{css_class} {#{InvisibleCaptcha.css_strategy}}"
|
47
59
|
end
|
48
60
|
end
|
49
61
|
|
data/spec/controllers_spec.rb
CHANGED
@@ -19,25 +19,29 @@ describe InvisibleCaptcha::ControllerExt, type: :controller do
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
before do
|
22
|
+
before(:each) do
|
23
23
|
@controller = TopicsController.new
|
24
|
+
request.env['HTTP_REFERER'] = 'http://test.host/topics'
|
25
|
+
InvisibleCaptcha.init!
|
24
26
|
InvisibleCaptcha.timestamp_threshold = 1
|
25
|
-
InvisibleCaptcha.timestamp_enabled = true
|
26
27
|
end
|
27
28
|
|
28
29
|
context 'without invisible_captcha_timestamp in session' do
|
29
30
|
it 'fails like if it was submitted too fast' do
|
30
|
-
request.env['HTTP_REFERER'] = 'http://test.host/topics'
|
31
31
|
switchable_post :create, topic: { title: 'foo' }
|
32
32
|
|
33
33
|
expect(response).to redirect_to 'http://test.host/topics'
|
34
34
|
expect(flash[:error]).to eq(InvisibleCaptcha.timestamp_error_message)
|
35
35
|
end
|
36
|
-
end
|
37
36
|
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
it 'passes if disabled at action level' do
|
38
|
+
switchable_post :copy, topic: { title: 'foo' }
|
39
|
+
|
40
|
+
expect(flash[:error]).not_to be_present
|
41
|
+
expect(response.body).to be_present
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'passes if disabled at app level' do
|
41
45
|
InvisibleCaptcha.timestamp_enabled = false
|
42
46
|
switchable_post :create, topic: { title: 'foo' }
|
43
47
|
|
@@ -47,22 +51,21 @@ describe InvisibleCaptcha::ControllerExt, type: :controller do
|
|
47
51
|
end
|
48
52
|
|
49
53
|
context 'submission timestamp_threshold' do
|
50
|
-
before do
|
54
|
+
before(:each) do
|
51
55
|
session[:invisible_captcha_timestamp] = Time.zone.now.iso8601
|
52
56
|
end
|
53
57
|
|
54
58
|
it 'fails if submission before timestamp_threshold' do
|
55
|
-
request.env['HTTP_REFERER'] = 'http://test.host/topics'
|
56
59
|
switchable_post :create, topic: { title: 'foo' }
|
57
60
|
|
58
61
|
expect(response).to redirect_to 'http://test.host/topics'
|
59
62
|
expect(flash[:error]).to eq(InvisibleCaptcha.timestamp_error_message)
|
60
63
|
end
|
61
64
|
|
62
|
-
it 'allow custom on_timestamp_spam callback' do
|
65
|
+
it 'allow a custom on_timestamp_spam callback' do
|
63
66
|
switchable_put :update, id: 1, topic: { title: 'bar' }
|
64
67
|
|
65
|
-
expect(response).to
|
68
|
+
expect(response.status).to eq(204)
|
66
69
|
end
|
67
70
|
|
68
71
|
context 'successful submissions' do
|
@@ -87,7 +90,7 @@ describe InvisibleCaptcha::ControllerExt, type: :controller do
|
|
87
90
|
end
|
88
91
|
|
89
92
|
context 'honeypot attribute' do
|
90
|
-
before do
|
93
|
+
before(:each) do
|
91
94
|
session[:invisible_captcha_timestamp] = Time.zone.now.iso8601
|
92
95
|
# Wait for valid submission
|
93
96
|
sleep InvisibleCaptcha.timestamp_threshold
|
@@ -105,10 +108,17 @@ describe InvisibleCaptcha::ControllerExt, type: :controller do
|
|
105
108
|
expect(response.body).to be_present
|
106
109
|
end
|
107
110
|
|
108
|
-
it 'allow custom on_spam callback' do
|
111
|
+
it 'allow a custom on_spam callback' do
|
109
112
|
switchable_put :update, id: 1, topic: { subtitle: 'foo' }
|
110
113
|
|
111
114
|
expect(response.body).to redirect_to(new_topic_path)
|
112
115
|
end
|
116
|
+
|
117
|
+
it 'honeypot is removed from params if you use a custom honeypot' do
|
118
|
+
switchable_post :create, topic: { title: 'foo', subtitle: '' }
|
119
|
+
|
120
|
+
expect(flash[:error]).not_to be_present
|
121
|
+
expect(@controller.params[:topic].key?(:subtitle)).to eq(false)
|
122
|
+
end
|
113
123
|
end
|
114
124
|
end
|
data/spec/dummy/README.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
1
|
# Dummy App
|
2
2
|
|
3
3
|
Dummy Rails Application to test `Invisible Captcha`.
|
4
|
+
|
5
|
+
It's also used as a demo application to show `Invisible Captcha` in action. You can run the app by using the following command, from the root of the project:
|
6
|
+
|
7
|
+
> bundle exec rake web
|
8
|
+
|
9
|
+
[« Back to Docs](https://github.com/markets/invisible_captcha#invisible-captcha)
|
@@ -1,15 +1 @@
|
|
1
|
-
|
2
|
-
// listed below.
|
3
|
-
//
|
4
|
-
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
-
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
-
//
|
7
|
-
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
-
// the compiled file.
|
9
|
-
//
|
10
|
-
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
11
|
-
// GO AFTER THE REQUIRES BELOW.
|
12
|
-
//
|
13
|
-
//= require jquery
|
14
|
-
//= require jquery_ujs
|
15
|
-
//= require_tree .
|
1
|
+
console.log('Hi from Invisible Captcha!');
|
@@ -1,13 +1,31 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
1
|
+
body {
|
2
|
+
font-family: Arial, Helvetica, sans-serif;
|
3
|
+
background-color: #ccc;
|
4
|
+
margin: 2em;
|
5
|
+
}
|
6
|
+
|
7
|
+
h1 {
|
8
|
+
border-bottom: 3px solid;
|
9
|
+
}
|
10
|
+
|
11
|
+
input,
|
12
|
+
textarea {
|
13
|
+
border: 0;
|
14
|
+
margin-bottom: 1.5em;
|
15
|
+
}
|
16
|
+
|
17
|
+
input {
|
18
|
+
height: 2em;
|
19
|
+
}
|
20
|
+
|
21
|
+
button {
|
22
|
+
background-color: #f0f0f0;
|
23
|
+
border-radius: 0.25em;
|
24
|
+
height: 3em;
|
25
|
+
width: 10em;
|
26
|
+
font-size: 1em;
|
27
|
+
}
|
28
|
+
|
29
|
+
.errors {
|
30
|
+
color: darkred;
|
31
|
+
}
|
@@ -1,10 +1,18 @@
|
|
1
1
|
class TopicsController < ApplicationController
|
2
2
|
invisible_captcha honeypot: :subtitle, only: :create
|
3
|
+
|
3
4
|
invisible_captcha honeypot: :subtitle, only: :update,
|
4
5
|
on_spam: :custom_callback,
|
5
6
|
on_timestamp_spam: :custom_timestamp_callback
|
7
|
+
|
6
8
|
invisible_captcha honeypot: :subtitle, only: :publish, timestamp_threshold: 2
|
7
9
|
|
10
|
+
invisible_captcha honeypot: :subtitle, only: :copy, timestamp_enabled: false
|
11
|
+
|
12
|
+
def index
|
13
|
+
redirect_to new_topic_path
|
14
|
+
end
|
15
|
+
|
8
16
|
def new
|
9
17
|
@topic = Topic.new
|
10
18
|
end
|
@@ -26,6 +34,16 @@ class TopicsController < ApplicationController
|
|
26
34
|
redirect_to new_topic_path
|
27
35
|
end
|
28
36
|
|
37
|
+
def copy
|
38
|
+
@topic = Topic.new(params[:topic])
|
39
|
+
|
40
|
+
if @topic.valid?
|
41
|
+
redirect_to new_topic_path(context: params[:context]), notice: 'Success!'
|
42
|
+
else
|
43
|
+
render action: 'new'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
29
47
|
private
|
30
48
|
|
31
49
|
def custom_callback
|
@@ -33,6 +51,6 @@ class TopicsController < ApplicationController
|
|
33
51
|
end
|
34
52
|
|
35
53
|
def custom_timestamp_callback
|
36
|
-
|
54
|
+
head(204)
|
37
55
|
end
|
38
56
|
end
|
@@ -2,9 +2,11 @@ class Topic
|
|
2
2
|
include ActiveModel::Validations
|
3
3
|
include ActiveModel::Conversion
|
4
4
|
|
5
|
-
attr_accessor :title, :body, :subtitle
|
5
|
+
attr_accessor :title, :author, :body, :subtitle
|
6
6
|
|
7
|
-
validates :title,
|
7
|
+
validates :title, length: { minimum: 5 }
|
8
|
+
validates :author, presence: true
|
9
|
+
validates :body, length: { minimum: 10 }
|
8
10
|
|
9
11
|
def initialize(attributes = {})
|
10
12
|
attributes.each do |name, value|
|
@@ -5,21 +5,25 @@
|
|
5
5
|
<%= stylesheet_link_tag "application", :media => "all" %>
|
6
6
|
<%= javascript_include_tag "application" %>
|
7
7
|
<%= csrf_meta_tags %>
|
8
|
+
<%= invisible_captcha_styles %>
|
8
9
|
</head>
|
9
10
|
<body>
|
11
|
+
<h1>InvisibleCaptcha v<%= InvisibleCaptcha::VERSION %> - Demo</h1>
|
10
12
|
|
11
|
-
|
12
|
-
<%= link_to "
|
13
|
+
<p>
|
14
|
+
<%= link_to "Default settings", new_topic_path %> |
|
15
|
+
<%= link_to "With visual honeypots", new_topic_path(context: "visual_honeypots") %> |
|
16
|
+
<%= link_to "With timestamp disabled", new_topic_path(context: "timestamp_disabled") %>
|
17
|
+
</p>
|
13
18
|
|
14
|
-
<% flash.each do |key, value| %>
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
<% end %>
|
19
|
+
<% flash.each do |key, value| %>
|
20
|
+
<ul class="errors">
|
21
|
+
<li>
|
22
|
+
<%= "[#{key.upcase}] #{value}" %>
|
23
|
+
</li>
|
24
|
+
</ul>
|
25
|
+
<% end %>
|
21
26
|
|
22
|
-
<%= yield %>
|
23
|
-
|
24
|
-
</body>
|
27
|
+
<%= yield %>
|
28
|
+
</body>
|
25
29
|
</html>
|
@@ -1,8 +1,6 @@
|
|
1
|
-
<h1>New topic</h1>
|
2
|
-
|
3
1
|
<% if @topic.errors.any? %>
|
4
|
-
<div>
|
5
|
-
<
|
2
|
+
<div class="errors">
|
3
|
+
<strong><%= pluralize(@topic.errors.count, "error") %> prohibited this record from being saved:</strong>
|
6
4
|
<ul>
|
7
5
|
<% @topic.errors.full_messages.each do |msg| %>
|
8
6
|
<li><%= msg %></li>
|
@@ -11,13 +9,13 @@
|
|
11
9
|
</div>
|
12
10
|
<% end %>
|
13
11
|
|
14
|
-
<%= form_for(@topic) do |f| %>
|
12
|
+
<%= form_for(@topic, url: { action: params[:context] == 'timestamp_disabled' ? :copy : :create }) do |f| %>
|
15
13
|
<%= hidden_field_tag :context, params[:context] %>
|
16
14
|
|
17
|
-
<% if params[:context]
|
18
|
-
<%= f.invisible_captcha :subtitle %>
|
19
|
-
<% else %>
|
15
|
+
<% if params[:context] && params[:context] == 'visual_honeypots' %>
|
20
16
|
<%= f.invisible_captcha :subtitle, visual_honeypots: true %>
|
17
|
+
<% else %>
|
18
|
+
<%= f.invisible_captcha :subtitle %>
|
21
19
|
<% end %>
|
22
20
|
|
23
21
|
<div class="field">
|
@@ -25,12 +23,17 @@
|
|
25
23
|
<%= f.text_field :title %>
|
26
24
|
</div>
|
27
25
|
|
26
|
+
<div class="field">
|
27
|
+
<%= f.label :author %><br />
|
28
|
+
<%= f.text_field :author %>
|
29
|
+
</div>
|
30
|
+
|
28
31
|
<div class="field">
|
29
32
|
<%= f.label :body %><br />
|
30
|
-
<%= f.text_area :body %>
|
33
|
+
<%= f.text_area :body, rows: 10, cols: 40 %>
|
31
34
|
</div>
|
32
35
|
|
33
36
|
<div class="actions">
|
34
|
-
<%= f.
|
37
|
+
<%= f.button 'Save' %>
|
35
38
|
</div>
|
36
|
-
<% end %>
|
39
|
+
<% end %>
|
@@ -4,6 +4,7 @@ require 'action_controller/railtie'
|
|
4
4
|
require 'action_view/railtie'
|
5
5
|
require 'action_mailer/railtie'
|
6
6
|
require 'active_model/railtie'
|
7
|
+
require 'sprockets/railtie'
|
7
8
|
|
8
9
|
# Require the gems listed in Gemfile, including any gems
|
9
10
|
# you've limited to :test, :development, or :production.
|
@@ -31,6 +31,9 @@ Dummy::Application.configure do
|
|
31
31
|
# yet still be able to expire them through the digest params.
|
32
32
|
# config.assets.digest = true
|
33
33
|
|
34
|
+
# quiet assets
|
35
|
+
config.assets.quiet = true
|
36
|
+
|
34
37
|
# Adds additional error checking when serving assets at runtime.
|
35
38
|
# Checks for improperly declared sprockets dependencies.
|
36
39
|
# Raises helpful error messages.
|
data/spec/dummy/config/routes.rb
CHANGED
@@ -5,10 +5,10 @@ describe InvisibleCaptcha do
|
|
5
5
|
InvisibleCaptcha.init!
|
6
6
|
|
7
7
|
expect(InvisibleCaptcha.sentence_for_humans).to eq('If you are a human, ignore this field')
|
8
|
-
expect(InvisibleCaptcha.error_message).to eq('You are a robot!')
|
9
8
|
expect(InvisibleCaptcha.timestamp_threshold).to eq(4.seconds)
|
10
9
|
expect(InvisibleCaptcha.timestamp_error_message).to eq('Sorry, that was too quick! Please resubmit.')
|
11
|
-
expect(InvisibleCaptcha.honeypots).to
|
10
|
+
expect(InvisibleCaptcha.honeypots).to be_an_instance_of(Array)
|
11
|
+
expect(InvisibleCaptcha.injectable_styles).to eq(false)
|
12
12
|
end
|
13
13
|
|
14
14
|
it 'allow setup via block' do
|
@@ -27,28 +27,23 @@ describe InvisibleCaptcha do
|
|
27
27
|
I18n.backend.store_translations(:en,
|
28
28
|
'invisible_captcha' => {
|
29
29
|
'sentence_for_humans' => "Can't touch this",
|
30
|
-
'error_message' => 'MR ROBOT',
|
31
30
|
'timestamp_error_message' => 'Fast and furious' })
|
32
31
|
|
33
32
|
I18n.backend.store_translations(:fr,
|
34
33
|
'invisible_captcha' => {
|
35
34
|
'sentence_for_humans' => 'Ne touchez pas',
|
36
|
-
'error_message' => 'Mon dieu, un robot!',
|
37
35
|
'timestamp_error_message' => 'Plus doucement SVP' })
|
38
36
|
|
39
37
|
I18n.locale = :en
|
40
38
|
expect(InvisibleCaptcha.sentence_for_humans).to eq("Can't touch this")
|
41
|
-
expect(InvisibleCaptcha.error_message).to eq('MR ROBOT')
|
42
39
|
expect(InvisibleCaptcha.timestamp_error_message).to eq('Fast and furious')
|
43
40
|
|
44
41
|
I18n.locale = :fr
|
45
42
|
expect(InvisibleCaptcha.sentence_for_humans).to eq('Ne touchez pas')
|
46
|
-
expect(InvisibleCaptcha.error_message).to eq('Mon dieu, un robot!')
|
47
43
|
expect(InvisibleCaptcha.timestamp_error_message).to eq('Plus doucement SVP')
|
48
44
|
|
49
45
|
I18n.backend.reload!
|
50
46
|
expect(InvisibleCaptcha.sentence_for_humans).to eq('If you are a human, ignore this field')
|
51
|
-
expect(InvisibleCaptcha.error_message).to eq('You are a robot!')
|
52
47
|
expect(InvisibleCaptcha.timestamp_error_message).to eq('Sorry, that was too quick! Please resubmit.')
|
53
48
|
end
|
54
49
|
end
|
data/spec/view_helpers_spec.rb
CHANGED
@@ -1,81 +1,75 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe InvisibleCaptcha::ViewHelpers, type: :helper do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
input_name = build_text_field_name(honeypot, scope)
|
8
|
-
html_id = generate_html_id(honeypot, scope)
|
9
|
-
visibilty = if options.key?(:visual_honeypots)
|
10
|
-
options[:visual_honeypots]
|
11
|
-
else
|
12
|
-
InvisibleCaptcha.visual_honeypots
|
13
|
-
end
|
14
|
-
style_attributes, input_attributes = if Gem::Version.new(Rails.version) > Gem::Version.new("4.2.0")
|
15
|
-
[
|
16
|
-
'type="text/css" media="screen" scoped="scoped"',
|
17
|
-
"type=\"text\" name=\"#{input_name}\" id=\"#{input_id}\""
|
18
|
-
]
|
19
|
-
else
|
20
|
-
[
|
21
|
-
'media="screen" scoped="scoped" type="text/css"',
|
22
|
-
"id=\"#{input_id}\" name=\"#{input_name}\" type=\"text\""
|
23
|
-
]
|
24
|
-
end
|
4
|
+
before(:each) do
|
5
|
+
allow(Time.zone).to receive(:now).and_return(Time.zone.parse('Feb 19 1986'))
|
6
|
+
allow(InvisibleCaptcha).to receive(:css_strategy).and_return("display:none;")
|
25
7
|
|
26
|
-
|
27
|
-
|
28
|
-
<style #{style_attributes}>#{visibilty ? '' : "##{html_id} { display:none; }"}</style>
|
29
|
-
<label for="#{input_id}">#{InvisibleCaptcha.sentence_for_humans}</label>
|
30
|
-
<input #{input_attributes} />
|
31
|
-
</div>
|
32
|
-
}.gsub(/\s+/, ' ').strip.gsub('> <', '><')
|
33
|
-
end
|
8
|
+
# to test content_for and provide
|
9
|
+
@view_flow = ActionView::OutputFlow.new
|
34
10
|
|
35
|
-
|
36
|
-
allow(Time.zone).to receive(:now).and_return(Time.zone.parse('Feb 19 1986'))
|
37
|
-
InvisibleCaptcha.visual_honeypots = false
|
38
|
-
InvisibleCaptcha.timestamp_enabled = true
|
11
|
+
InvisibleCaptcha.init!
|
39
12
|
end
|
40
13
|
|
41
14
|
it 'with no arguments' do
|
42
15
|
InvisibleCaptcha.honeypots = [:foo_id]
|
43
|
-
expect(invisible_captcha).to
|
16
|
+
expect(invisible_captcha).to match(/name="foo_id"/)
|
44
17
|
end
|
45
18
|
|
46
19
|
it 'with specific honeypot' do
|
47
|
-
expect(invisible_captcha(:subtitle)).to
|
20
|
+
expect(invisible_captcha(:subtitle)).to match(/name="subtitle"/)
|
48
21
|
end
|
49
22
|
|
50
23
|
it 'with specific honeypot and scope' do
|
51
|
-
expect(invisible_captcha(:subtitle, :topic)).to
|
24
|
+
expect(invisible_captcha(:subtitle, :topic)).to match(/name="topic\[subtitle\]"/)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'with custom html options' do
|
28
|
+
expect(invisible_captcha(:subtitle, :topic, { class: 'foo_class' })).to match(/class="foo_class"/)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'generated html + styles' do
|
32
|
+
InvisibleCaptcha.honeypots = [:foo_id]
|
33
|
+
output = invisible_captcha.gsub("\"", "'")
|
34
|
+
regexp = %r{<div class='foo_id_\w*'><style.*>.foo_id_\w* {display:none;}</style><label.*>#{InvisibleCaptcha.sentence_for_humans}.*<input.*name='foo_id'.*tabindex='-1'.*</div>}
|
35
|
+
|
36
|
+
expect(output).to match(regexp)
|
52
37
|
end
|
53
38
|
|
54
39
|
context "honeypot visibilty" do
|
55
40
|
it 'visible from defaults' do
|
56
|
-
InvisibleCaptcha.honeypots = [:foo_id]
|
57
41
|
InvisibleCaptcha.visual_honeypots = true
|
58
42
|
|
59
|
-
expect(invisible_captcha).
|
43
|
+
expect(invisible_captcha).not_to match(/display:none/)
|
60
44
|
end
|
61
45
|
|
62
46
|
it 'visible from given instance (default override)' do
|
63
|
-
|
64
|
-
|
65
|
-
expect(invisible_captcha(visual_honeypots: true)).to eq(helper_output(nil, nil, visual_honeypots: true))
|
47
|
+
expect(invisible_captcha(visual_honeypots: true)).not_to match(/display:none/)
|
66
48
|
end
|
67
49
|
|
68
50
|
it 'invisible from given instance (default override)' do
|
69
|
-
InvisibleCaptcha.honeypots = [:foo_id]
|
70
51
|
InvisibleCaptcha.visual_honeypots = true
|
71
52
|
|
72
|
-
expect(invisible_captcha(visual_honeypots: false)).to
|
53
|
+
expect(invisible_captcha(visual_honeypots: false)).to match(/display:none/)
|
73
54
|
end
|
74
55
|
end
|
75
56
|
|
76
57
|
it 'should set spam timestamp' do
|
77
|
-
InvisibleCaptcha.honeypots = [:foo_id]
|
78
58
|
invisible_captcha
|
79
59
|
expect(session[:invisible_captcha_timestamp]).to eq(Time.zone.now.iso8601)
|
80
60
|
end
|
61
|
+
|
62
|
+
context 'injectable_styles option' do
|
63
|
+
it 'by default, render styles along with the honeypot' do
|
64
|
+
expect(invisible_captcha).to match(/display:none/)
|
65
|
+
expect(helper.content_for(:invisible_captcha_styles)).to be_blank
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'if injectable_styles is set, do not append styles inline' do
|
69
|
+
InvisibleCaptcha.injectable_styles = true
|
70
|
+
|
71
|
+
expect(invisible_captcha).not_to match(/display:none;/)
|
72
|
+
expect(helper.content_for(:invisible_captcha_styles)).to match(/display:none;/)
|
73
|
+
end
|
74
|
+
end
|
81
75
|
end
|
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.
|
4
|
+
version: 0.10.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: 2017-
|
11
|
+
date: 2017-12-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -67,19 +67,19 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '3.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: byebug
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '0'
|
83
83
|
description: Unobtrusive, flexible and simple spam protection for Rails applications
|
84
84
|
using honeypot strategy for better user experience.
|
85
85
|
email:
|
@@ -116,7 +116,6 @@ files:
|
|
116
116
|
- spec/dummy/app/controllers/topics_controller.rb
|
117
117
|
- spec/dummy/app/helpers/application_helper.rb
|
118
118
|
- spec/dummy/app/mailers/.gitkeep
|
119
|
-
- spec/dummy/app/models/.gitkeep
|
120
119
|
- spec/dummy/app/models/topic.rb
|
121
120
|
- spec/dummy/app/views/layouts/application.html.erb
|
122
121
|
- spec/dummy/app/views/topics/new.html.erb
|
@@ -135,7 +134,6 @@ files:
|
|
135
134
|
- spec/dummy/config/initializers/cookies_serializer.rb
|
136
135
|
- spec/dummy/config/initializers/filter_parameter_logging.rb
|
137
136
|
- spec/dummy/config/initializers/inflections.rb
|
138
|
-
- spec/dummy/config/initializers/invisible_captcha.rb
|
139
137
|
- spec/dummy/config/initializers/mime_types.rb
|
140
138
|
- spec/dummy/config/initializers/secret_token.rb
|
141
139
|
- spec/dummy/config/initializers/session_store.rb
|
@@ -172,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
172
170
|
version: '0'
|
173
171
|
requirements: []
|
174
172
|
rubyforge_project:
|
175
|
-
rubygems_version: 2.
|
173
|
+
rubygems_version: 2.6.13
|
176
174
|
signing_key:
|
177
175
|
specification_version: 4
|
178
176
|
summary: Simple honeypot protection for RoR apps
|
@@ -186,7 +184,6 @@ test_files:
|
|
186
184
|
- spec/dummy/app/controllers/topics_controller.rb
|
187
185
|
- spec/dummy/app/helpers/application_helper.rb
|
188
186
|
- spec/dummy/app/mailers/.gitkeep
|
189
|
-
- spec/dummy/app/models/.gitkeep
|
190
187
|
- spec/dummy/app/models/topic.rb
|
191
188
|
- spec/dummy/app/views/layouts/application.html.erb
|
192
189
|
- spec/dummy/app/views/topics/new.html.erb
|
@@ -205,7 +202,6 @@ test_files:
|
|
205
202
|
- spec/dummy/config/initializers/cookies_serializer.rb
|
206
203
|
- spec/dummy/config/initializers/filter_parameter_logging.rb
|
207
204
|
- spec/dummy/config/initializers/inflections.rb
|
208
|
-
- spec/dummy/config/initializers/invisible_captcha.rb
|
209
205
|
- spec/dummy/config/initializers/mime_types.rb
|
210
206
|
- spec/dummy/config/initializers/secret_token.rb
|
211
207
|
- spec/dummy/config/initializers/session_store.rb
|
File without changes
|