recaptcha 4.14.0 → 5.2.1
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/CHANGELOG.md +17 -0
- data/README.md +340 -75
- data/lib/recaptcha.rb +68 -32
- data/lib/recaptcha/adapters/controller_methods.rb +87 -0
- data/lib/recaptcha/adapters/view_methods.rb +26 -0
- data/lib/recaptcha/configuration.rb +10 -4
- data/lib/recaptcha/helpers.rb +298 -0
- data/lib/recaptcha/railtie.rb +24 -4
- data/lib/recaptcha/version.rb +1 -1
- data/rails/locales/en.yml +5 -0
- metadata +9 -21
- data/lib/recaptcha/client_helper.rb +0 -157
- data/lib/recaptcha/verify.rb +0 -108
data/lib/recaptcha/railtie.rb
CHANGED
@@ -3,13 +3,33 @@
|
|
3
3
|
module Recaptcha
|
4
4
|
class Railtie < Rails::Railtie
|
5
5
|
ActiveSupport.on_load(:action_view) do
|
6
|
-
|
7
|
-
include Recaptcha::ClientHelper
|
6
|
+
include Recaptcha::Adapters::ViewMethods
|
8
7
|
end
|
9
8
|
|
10
9
|
ActiveSupport.on_load(:action_controller) do
|
11
|
-
|
12
|
-
|
10
|
+
include Recaptcha::Adapters::ControllerMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
initializer 'recaptcha' do |app|
|
14
|
+
Recaptcha::Railtie.instance_eval do
|
15
|
+
pattern = pattern_from app.config.i18n.available_locales
|
16
|
+
|
17
|
+
add("rails/locales/#{pattern}.yml")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
protected
|
23
|
+
|
24
|
+
def add(pattern)
|
25
|
+
files = Dir[File.join(File.dirname(__FILE__), '../..', pattern)]
|
26
|
+
I18n.load_path.concat(files)
|
27
|
+
end
|
28
|
+
|
29
|
+
def pattern_from(args)
|
30
|
+
array = Array(args || [])
|
31
|
+
array.blank? ? '*' : "{#{array.join ','}}"
|
32
|
+
end
|
13
33
|
end
|
14
34
|
end
|
15
35
|
end
|
data/lib/recaptcha/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: recaptcha
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason L Perry
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-10-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: activesupport
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: i18n
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -161,16 +147,19 @@ files:
|
|
161
147
|
- LICENSE
|
162
148
|
- README.md
|
163
149
|
- lib/recaptcha.rb
|
164
|
-
- lib/recaptcha/
|
150
|
+
- lib/recaptcha/adapters/controller_methods.rb
|
151
|
+
- lib/recaptcha/adapters/view_methods.rb
|
165
152
|
- lib/recaptcha/configuration.rb
|
153
|
+
- lib/recaptcha/helpers.rb
|
166
154
|
- lib/recaptcha/rails.rb
|
167
155
|
- lib/recaptcha/railtie.rb
|
168
|
-
- lib/recaptcha/verify.rb
|
169
156
|
- lib/recaptcha/version.rb
|
157
|
+
- rails/locales/en.yml
|
170
158
|
homepage: http://github.com/ambethia/recaptcha
|
171
159
|
licenses:
|
172
160
|
- MIT
|
173
|
-
metadata:
|
161
|
+
metadata:
|
162
|
+
source_code_uri: https://github.com/ambethia/recaptcha
|
174
163
|
post_install_message:
|
175
164
|
rdoc_options: []
|
176
165
|
require_paths:
|
@@ -186,8 +175,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
186
175
|
- !ruby/object:Gem::Version
|
187
176
|
version: '0'
|
188
177
|
requirements: []
|
189
|
-
|
190
|
-
rubygems_version: 2.7.6
|
178
|
+
rubygems_version: 3.0.3
|
191
179
|
signing_key:
|
192
180
|
specification_version: 4
|
193
181
|
summary: Helpers for the reCAPTCHA API
|
@@ -1,157 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Recaptcha
|
4
|
-
module ClientHelper
|
5
|
-
# Your public API can be specified in the +options+ hash or preferably
|
6
|
-
# using the Configuration.
|
7
|
-
def recaptcha_tags(options = {})
|
8
|
-
if options.key?(:stoken)
|
9
|
-
raise(RecaptchaError, "Secure Token is deprecated. Please remove 'stoken' from your calls to recaptcha_tags.")
|
10
|
-
end
|
11
|
-
if options.key?(:ssl)
|
12
|
-
raise(RecaptchaError, "SSL is now always true. Please remove 'ssl' from your calls to recaptcha_tags.")
|
13
|
-
end
|
14
|
-
|
15
|
-
noscript = options.delete(:noscript)
|
16
|
-
|
17
|
-
html, tag_attributes, fallback_uri = Recaptcha::ClientHelper.recaptcha_components(options)
|
18
|
-
html << %(<div #{tag_attributes}></div>\n)
|
19
|
-
|
20
|
-
if noscript != false
|
21
|
-
html << <<-HTML
|
22
|
-
<noscript>
|
23
|
-
<div>
|
24
|
-
<div style="width: 302px; height: 422px; position: relative;">
|
25
|
-
<div style="width: 302px; height: 422px; position: absolute;">
|
26
|
-
<iframe
|
27
|
-
src="#{fallback_uri}"
|
28
|
-
name="ReCAPTCHA"
|
29
|
-
style="width: 302px; height: 422px; border-style: none; border: 0; overflow: hidden;">
|
30
|
-
</iframe>
|
31
|
-
</div>
|
32
|
-
</div>
|
33
|
-
<div style="width: 300px; height: 60px; border-style: none;
|
34
|
-
bottom: 12px; left: 25px; margin: 0px; padding: 0px; right: 25px;
|
35
|
-
background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
|
36
|
-
<textarea id="g-recaptcha-response" name="g-recaptcha-response"
|
37
|
-
class="g-recaptcha-response"
|
38
|
-
style="width: 250px; height: 40px; border: 1px solid #c1c1c1;
|
39
|
-
margin: 10px 25px; padding: 0px; resize: none;">
|
40
|
-
</textarea>
|
41
|
-
</div>
|
42
|
-
</div>
|
43
|
-
</noscript>
|
44
|
-
HTML
|
45
|
-
end
|
46
|
-
|
47
|
-
html.respond_to?(:html_safe) ? html.html_safe : html
|
48
|
-
end
|
49
|
-
|
50
|
-
# Invisible reCAPTCHA implementation
|
51
|
-
def invisible_recaptcha_tags(options = {})
|
52
|
-
options = {callback: 'invisibleRecaptchaSubmit', ui: :button}.merge options
|
53
|
-
text = options.delete(:text)
|
54
|
-
html, tag_attributes = Recaptcha::ClientHelper.recaptcha_components(options)
|
55
|
-
html << recaptcha_default_callback(options) if recaptcha_default_callback_required?(options)
|
56
|
-
case options[:ui]
|
57
|
-
when :button
|
58
|
-
html << %(<button type="submit" #{tag_attributes}>#{text}</button>\n)
|
59
|
-
when :invisible
|
60
|
-
html << %(<div data-size="invisible" #{tag_attributes}></div>\n)
|
61
|
-
when :input
|
62
|
-
html << %(<input type="submit" #{tag_attributes} value="#{text}"/>\n)
|
63
|
-
else
|
64
|
-
raise(RecaptchaError, "ReCAPTCHA ui `#{options[:ui]}` is not valid.")
|
65
|
-
end
|
66
|
-
html.respond_to?(:html_safe) ? html.html_safe : html
|
67
|
-
end
|
68
|
-
|
69
|
-
def self.recaptcha_components(options = {})
|
70
|
-
html = +''
|
71
|
-
attributes = {}
|
72
|
-
fallback_uri = +''
|
73
|
-
|
74
|
-
# Since leftover options get passed directly through as tag
|
75
|
-
# attributes, we must unconditionally delete all our options
|
76
|
-
options = options.dup
|
77
|
-
env = options.delete(:env)
|
78
|
-
class_attribute = options.delete(:class)
|
79
|
-
site_key = options.delete(:site_key)
|
80
|
-
hl = options.delete(:hl)
|
81
|
-
onload = options.delete(:onload)
|
82
|
-
render = options.delete(:render)
|
83
|
-
nonce = options.delete(:nonce)
|
84
|
-
skip_script = (options.delete(:script) == false)
|
85
|
-
ui = options.delete(:ui)
|
86
|
-
|
87
|
-
data_attribute_keys = [:badge, :theme, :type, :callback, :expired_callback, :error_callback, :size]
|
88
|
-
data_attribute_keys << :tabindex unless ui == :button
|
89
|
-
data_attributes = {}
|
90
|
-
data_attribute_keys.each do |data_attribute|
|
91
|
-
value = options.delete(data_attribute)
|
92
|
-
data_attributes["data-#{data_attribute.to_s.tr('_', '-')}"] = value if value
|
93
|
-
end
|
94
|
-
|
95
|
-
unless Recaptcha::Verify.skip?(env)
|
96
|
-
site_key ||= Recaptcha.configuration.site_key!
|
97
|
-
script_url = Recaptcha.configuration.api_server_url
|
98
|
-
query_params = hash_to_query(
|
99
|
-
hl: hl,
|
100
|
-
onload: onload,
|
101
|
-
render: render
|
102
|
-
)
|
103
|
-
script_url += "?#{query_params}" unless query_params.empty?
|
104
|
-
nonce_attr = " nonce='#{nonce}'" if nonce
|
105
|
-
html << %(<script src="#{script_url}" async defer#{nonce_attr}></script>\n) unless skip_script
|
106
|
-
fallback_uri = %(#{script_url.chomp(".js")}/fallback?k=#{site_key})
|
107
|
-
attributes["data-sitekey"] = site_key
|
108
|
-
attributes.merge! data_attributes
|
109
|
-
end
|
110
|
-
|
111
|
-
# Append whatever that's left of options to be attributes on the tag.
|
112
|
-
attributes["class"] = "g-recaptcha #{class_attribute}"
|
113
|
-
tag_attributes = attributes.merge(options).map { |k, v| %(#{k}="#{v}") }.join(" ")
|
114
|
-
|
115
|
-
[html, tag_attributes, fallback_uri]
|
116
|
-
end
|
117
|
-
|
118
|
-
private
|
119
|
-
|
120
|
-
def recaptcha_default_callback(options = {})
|
121
|
-
nonce = options[:nonce]
|
122
|
-
nonce_attr = " nonce='#{nonce}'" if nonce
|
123
|
-
|
124
|
-
<<-HTML
|
125
|
-
<script#{nonce_attr}>
|
126
|
-
var invisibleRecaptchaSubmit = function () {
|
127
|
-
var closestForm = function (ele) {
|
128
|
-
var curEle = ele.parentNode;
|
129
|
-
while (curEle.nodeName !== 'FORM' && curEle.nodeName !== 'BODY'){
|
130
|
-
curEle = curEle.parentNode;
|
131
|
-
}
|
132
|
-
return curEle.nodeName === 'FORM' ? curEle : null
|
133
|
-
};
|
134
|
-
|
135
|
-
var eles = document.getElementsByClassName('g-recaptcha');
|
136
|
-
if (eles.length > 0) {
|
137
|
-
var form = closestForm(eles[0]);
|
138
|
-
if (form) {
|
139
|
-
form.submit();
|
140
|
-
}
|
141
|
-
}
|
142
|
-
};
|
143
|
-
</script>
|
144
|
-
HTML
|
145
|
-
end
|
146
|
-
|
147
|
-
def recaptcha_default_callback_required?(options)
|
148
|
-
options[:callback] == 'invisibleRecaptchaSubmit' &&
|
149
|
-
!Recaptcha::Verify.skip?(options[:env]) &&
|
150
|
-
options[:script] != false
|
151
|
-
end
|
152
|
-
|
153
|
-
private_class_method def self.hash_to_query(hash)
|
154
|
-
hash.delete_if { |_, val| val.nil? || val.empty? }.to_a.map { |pair| pair.join('=') }.join('&')
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
data/lib/recaptcha/verify.rb
DELETED
@@ -1,108 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'json'
|
4
|
-
|
5
|
-
module Recaptcha
|
6
|
-
module Verify
|
7
|
-
G_RESPONSE_LIMIT = 4000
|
8
|
-
# Your private API can be specified in the +options+ hash or preferably
|
9
|
-
# using the Configuration.
|
10
|
-
def verify_recaptcha(options = {})
|
11
|
-
options = {model: options} unless options.is_a? Hash
|
12
|
-
return true if Recaptcha::Verify.skip?(options[:env])
|
13
|
-
|
14
|
-
model = options[:model]
|
15
|
-
attribute = options[:attribute] || :base
|
16
|
-
recaptcha_response = options[:response] || params['g-recaptcha-response'].to_s
|
17
|
-
|
18
|
-
begin
|
19
|
-
verified = if recaptcha_response.empty? || recaptcha_response.length > G_RESPONSE_LIMIT
|
20
|
-
false
|
21
|
-
else
|
22
|
-
recaptcha_verify_via_api_call(request, recaptcha_response, options)
|
23
|
-
end
|
24
|
-
|
25
|
-
if verified
|
26
|
-
flash.delete(:recaptcha_error) if recaptcha_flash_supported? && !model
|
27
|
-
true
|
28
|
-
else
|
29
|
-
recaptcha_error(
|
30
|
-
model,
|
31
|
-
attribute,
|
32
|
-
options[:message],
|
33
|
-
"recaptcha.errors.verification_failed",
|
34
|
-
"reCAPTCHA verification failed, please try again."
|
35
|
-
)
|
36
|
-
false
|
37
|
-
end
|
38
|
-
rescue Timeout::Error
|
39
|
-
if Recaptcha.configuration.handle_timeouts_gracefully
|
40
|
-
recaptcha_error(
|
41
|
-
model,
|
42
|
-
attribute,
|
43
|
-
options[:message],
|
44
|
-
"recaptcha.errors.recaptcha_unreachable",
|
45
|
-
"Oops, we failed to validate your reCAPTCHA response. Please try again."
|
46
|
-
)
|
47
|
-
false
|
48
|
-
else
|
49
|
-
raise RecaptchaError, "Recaptcha unreachable."
|
50
|
-
end
|
51
|
-
rescue StandardError => e
|
52
|
-
raise RecaptchaError, e.message, e.backtrace
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def verify_recaptcha!(options = {})
|
57
|
-
verify_recaptcha(options) || raise(VerifyError)
|
58
|
-
end
|
59
|
-
|
60
|
-
def self.skip?(env)
|
61
|
-
env ||= ENV['RAILS_ENV'] || ENV['RACK_ENV'] || (Rails.env if defined? Rails.env)
|
62
|
-
Recaptcha.configuration.skip_verify_env.include? env
|
63
|
-
end
|
64
|
-
|
65
|
-
private
|
66
|
-
|
67
|
-
def recaptcha_verify_via_api_call(request, recaptcha_response, options)
|
68
|
-
secret_key = options[:secret_key] || Recaptcha.configuration.secret_key!
|
69
|
-
|
70
|
-
verify_hash = {
|
71
|
-
"secret" => secret_key,
|
72
|
-
"response" => recaptcha_response
|
73
|
-
}
|
74
|
-
|
75
|
-
unless options[:skip_remote_ip]
|
76
|
-
remoteip = (request.respond_to?(:remote_ip) && request.remote_ip) || (env && env['REMOTE_ADDR'])
|
77
|
-
verify_hash["remoteip"] = remoteip.to_s
|
78
|
-
end
|
79
|
-
|
80
|
-
reply = JSON.parse(Recaptcha.get(verify_hash, options))
|
81
|
-
reply['success'].to_s == "true" &&
|
82
|
-
recaptcha_hostname_valid?(reply['hostname'], options[:hostname])
|
83
|
-
end
|
84
|
-
|
85
|
-
def recaptcha_hostname_valid?(hostname, validation)
|
86
|
-
validation ||= Recaptcha.configuration.hostname
|
87
|
-
|
88
|
-
case validation
|
89
|
-
when nil, FalseClass then true
|
90
|
-
when String then validation == hostname
|
91
|
-
else validation.call(hostname)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def recaptcha_error(model, attribute, message, key, default)
|
96
|
-
message ||= Recaptcha.i18n(key, default)
|
97
|
-
if model
|
98
|
-
model.errors.add attribute, message
|
99
|
-
else
|
100
|
-
flash[:recaptcha_error] = message if recaptcha_flash_supported?
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def recaptcha_flash_supported?
|
105
|
-
request.respond_to?(:format) && request.format == :html && respond_to?(:flash)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|