invisible_captcha 0.6.5 → 0.7.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 +7 -0
- data/.gitignore +3 -1
- data/.travis.yml +5 -0
- data/README.md +107 -47
- data/Rakefile +17 -1
- data/invisible_captcha.gemspec +2 -2
- data/lib/invisible_captcha.rb +28 -24
- data/lib/invisible_captcha/controller_ext.rb +48 -0
- data/lib/invisible_captcha/form_helpers.rb +3 -7
- data/lib/invisible_captcha/railtie.rb +9 -0
- data/lib/invisible_captcha/validator.rb +3 -7
- data/lib/invisible_captcha/version.rb +1 -1
- data/lib/invisible_captcha/view_helpers.rb +25 -23
- data/spec/controllers_spec.rb +25 -0
- data/spec/dummy/README.md +3 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/topics_controller.rb +27 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/topic.rb +18 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/topics/new.html.erb +17 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +20 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +30 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +34 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/invisible_captcha.rb +6 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +9 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +5 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +5 -0
- data/spec/helpers_spec.rb +43 -0
- data/spec/invisible_captcha_spec.rb +19 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/validator_spec.rb +12 -0
- metadata +100 -24
- data/lib/invisible_captcha/controller_methods.rb +0 -21
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 57c21321c483942e15254c832843c09cbbba7830
|
4
|
+
data.tar.gz: 6c18c874fdd903093e1695a362064fd2d2b274cc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ed8400648117aac3086d26176f2f9d67eb615f737f4078ed5c817bb3604ac8d85b295266c92bcca9ceeba25953ee68601a69a79044b804b3287bfd8c28b1eaf0
|
7
|
+
data.tar.gz: be584800363bf741d078f3a09f931e98544da1af91ce7757b78b2d5e4e8f896ab93583f974a7a471f8da20ba37c0fdb85f48e52b1be163cdeba3716b6acc0ca1
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,106 +1,166 @@
|
|
1
1
|
# Invisible Captcha
|
2
|
-
|
3
|
-
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/invisible_captcha) [](https://travis-ci.org/markets/invisible_captcha)
|
4
|
+
|
5
|
+
Simple and flexible spam protection solution for Rails applications. Based on the `honeypot` strategy to provide a better user experience.
|
6
|
+
|
7
|
+
**Background**
|
8
|
+
|
9
|
+
The strategy is based on adding an input field into the form that:
|
10
|
+
|
11
|
+
* shouldn't be visible by the real users
|
12
|
+
* should be left empty by the real users
|
13
|
+
* will most be filled by spam bots
|
4
14
|
|
5
15
|
## Installation
|
16
|
+
|
6
17
|
Add this line to you Gemfile:
|
7
18
|
|
8
19
|
```
|
9
20
|
gem 'invisible_captcha'
|
10
21
|
```
|
11
22
|
|
12
|
-
Or install gem manually:
|
23
|
+
Or install the gem manually:
|
13
24
|
|
14
25
|
```
|
15
|
-
gem install invisible_captcha
|
26
|
+
$ gem install invisible_captcha
|
16
27
|
```
|
17
28
|
|
18
29
|
## Usage
|
19
|
-
There are different ways to implement:
|
20
30
|
|
21
|
-
|
31
|
+
There are different ways to implement, at Controller level or Model level:
|
32
|
+
|
33
|
+
### Controller style
|
34
|
+
|
22
35
|
View code:
|
23
36
|
|
24
37
|
```erb
|
25
|
-
<%=
|
26
|
-
<%=
|
38
|
+
<%= form_tag(create_topic_path) do |f| %>
|
39
|
+
<%= invisible_captcha %>
|
27
40
|
<% end %>
|
28
41
|
```
|
29
42
|
|
30
|
-
|
43
|
+
Controller code:
|
31
44
|
|
32
45
|
```ruby
|
33
|
-
class
|
34
|
-
|
35
|
-
validates :subtitle, :invisible_captcha => true
|
46
|
+
class TopicsController < ApplicationController
|
47
|
+
invisible_captcha only: [:create, :update]
|
36
48
|
end
|
37
49
|
```
|
38
50
|
|
39
|
-
|
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:
|
52
|
+
|
40
53
|
```ruby
|
41
|
-
|
42
|
-
|
54
|
+
invisible_captcha only: [:create, :update], on_spam: :your_on_spam_callback_method
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def your_on_spam_callback_method
|
59
|
+
redirect_to root_path
|
43
60
|
end
|
44
61
|
```
|
45
62
|
|
46
|
-
|
47
|
-
|
63
|
+
[Check here a complete list of allowed options.](#controller-method-options)
|
64
|
+
|
65
|
+
### Controller style (resource oriented):
|
66
|
+
|
67
|
+
In your form:
|
48
68
|
|
49
69
|
```erb
|
50
70
|
<%= form_for(@topic) do |f| %>
|
51
|
-
<%= invisible_captcha %>
|
71
|
+
<%= f.invisible_captcha :subtitle %>
|
72
|
+
<!-- or -->
|
73
|
+
<%= invisible_captcha :subtitle, :topic %>
|
52
74
|
<% end %>
|
53
75
|
```
|
54
76
|
|
55
|
-
|
77
|
+
In your controller:
|
56
78
|
|
57
79
|
```ruby
|
58
|
-
|
59
|
-
before_filter :check_invisible_captcha # :only => [:create, :update]
|
60
|
-
# your controller code
|
61
|
-
end
|
80
|
+
invisible_captcha only: [:create, :update], honeypot: :subtitle
|
62
81
|
```
|
63
82
|
|
64
|
-
|
65
|
-
|
66
|
-
```ruby
|
67
|
-
if invisible_captcha?
|
68
|
-
# spam present
|
69
|
-
else
|
70
|
-
# no spam
|
71
|
-
end
|
72
|
-
```
|
83
|
+
### Model style
|
73
84
|
|
74
|
-
|
85
|
+
View code:
|
75
86
|
|
76
|
-
In your form:
|
77
87
|
```erb
|
78
88
|
<%= form_for(@topic) do |f| %>
|
79
89
|
<%= f.invisible_captcha :subtitle %>
|
80
90
|
<% end %>
|
81
91
|
```
|
82
92
|
|
83
|
-
|
93
|
+
Model code:
|
94
|
+
|
84
95
|
```ruby
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
else
|
89
|
-
# regular workflow
|
90
|
-
end
|
96
|
+
class Topic < ActiveRecord::Base
|
97
|
+
attr_accessor :subtitle # define a virtual attribute, the honeypot
|
98
|
+
validates :subtitle, :invisible_captcha => true
|
91
99
|
end
|
92
100
|
```
|
93
101
|
|
94
|
-
|
95
|
-
|
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
|
108
|
+
```
|
109
|
+
|
110
|
+
## Options and customization
|
111
|
+
|
112
|
+
This section contains the option list of `invisible_captcha` method (controllers side) and the plugin setup options (initializer).
|
113
|
+
|
114
|
+
### Controller method options:
|
115
|
+
|
116
|
+
The `invisible_captcha` method accepts some options:
|
117
|
+
|
118
|
+
* `only`: apply to given controller actions.
|
119
|
+
* `except`: exclude to given controller actions.
|
120
|
+
* `honeypot`: name of honeypot.
|
121
|
+
* `scope`: name of scope, ie: 'topic[subtitle]' -> 'topic' is the scope.
|
122
|
+
* `on_spam`: custom callback to be called on spam detection.
|
123
|
+
|
124
|
+
### Plugin options:
|
125
|
+
|
126
|
+
You also can customize some plugin options:
|
127
|
+
|
128
|
+
* `sentence_for_humans`: text for real users if input field was visible.
|
129
|
+
* `error_message`: error message thrown by model validation (only model implementation).
|
130
|
+
* `honeypots`: collection of default honeypots, used by the view helper, called with no args, to generate the honeypot field name
|
131
|
+
* `visual_honeypots`: make honeypots visible, useful to test/debug your implementation.
|
132
|
+
|
133
|
+
To change these defaults, add the following to an initializer (recommended `config/initializers/invisible_captcha.rb`):
|
96
134
|
|
97
135
|
```ruby
|
98
|
-
InvisibleCaptcha.setup do |
|
99
|
-
|
100
|
-
|
101
|
-
|
136
|
+
InvisibleCaptcha.setup do |config|
|
137
|
+
config.sentence_for_humans = 'If you are a human, ignore this field'
|
138
|
+
config.error_message = 'You are a robot!'
|
139
|
+
config.honeypots += 'fake_resource_title'
|
140
|
+
config.visual_honeypots = false
|
102
141
|
end
|
103
142
|
```
|
104
143
|
|
144
|
+
## Contribute
|
145
|
+
|
146
|
+
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).
|
147
|
+
|
148
|
+
## Development
|
149
|
+
|
150
|
+
Clone/fork this repository and start to hack.
|
151
|
+
|
152
|
+
Run test suite:
|
153
|
+
|
154
|
+
```
|
155
|
+
$ rspec
|
156
|
+
```
|
157
|
+
|
158
|
+
Start a sample Rails app ([source code](spec/dummy)) with `InvisibleCaptcha` integrated:
|
159
|
+
|
160
|
+
```
|
161
|
+
$ rake web # PORT=4000 (default: 3000)
|
162
|
+
```
|
163
|
+
|
105
164
|
## License
|
165
|
+
|
106
166
|
Copyright (c) 2012-2014 Marc Anguera. Invisible Captcha is released under the [MIT](LICENSE) License.
|
data/Rakefile
CHANGED
@@ -1 +1,17 @@
|
|
1
|
-
require "bundler/gem_tasks"
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
desc 'Start development Rails app'
|
9
|
+
task :web do
|
10
|
+
app_path = 'spec/dummy'
|
11
|
+
port = ENV['PORT'] || 3000
|
12
|
+
|
13
|
+
puts "Starting application in http://localhost:#{port} ... \n"
|
14
|
+
|
15
|
+
Dir.chdir(app_path)
|
16
|
+
`rails s -p #{port}`
|
17
|
+
end
|
data/invisible_captcha.gemspec
CHANGED
@@ -16,8 +16,8 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
17
|
spec.require_paths = ["lib"]
|
18
18
|
|
19
|
-
spec.add_dependency
|
19
|
+
spec.add_dependency 'rails'
|
20
20
|
|
21
|
-
spec.add_development_dependency
|
21
|
+
spec.add_development_dependency 'rspec-rails', '~> 3.1'
|
22
22
|
end
|
23
23
|
|
data/lib/invisible_captcha.rb
CHANGED
@@ -1,32 +1,36 @@
|
|
1
1
|
require 'invisible_captcha/version'
|
2
|
-
require 'invisible_captcha/
|
2
|
+
require 'invisible_captcha/controller_ext'
|
3
3
|
require 'invisible_captcha/view_helpers'
|
4
4
|
require 'invisible_captcha/form_helpers'
|
5
5
|
require 'invisible_captcha/validator'
|
6
|
+
require 'invisible_captcha/railtie'
|
6
7
|
|
7
8
|
module InvisibleCaptcha
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
# InvisibleCaptcha.setup do |ic|
|
21
|
-
# ic.sentence_for_humans = 'Another sentence'
|
22
|
-
# ic.error_message = 'Another error message'
|
23
|
-
# ic.fake_fields << 'another_fake_field'
|
24
|
-
# end
|
25
|
-
def self.setup
|
26
|
-
yield(self)
|
27
|
-
end
|
9
|
+
class << self
|
10
|
+
attr_accessor :sentence_for_humans, :error_message, :honeypots, :visual_honeypots
|
11
|
+
|
12
|
+
def init!
|
13
|
+
# Default sentence for real users if text field was visible
|
14
|
+
self.sentence_for_humans = 'If you are a human, ignore this field'
|
15
|
+
|
16
|
+
# Default error message for validator
|
17
|
+
self.error_message = 'You are a robot!'
|
18
|
+
|
19
|
+
# Default fake fields for controller based workflow
|
20
|
+
self.honeypots = ['foo_id', 'bar_id', 'baz_id']
|
28
21
|
|
29
|
-
|
30
|
-
|
22
|
+
# Make honeypots visibles
|
23
|
+
self.visual_honeypots = false
|
24
|
+
end
|
25
|
+
|
26
|
+
def setup
|
27
|
+
yield(self) if block_given?
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_honeypot
|
31
|
+
honeypots.sample
|
32
|
+
end
|
31
33
|
end
|
32
|
-
end
|
34
|
+
end
|
35
|
+
|
36
|
+
InvisibleCaptcha.init!
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module InvisibleCaptcha
|
2
|
+
module ControllerExt
|
3
|
+
module ClassMethods
|
4
|
+
def invisible_captcha(options = {})
|
5
|
+
before_filter(options) do
|
6
|
+
detect_spam(options)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def detect_spam(options = {})
|
12
|
+
if invisible_captcha?(options)
|
13
|
+
on_spam_action(options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def on_spam_action(options = {})
|
18
|
+
if action = options[:on_spam]
|
19
|
+
send(action)
|
20
|
+
else
|
21
|
+
default_on_spam
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def default_on_spam
|
26
|
+
head(200)
|
27
|
+
end
|
28
|
+
|
29
|
+
def invisible_captcha?(options = {})
|
30
|
+
honeypot = options[:honeypot]
|
31
|
+
scope = options[:scope] || controller_name.singularize
|
32
|
+
|
33
|
+
if honeypot
|
34
|
+
# If honeypot is presented, search for:
|
35
|
+
# - honeypot: params[:subtitle]
|
36
|
+
# - honeypot with scope: params[:topic][:subtitle]
|
37
|
+
if params[honeypot].present? || (params[scope] && params[scope][honeypot].present?)
|
38
|
+
return true
|
39
|
+
end
|
40
|
+
else
|
41
|
+
InvisibleCaptcha.honeypots.each do |field|
|
42
|
+
return true if params[field].present?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -1,11 +1,7 @@
|
|
1
1
|
module InvisibleCaptcha
|
2
2
|
module FormHelpers
|
3
|
-
|
4
|
-
|
5
|
-
@template.invisible_captcha(self.object_name, method)
|
3
|
+
def invisible_captcha(honeypot)
|
4
|
+
@template.invisible_captcha(honeypot, self.object_name)
|
6
5
|
end
|
7
|
-
|
8
6
|
end
|
9
|
-
end
|
10
|
-
|
11
|
-
ActionView::Helpers::FormBuilder.send :include, InvisibleCaptcha::FormHelpers
|
7
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module InvisibleCaptcha
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
ActionController::Base.send :include, InvisibleCaptcha::ControllerExt
|
4
|
+
ActionController::Base.send :extend, InvisibleCaptcha::ControllerExt::ClassMethods
|
5
|
+
ActionView::Base.send :include, InvisibleCaptcha::ViewHelpers
|
6
|
+
ActionView::Helpers::FormBuilder.send :include, InvisibleCaptcha::FormHelpers
|
7
|
+
ActiveModel::Validations::InvisibleCaptchaValidator = InvisibleCaptcha::InvisibleCaptchaValidator
|
8
|
+
end
|
9
|
+
end
|
@@ -2,7 +2,6 @@ require 'active_model/validator'
|
|
2
2
|
|
3
3
|
module InvisibleCaptcha
|
4
4
|
class InvisibleCaptchaValidator < ActiveModel::EachValidator
|
5
|
-
|
6
5
|
def validate_each(record, attribute, value)
|
7
6
|
if invisible_captcha?(record, attribute)
|
8
7
|
record.errors.clear
|
@@ -12,11 +11,8 @@ module InvisibleCaptcha
|
|
12
11
|
|
13
12
|
private
|
14
13
|
|
15
|
-
def invisible_captcha?(object,
|
16
|
-
object.send(
|
14
|
+
def invisible_captcha?(object, honeypot)
|
15
|
+
object.send(honeypot).present?
|
17
16
|
end
|
18
|
-
|
19
17
|
end
|
20
|
-
end
|
21
|
-
|
22
|
-
ActiveModel::Validations::InvisibleCaptchaValidator = InvisibleCaptcha::InvisibleCaptchaValidator
|
18
|
+
end
|
@@ -1,50 +1,52 @@
|
|
1
1
|
module InvisibleCaptcha
|
2
2
|
module ViewHelpers
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
# Builds the honeypot html
|
4
|
+
#
|
5
|
+
# @param honeypot [Symbol] name of honeypot, ie: subtitle => input name: subtitle
|
6
|
+
# @param scope [Symbol] name of honeypot scope, ie: topic => input name: topic[subtitle]
|
7
|
+
# @return [String] the generated html
|
8
|
+
def invisible_captcha(honeypot = nil, scope = nil)
|
9
|
+
build_invisible_captcha(honeypot, scope)
|
6
10
|
end
|
7
11
|
|
8
12
|
private
|
9
13
|
|
10
|
-
def build_invisible_captcha(
|
11
|
-
|
12
|
-
label
|
13
|
-
html_id
|
14
|
+
def build_invisible_captcha(honeypot = nil, scope = nil)
|
15
|
+
honeypot = honeypot ? honeypot.to_s : InvisibleCaptcha.get_honeypot
|
16
|
+
label = InvisibleCaptcha.sentence_for_humans
|
17
|
+
html_id = generate_html_id(honeypot, scope)
|
14
18
|
|
15
19
|
content_tag(:div, :id => html_id) do
|
16
20
|
insert_inline_css(html_id) +
|
17
|
-
label_tag(build_label_name(
|
18
|
-
text_field_tag(build_text_field_name(
|
21
|
+
label_tag(build_label_name(honeypot, scope), label) +
|
22
|
+
text_field_tag(build_text_field_name(honeypot, scope))
|
19
23
|
end.html_safe
|
20
24
|
end
|
21
25
|
|
22
|
-
def generate_html_id(
|
23
|
-
"#{
|
26
|
+
def generate_html_id(honeypot, scope = nil)
|
27
|
+
"#{scope || honeypot}_#{Time.now.to_i}"
|
24
28
|
end
|
25
29
|
|
26
30
|
def insert_inline_css(container_id)
|
27
31
|
content_tag(:style, :type => 'text/css', :media => 'screen', :scoped => 'scoped') do
|
28
|
-
"##{container_id} { display:none; }"
|
32
|
+
"##{container_id} { display:none; }" unless InvisibleCaptcha.visual_honeypots
|
29
33
|
end
|
30
34
|
end
|
31
35
|
|
32
|
-
def build_label_name(
|
33
|
-
if
|
34
|
-
"#{
|
36
|
+
def build_label_name(honeypot, scope = nil)
|
37
|
+
if scope.present?
|
38
|
+
"#{scope}_#{honeypot}"
|
35
39
|
else
|
36
|
-
|
40
|
+
honeypot
|
37
41
|
end
|
38
42
|
end
|
39
43
|
|
40
|
-
def build_text_field_name(
|
41
|
-
if
|
42
|
-
"#{
|
44
|
+
def build_text_field_name(honeypot, scope = nil)
|
45
|
+
if scope.present?
|
46
|
+
"#{scope}[#{honeypot}]"
|
43
47
|
else
|
44
|
-
|
48
|
+
honeypot
|
45
49
|
end
|
46
50
|
end
|
47
51
|
end
|
48
|
-
end
|
49
|
-
|
50
|
-
ActionView::Base.send :include, InvisibleCaptcha::ViewHelpers
|
52
|
+
end
|