invisible_captcha 0.8.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +5 -1
- data/.rspec +2 -0
- data/.travis.yml +13 -2
- data/Appraisals +11 -0
- data/LICENSE +1 -1
- data/README.md +62 -63
- data/gemfiles/rails_3.2.gemfile +7 -0
- data/gemfiles/rails_4.1.gemfile +7 -0
- data/gemfiles/rails_4.2.gemfile +7 -0
- data/invisible_captcha.gemspec +5 -2
- data/lib/invisible_captcha/controller_ext.rb +35 -2
- data/lib/invisible_captcha/railtie.rb +1 -3
- data/lib/invisible_captcha/version.rb +1 -1
- data/lib/invisible_captcha/view_helpers.rb +5 -2
- data/lib/invisible_captcha.rb +38 -5
- data/spec/controllers_spec.rb +84 -11
- data/spec/dummy/app/controllers/topics_controller.rb +12 -1
- data/spec/dummy/app/models/topic.rb +0 -1
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config/application.rb +16 -5
- data/spec/dummy/config/boot.rb +2 -4
- data/spec/dummy/config/environment.rb +3 -3
- data/spec/dummy/config/environments/development.rb +23 -12
- data/spec/dummy/config/environments/production.rb +45 -33
- data/spec/dummy/config/environments/test.rb +19 -11
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +6 -5
- data/spec/dummy/config/initializers/mime_types.rb +0 -1
- data/spec/dummy/config/initializers/session_store.rb +1 -6
- data/spec/dummy/config/initializers/wrap_parameters.rb +7 -2
- data/spec/dummy/config/locales/en.yml +20 -2
- data/spec/dummy/config/routes.rb +6 -2
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/invisible_captcha_spec.rb +36 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/view_helpers_spec.rb +22 -4
- metadata +67 -9
- data/lib/invisible_captcha/validator.rb +0 -15
- data/spec/dummy/script/rails +0 -5
- data/spec/validator_spec.rb +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6667c856fdf4fb2ae0147d0d2d3d0531f8fab862
|
4
|
+
data.tar.gz: 7d2dadae6a9010c3614166343221fc0064fe9bc2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a76b88b892d95a1d72fcf5f2bffb2165c0608554feab107ca5a61e25db4a6b2e4eb3349ecba9b7e0c817d2766c23bb6ebd6723fa20717f8b3518ea38583ebab8
|
7
|
+
data.tar.gz: b15a66cfab3ec429aaafcdcd310e66f178cf17ab1375d58f0f0454b75b00059600295d5b54df404630ba8ceb0d596a84b6f688312bdeb2c04e696d7d211ddf04
|
data/.gitignore
CHANGED
data/.rspec
ADDED
data/.travis.yml
CHANGED
data/Appraisals
ADDED
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -2,11 +2,13 @@
|
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/invisible_captcha.svg)](http://badge.fury.io/rb/invisible_captcha) [![Build Status](https://travis-ci.org/markets/invisible_captcha.svg)](https://travis-ci.org/markets/invisible_captcha)
|
4
4
|
|
5
|
-
Simple and flexible spam protection solution for Rails applications.
|
5
|
+
> Simple and flexible spam protection solution for Rails applications.
|
6
|
+
|
7
|
+
It is based on the `honeypot` strategy to provide a better user experience. It also provides a time-sensitive form submission.
|
6
8
|
|
7
9
|
**Background**
|
8
10
|
|
9
|
-
The strategy is
|
11
|
+
The strategy is about adding an input field into the form that:
|
10
12
|
|
11
13
|
* shouldn't be visible by the real users
|
12
14
|
* should be left empty by the real users
|
@@ -14,6 +16,8 @@ The strategy is based on adding an input field into the form that:
|
|
14
16
|
|
15
17
|
## Installation
|
16
18
|
|
19
|
+
Invisible Captcha is tested against Rails `>= 3.2` and Ruby `>= 1.9.3`.
|
20
|
+
|
17
21
|
Add this line to you Gemfile:
|
18
22
|
|
19
23
|
```
|
@@ -28,15 +32,13 @@ $ gem install invisible_captcha
|
|
28
32
|
|
29
33
|
## Usage
|
30
34
|
|
31
|
-
There are different ways to implement, at Controller level or Model level:
|
32
|
-
|
33
|
-
### Controller style
|
34
|
-
|
35
35
|
View code:
|
36
36
|
|
37
37
|
```erb
|
38
|
-
<%=
|
39
|
-
<%= invisible_captcha %>
|
38
|
+
<%= form_for(@topic) do |f| %>
|
39
|
+
<%= f.invisible_captcha :subtitle %>
|
40
|
+
<!-- or -->
|
41
|
+
<%= invisible_captcha :subtitle, :topic %>
|
40
42
|
<% end %>
|
41
43
|
```
|
42
44
|
|
@@ -44,67 +46,36 @@ Controller code:
|
|
44
46
|
|
45
47
|
```ruby
|
46
48
|
class TopicsController < ApplicationController
|
47
|
-
invisible_captcha only: [:create, :update]
|
49
|
+
invisible_captcha only: [:create, :update], honeypot: :subtitle
|
48
50
|
end
|
49
51
|
```
|
50
52
|
|
51
|
-
This method will act as a `before_filter` that triggers when spam is detected (honeypot field has some value). By default it responds with no content (only headers: `head(200)`). But you are able to define your own callback by passing a method to the `on_spam` option:
|
53
|
+
This method will act as a `before_filter` 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
54
|
|
53
55
|
```ruby
|
54
|
-
|
56
|
+
class TopicsController < ApplicationController
|
57
|
+
invisible_captcha only: [:create, :update], on_spam: :your_spam_callback_method
|
55
58
|
|
56
|
-
private
|
59
|
+
private
|
57
60
|
|
58
|
-
def
|
59
|
-
|
61
|
+
def your_spam_callback_method
|
62
|
+
redirect_to root_path
|
63
|
+
end
|
60
64
|
end
|
61
65
|
```
|
62
66
|
|
63
|
-
|
64
|
-
|
65
|
-
### Controller style (resource oriented):
|
66
|
-
|
67
|
-
In your form:
|
68
|
-
|
69
|
-
```erb
|
70
|
-
<%= form_for(@topic) do |f| %>
|
71
|
-
<%= f.invisible_captcha :subtitle %>
|
72
|
-
<!-- or -->
|
73
|
-
<%= invisible_captcha :subtitle, :topic %>
|
74
|
-
<% end %>
|
75
|
-
```
|
76
|
-
|
77
|
-
In your controller:
|
78
|
-
|
79
|
-
```ruby
|
80
|
-
invisible_captcha only: [:create, :update], honeypot: :subtitle
|
81
|
-
```
|
82
|
-
|
83
|
-
### Model style
|
84
|
-
|
85
|
-
View code:
|
67
|
+
Note that isn't 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:
|
86
68
|
|
87
69
|
```erb
|
88
|
-
<%=
|
89
|
-
<%=
|
70
|
+
<%= form_tag(new_contact_path) do |f| %>
|
71
|
+
<%= invisible_captcha %>
|
90
72
|
<% end %>
|
91
73
|
```
|
92
74
|
|
93
|
-
|
75
|
+
In you controller:
|
94
76
|
|
95
|
-
```ruby
|
96
|
-
class Topic < ActiveRecord::Base
|
97
|
-
attr_accessor :subtitle # define a virtual attribute, the honeypot
|
98
|
-
validates :subtitle, :invisible_captcha => true
|
99
|
-
end
|
100
77
|
```
|
101
|
-
|
102
|
-
If you are using [strong_parameters](https://github.com/rails/strong_parameters) (by default in Rails 4), don't forget to keep the honeypot attribute into the params hash:
|
103
|
-
|
104
|
-
```ruby
|
105
|
-
def topic_params
|
106
|
-
params.require(:topic).permit(:subtitle)
|
107
|
-
end
|
78
|
+
invisible_captcha only: [:new_contact]
|
108
79
|
```
|
109
80
|
|
110
81
|
## Options and customization
|
@@ -115,19 +86,24 @@ This section contains a description of all plugin options and customizations.
|
|
115
86
|
|
116
87
|
You can customize:
|
117
88
|
|
118
|
-
* `sentence_for_humans`: text for real users if input field was visible.
|
119
|
-
* `
|
120
|
-
* `honeypots`: collection of default honeypots, used by the view helper, called with no args, to generate the honeypot field name
|
89
|
+
* `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.
|
121
91
|
* `visual_honeypots`: make honeypots visible, also useful to test/debug your implementation.
|
92
|
+
* `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
|
+
* `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
|
+
* `timestamp_error_message`: flash error message thrown when form submitted quicker than the `timestamp_threshold` value. It uses I18n by default.
|
122
95
|
|
123
96
|
To change these defaults, add the following to an initializer (recommended `config/initializers/invisible_captcha.rb`):
|
124
97
|
|
125
98
|
```ruby
|
126
99
|
InvisibleCaptcha.setup do |config|
|
127
|
-
config.
|
128
|
-
config.error_message = 'You are a robot!'
|
129
|
-
config.honeypots += 'fake_resource_title'
|
100
|
+
config.honeypots << 'another_fake_attribute'
|
130
101
|
config.visual_honeypots = false
|
102
|
+
config.timestamp_threshold = 4
|
103
|
+
config.timestamp_enabled = true
|
104
|
+
# Leave these unset if you want to use I18n (see below)
|
105
|
+
# config.sentence_for_humans = 'If you are a human, ignore this field'
|
106
|
+
# config.timestamp_error_message = 'Sorry, that was too quick! Please resubmit.'
|
131
107
|
end
|
132
108
|
```
|
133
109
|
|
@@ -140,6 +116,8 @@ The `invisible_captcha` method accepts some options:
|
|
140
116
|
* `honeypot`: name of honeypot.
|
141
117
|
* `scope`: name of scope, ie: 'topic[subtitle]' -> 'topic' is the scope.
|
142
118
|
* `on_spam`: custom callback to be called on spam detection.
|
119
|
+
* `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
|
+
* `timestamp_threshold`: custom threshold per controller/action. Overrides the global value for `InvisibleCaptcha.timestamp_threshold`.
|
143
121
|
|
144
122
|
### View helpers options:
|
145
123
|
|
@@ -153,26 +131,47 @@ Using the view/form helper you can override some defaults for the given instance
|
|
153
131
|
<% end %>
|
154
132
|
```
|
155
133
|
|
134
|
+
### I18n
|
135
|
+
|
136
|
+
`invisible_captcha` tries to use I18n when it's available by default. The keys it looks for are the following:
|
137
|
+
|
138
|
+
```yaml
|
139
|
+
en:
|
140
|
+
invisible_captcha:
|
141
|
+
sentence_for_humans: "If you are human, ignore this field"
|
142
|
+
timestamp_error_message: "Sorry, that was too quick! Please resubmit."
|
143
|
+
```
|
144
|
+
|
145
|
+
You can override the english ones in your own i18n config files as well as add new ones for other locales.
|
146
|
+
|
147
|
+
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.
|
148
|
+
|
156
149
|
## Contribute
|
157
150
|
|
158
151
|
Any kind of idea, feedback or bug report are welcome! Open an [issue](https://github.com/markets/invisible_captcha/issues) or send a [pull request](https://github.com/markets/invisible_captcha/pulls).
|
159
152
|
|
160
153
|
## Development
|
161
154
|
|
162
|
-
Clone/fork this repository
|
155
|
+
Clone/fork this repository, start to hack on it and send a pull request.
|
156
|
+
|
157
|
+
Run the test suite:
|
158
|
+
|
159
|
+
```
|
160
|
+
$ bundle exec rspec
|
161
|
+
```
|
163
162
|
|
164
|
-
Run test suite:
|
163
|
+
Run the test suite against all supported versions:
|
165
164
|
|
166
165
|
```
|
167
|
-
$
|
166
|
+
$ bundle exec appraisal rake
|
168
167
|
```
|
169
168
|
|
170
169
|
Start a sample Rails app ([source code](spec/dummy)) with `InvisibleCaptcha` integrated:
|
171
170
|
|
172
171
|
```
|
173
|
-
$ rake web # PORT=4000 (default: 3000)
|
172
|
+
$ bundle exec rake web # PORT=4000 (default: 3000)
|
174
173
|
```
|
175
174
|
|
176
175
|
## License
|
177
176
|
|
178
|
-
Copyright (c) 2012-
|
177
|
+
Copyright (c) 2012-2016 Marc Anguera. Invisible Captcha is released under the [MIT](LICENSE) License.
|
data/invisible_captcha.gemspec
CHANGED
@@ -6,8 +6,8 @@ Gem::Specification.new do |spec|
|
|
6
6
|
spec.version = InvisibleCaptcha::VERSION
|
7
7
|
spec.authors = ["Marc Anguera Insa"]
|
8
8
|
spec.email = ["srmarc.ai@gmail.com"]
|
9
|
-
spec.description =
|
10
|
-
spec.summary =
|
9
|
+
spec.description = "Unobtrusive, flexible and simple spam protection for Rails applications using honeypot strategy for better user experience."
|
10
|
+
spec.summary = "Simple honeypot protection for RoR apps"
|
11
11
|
spec.homepage = "https://github.com/markets/invisible_captcha"
|
12
12
|
spec.license = "MIT"
|
13
13
|
|
@@ -19,5 +19,8 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.add_dependency 'rails'
|
20
20
|
|
21
21
|
spec.add_development_dependency 'rspec-rails', '~> 3.1'
|
22
|
+
spec.add_development_dependency 'appraisal'
|
23
|
+
spec.add_development_dependency 'test-unit', '~> 3.0'
|
24
|
+
spec.add_development_dependency 'mime-types', '< 3.0'
|
22
25
|
end
|
23
26
|
|
@@ -9,11 +9,21 @@ module InvisibleCaptcha
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def detect_spam(options = {})
|
12
|
-
if
|
12
|
+
if invisible_captcha_timestamp?(options)
|
13
|
+
on_timestamp_spam_action(options)
|
14
|
+
elsif invisible_captcha?(options)
|
13
15
|
on_spam_action(options)
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
19
|
+
def on_timestamp_spam_action(options = {})
|
20
|
+
if action = options[:on_timestamp_spam]
|
21
|
+
send(action)
|
22
|
+
else
|
23
|
+
redirect_to :back, flash: { error: InvisibleCaptcha.timestamp_error_message }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
17
27
|
def on_spam_action(options = {})
|
18
28
|
if action = options[:on_spam]
|
19
29
|
send(action)
|
@@ -26,6 +36,29 @@ module InvisibleCaptcha
|
|
26
36
|
head(200)
|
27
37
|
end
|
28
38
|
|
39
|
+
def invisible_captcha_timestamp?(options = {})
|
40
|
+
unless InvisibleCaptcha.timestamp_enabled
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
|
44
|
+
timestamp = session[:invisible_captcha_timestamp]
|
45
|
+
|
46
|
+
# Consider as spam if timestamp not in session, cause that means the form was not fetched at all
|
47
|
+
unless timestamp
|
48
|
+
logger.warn("Potential spam detected for IP #{request.env['REMOTE_ADDR']}. Invisible Captcha timestamp not found in session.")
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
|
52
|
+
time_to_submit = Time.zone.now - DateTime.iso8601(timestamp)
|
53
|
+
|
54
|
+
# Consider as spam if form submitted too quickly
|
55
|
+
if time_to_submit < (options[:timestamp_threshold] || InvisibleCaptcha.timestamp_threshold)
|
56
|
+
logger.warn("Potential spam detected for IP #{request.env['REMOTE_ADDR']}. Invisible Captcha timestamp threshold not reached (took #{time_to_submit.to_i}s).")
|
57
|
+
return true
|
58
|
+
end
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
29
62
|
def invisible_captcha?(options = {})
|
30
63
|
honeypot = options[:honeypot]
|
31
64
|
scope = options[:scope] || controller_name.singularize
|
@@ -45,4 +78,4 @@ module InvisibleCaptcha
|
|
45
78
|
false
|
46
79
|
end
|
47
80
|
end
|
48
|
-
end
|
81
|
+
end
|
@@ -10,8 +10,6 @@ module InvisibleCaptcha
|
|
10
10
|
include InvisibleCaptcha::ViewHelpers
|
11
11
|
ActionView::Helpers::FormBuilder.send :include, InvisibleCaptcha::FormHelpers
|
12
12
|
end
|
13
|
-
|
14
|
-
ActiveModel::Validations::InvisibleCaptchaValidator = InvisibleCaptcha::InvisibleCaptchaValidator
|
15
13
|
end
|
16
14
|
end
|
17
|
-
end
|
15
|
+
end
|
@@ -6,6 +6,9 @@ module InvisibleCaptcha
|
|
6
6
|
# @param scope [Symbol] name of honeypot scope, ie: topic => input name: topic[subtitle]
|
7
7
|
# @return [String] the generated html
|
8
8
|
def invisible_captcha(honeypot = nil, scope = nil, options = {})
|
9
|
+
if InvisibleCaptcha.timestamp_enabled
|
10
|
+
session[:invisible_captcha_timestamp] ||= Time.zone.now.iso8601
|
11
|
+
end
|
9
12
|
build_invisible_captcha(honeypot, scope, options)
|
10
13
|
end
|
11
14
|
|
@@ -29,7 +32,7 @@ module InvisibleCaptcha
|
|
29
32
|
end
|
30
33
|
|
31
34
|
def generate_html_id(honeypot, scope = nil)
|
32
|
-
"#{scope || honeypot}_#{Time.now.to_i}"
|
35
|
+
"#{scope || honeypot}_#{Time.zone.now.to_i}"
|
33
36
|
end
|
34
37
|
|
35
38
|
def visibility_css(container_id, options)
|
@@ -60,4 +63,4 @@ module InvisibleCaptcha
|
|
60
63
|
end
|
61
64
|
end
|
62
65
|
end
|
63
|
-
end
|
66
|
+
end
|
data/lib/invisible_captcha.rb
CHANGED
@@ -2,27 +2,54 @@ require 'invisible_captcha/version'
|
|
2
2
|
require 'invisible_captcha/controller_ext'
|
3
3
|
require 'invisible_captcha/view_helpers'
|
4
4
|
require 'invisible_captcha/form_helpers'
|
5
|
-
require 'invisible_captcha/validator'
|
6
5
|
require 'invisible_captcha/railtie'
|
7
6
|
|
8
7
|
module InvisibleCaptcha
|
9
8
|
class << self
|
10
|
-
|
9
|
+
attr_writer :sentence_for_humans,
|
10
|
+
:timestamp_error_message,
|
11
|
+
:error_message
|
12
|
+
|
13
|
+
attr_accessor :honeypots,
|
14
|
+
:timestamp_threshold,
|
15
|
+
:timestamp_enabled,
|
16
|
+
:visual_honeypots
|
11
17
|
|
12
18
|
def init!
|
13
19
|
# Default sentence for real users if text field was visible
|
14
|
-
self.sentence_for_humans = 'If you are a human, ignore this field'
|
20
|
+
self.sentence_for_humans = -> { I18n.t('invisible_captcha.sentence_for_humans', default: 'If you are a human, ignore this field') }
|
15
21
|
|
16
22
|
# Default error message for validator
|
17
|
-
self.error_message = 'You are a robot!'
|
23
|
+
self.error_message = -> { I18n.t('invisible_captcha.error_message', default: 'You are a robot!') }
|
18
24
|
|
19
25
|
# Default fake fields for controller based workflow
|
20
26
|
self.honeypots = ['foo_id', 'bar_id', 'baz_id']
|
21
27
|
|
28
|
+
# Fastest time (in seconds) to expect a human to submit the form
|
29
|
+
self.timestamp_threshold = 4
|
30
|
+
|
31
|
+
# Timestamp check enabled by default
|
32
|
+
self.timestamp_enabled = true
|
33
|
+
|
34
|
+
# Default error message for validator when form submitted too quickly
|
35
|
+
self.timestamp_error_message = -> { I18n.t('invisible_captcha.timestamp_error_message', default: 'Sorry, that was too quick! Please resubmit.') }
|
36
|
+
|
22
37
|
# Make honeypots visibles
|
23
38
|
self.visual_honeypots = false
|
24
39
|
end
|
25
40
|
|
41
|
+
def sentence_for_humans
|
42
|
+
call_lambda_or_return(@sentence_for_humans)
|
43
|
+
end
|
44
|
+
|
45
|
+
def error_message
|
46
|
+
call_lambda_or_return(@error_message)
|
47
|
+
end
|
48
|
+
|
49
|
+
def timestamp_error_message
|
50
|
+
call_lambda_or_return(@timestamp_error_message)
|
51
|
+
end
|
52
|
+
|
26
53
|
def setup
|
27
54
|
yield(self) if block_given?
|
28
55
|
end
|
@@ -30,7 +57,13 @@ module InvisibleCaptcha
|
|
30
57
|
def get_honeypot
|
31
58
|
honeypots.sample
|
32
59
|
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def call_lambda_or_return(obj)
|
64
|
+
obj.respond_to?(:call) ? obj.call : obj
|
65
|
+
end
|
33
66
|
end
|
34
67
|
end
|
35
68
|
|
36
|
-
InvisibleCaptcha.init!
|
69
|
+
InvisibleCaptcha.init!
|
data/spec/controllers_spec.rb
CHANGED
@@ -3,23 +3,96 @@ require 'spec_helper'
|
|
3
3
|
describe InvisibleCaptcha::ControllerExt, type: :controller do
|
4
4
|
render_views
|
5
5
|
|
6
|
-
before
|
6
|
+
before do
|
7
|
+
@controller = TopicsController.new
|
8
|
+
InvisibleCaptcha.timestamp_threshold = 1
|
9
|
+
InvisibleCaptcha.timestamp_enabled = true
|
10
|
+
end
|
7
11
|
|
8
|
-
|
9
|
-
|
12
|
+
context 'without invisible_captcha_timestamp in session' do
|
13
|
+
it 'fails like if it was submitted too fast' do
|
14
|
+
request.env['HTTP_REFERER'] = 'http://test.host/topics'
|
15
|
+
post :create, topic: { title: 'foo' }
|
10
16
|
|
11
|
-
|
17
|
+
expect(response).to redirect_to :back
|
18
|
+
expect(flash[:error]).to eq(InvisibleCaptcha.timestamp_error_message)
|
19
|
+
end
|
12
20
|
end
|
13
21
|
|
14
|
-
|
15
|
-
|
22
|
+
context 'without invisible_captcha_timestamp in session and timestamp_enabled=false' do
|
23
|
+
it 'does not fail like if it was submitted too fast' do
|
24
|
+
request.env['HTTP_REFERER'] = 'http://test.host/topics'
|
25
|
+
InvisibleCaptcha.timestamp_enabled = false
|
26
|
+
post :create, topic: { title: 'foo' }
|
27
|
+
|
28
|
+
expect(flash[:error]).not_to be_present
|
29
|
+
expect(response.body).to be_present
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'submission timestamp_threshold' do
|
34
|
+
before do
|
35
|
+
session[:invisible_captcha_timestamp] = Time.zone.now.iso8601
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'fails if submission before timestamp_threshold' do
|
39
|
+
request.env['HTTP_REFERER'] = 'http://test.host/topics'
|
40
|
+
post :create, topic: { title: 'foo' }
|
41
|
+
|
42
|
+
expect(response).to redirect_to :back
|
43
|
+
expect(flash[:error]).to eq(InvisibleCaptcha.timestamp_error_message)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'allow custom on_timestamp_spam callback' do
|
47
|
+
put :update, id: 1, topic: { title: 'bar' }
|
48
|
+
|
49
|
+
expect(response).to redirect_to(root_path)
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'successful submissions' do
|
53
|
+
it 'passes if submission on or after timestamp_threshold' do
|
54
|
+
sleep InvisibleCaptcha.timestamp_threshold
|
16
55
|
|
17
|
-
|
56
|
+
post :create, topic: { title: 'foo' }
|
57
|
+
|
58
|
+
expect(flash[:error]).not_to be_present
|
59
|
+
expect(response.body).to be_present
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'allow to set a custom timestamp_threshold per action' do
|
63
|
+
sleep 2 # custom threshold
|
64
|
+
|
65
|
+
post :publish, id: 1
|
66
|
+
|
67
|
+
expect(flash[:error]).not_to be_present
|
68
|
+
expect(response.body).to be_present
|
69
|
+
end
|
70
|
+
end
|
18
71
|
end
|
19
72
|
|
20
|
-
|
21
|
-
|
73
|
+
context 'honeypot attribute' do
|
74
|
+
before do
|
75
|
+
session[:invisible_captcha_timestamp] = Time.zone.now.iso8601
|
76
|
+
# Wait for valid submission
|
77
|
+
sleep InvisibleCaptcha.timestamp_threshold
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'fails with spam' do
|
81
|
+
post :create, topic: { subtitle: 'foo' }
|
82
|
+
|
83
|
+
expect(response.body).to be_blank
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'passes with no spam' do
|
87
|
+
post :create, topic: { title: 'foo' }
|
88
|
+
|
89
|
+
expect(response.body).to be_present
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'allow custom on_spam callback' do
|
93
|
+
put :update, id: 1, topic: { subtitle: 'foo' }
|
22
94
|
|
23
|
-
|
95
|
+
expect(response.body).to redirect_to(new_topic_path)
|
96
|
+
end
|
24
97
|
end
|
25
|
-
end
|
98
|
+
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
class TopicsController < ApplicationController
|
2
2
|
invisible_captcha honeypot: :subtitle, only: :create
|
3
|
-
invisible_captcha honeypot: :subtitle, only: :update,
|
3
|
+
invisible_captcha honeypot: :subtitle, only: :update,
|
4
|
+
on_spam: :custom_callback,
|
5
|
+
on_timestamp_spam: :custom_timestamp_callback
|
6
|
+
invisible_captcha honeypot: :subtitle, only: :publish, timestamp_threshold: 2
|
4
7
|
|
5
8
|
def new
|
6
9
|
@topic = Topic.new
|
@@ -19,9 +22,17 @@ class TopicsController < ApplicationController
|
|
19
22
|
def update
|
20
23
|
end
|
21
24
|
|
25
|
+
def publish
|
26
|
+
redirect_to new_topic_path
|
27
|
+
end
|
28
|
+
|
22
29
|
private
|
23
30
|
|
24
31
|
def custom_callback
|
25
32
|
redirect_to new_topic_path
|
26
33
|
end
|
34
|
+
|
35
|
+
def custom_timestamp_callback
|
36
|
+
redirect_to root_path
|
37
|
+
end
|
27
38
|
end
|
data/spec/dummy/bin/rake
ADDED