invisible_captcha 0.9.3 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|