hcaptcha 7.0.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 +7 -0
- data/CHANGELOG.md +1 -0
- data/LICENSE +19 -0
- data/README.md +160 -0
- data/lib/hcaptcha.rb +117 -0
- data/lib/hcaptcha/adapters/controller_methods.rb +87 -0
- data/lib/hcaptcha/adapters/view_methods.rb +11 -0
- data/lib/hcaptcha/configuration.rb +68 -0
- data/lib/hcaptcha/helpers.rb +93 -0
- data/lib/hcaptcha/rails.rb +4 -0
- data/lib/hcaptcha/railtie.rb +35 -0
- data/lib/hcaptcha/version.rb +5 -0
- data/rails/locales/en.yml +5 -0
- metadata +182 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5a8d13c5892c269bd846a2be83ff471553e73cabe9e029bd0665835f38ab0750
|
4
|
+
data.tar.gz: 61eb765060b5f609a35e058f6841144c829b52ebf3019d55929f659b3adf8e7d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4dd587e34892cdf438cc18039659760682985c69d50fee1eaadc8becdf660a79b1d4297ad46699e0a6e81bd7bf2e7dc80cb7d4bd45263250801e5585453848f5
|
7
|
+
data.tar.gz: 1902d04442608418ddfac1e3bb1ad4acef2763691482c1b9a0a632adcdb254d5392b03f5e3deb389f74ddd797e967edfb5b0c83597585279e04c004592c86beb
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
## Next
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2019 First Movers Advantage
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
# hCaptcha
|
2
|
+
[](https://badge.fury.io/rb/hcaptcha)
|
3
|
+
|
4
|
+
Disclaimer: This gem is forked from the [recaptcha gem](https://github.com/ambethia/recaptcha). All ideas, including the documentation and demo Rails and Sinatra integrations come from [recaptcha gem](https://github.com/ambethia/recaptcha) but are adoped for hCaptcha.
|
5
|
+
|
6
|
+
Author: Tyler VanNurden & Jason L Perry (http://ambethia.com)<br/>
|
7
|
+
License: [MIT](http://creativecommons.org/licenses/MIT/)<br/>
|
8
|
+
Info: https://github.com/firstmoversadvantage/hcaptcha<br/>
|
9
|
+
Bugs: https://github.com/firstmoversadvantage/hcaptcha/issues<br/>
|
10
|
+
|
11
|
+
This gem provides helper methods for the [hCaptcha API](https://hcaptcha.com). In your
|
12
|
+
views you can use the `hcaptcha_tags` method to embed the needed javascript, and you can validate
|
13
|
+
in your controllers with `verify_hcaptcha` or `verify_hcaptcha!`.
|
14
|
+
|
15
|
+
## Obtaining a key and setup
|
16
|
+
|
17
|
+
Go to the [hCaptcha](https://hcaptcha.com/webmaster/signup) signup page to obtain API keys. **You'll also need to set a hostname that your application will run from, even for local development. hCaptcha will not work if your application is being served from `localhost` or `127.0.0.1`. You will need to add a hosts entry for local development.** See the [hCaptcha docs](https://hcaptcha.com/docs) for how to do this.
|
18
|
+
|
19
|
+
The hostname you set it to must be a real hostname, since hCaptcha validates it when you create it in the portal. For example, `example.fmadata.com` does not have a DNS record, but `mydomain.com` does. The DNS record doesn't need to point to your application though, it just has to exist - that's why we added the record into the local hosts file.
|
20
|
+
|
21
|
+
## Rails Installation
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem "hcaptcha"
|
25
|
+
```
|
26
|
+
|
27
|
+
You can keep keys out of the code base with environment variables or with Rails [secrets](https://api.rubyonrails.org/classes/Rails/Application.html#method-i-secrets).<br/>
|
28
|
+
|
29
|
+
In development, you can use the [dotenv](https://github.com/bkeepers/dotenv) gem. (Make sure to add it above `gem 'hcaptcha'`.)
|
30
|
+
|
31
|
+
See [Alternative API key setup](#alternative-api-key-setup) for more ways to configure or override
|
32
|
+
keys. See also the
|
33
|
+
[Configuration](https://www.rubydoc.info/github/ambethia/recaptcha/master/Recaptcha/Configuration)
|
34
|
+
documentation.
|
35
|
+
|
36
|
+
```shell
|
37
|
+
export HCAPTCHA_SITE_KEY='6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
|
38
|
+
export HCAPTCHA_SECRET_KEY='6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
|
39
|
+
```
|
40
|
+
|
41
|
+
Add `hcaptcha_tags` to the forms you want to protect:
|
42
|
+
|
43
|
+
```erb
|
44
|
+
<%= form_for @foo do |f| %>
|
45
|
+
# …
|
46
|
+
<%= hcaptcha_tags %>
|
47
|
+
# …
|
48
|
+
<% end %>
|
49
|
+
```
|
50
|
+
|
51
|
+
Then, add `verify_hcaptcha` logic to each form action that you've protected:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
# app/controllers/users_controller.rb
|
55
|
+
@user = User.new(params[:user].permit(:name))
|
56
|
+
if verify_hcaptcha(model: @user) && @user.save
|
57
|
+
redirect_to @user
|
58
|
+
else
|
59
|
+
render 'new'
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
## Sinatra / Rack / Ruby installation
|
64
|
+
|
65
|
+
See [sinatra demo](/demo/sinatra) for details.
|
66
|
+
|
67
|
+
- add `gem 'hcaptcha'` to `Gemfile`
|
68
|
+
- set env variables
|
69
|
+
- `include Hcaptcha::Adapters::ViewMethods` where you need `recaptcha_tags`
|
70
|
+
- `include Hcaptcha::Adapters::ControllerMethods` where you need `verify_recaptcha`
|
71
|
+
|
72
|
+
|
73
|
+
## hCaptcha API and Usage
|
74
|
+
|
75
|
+
### `recaptcha_tags`
|
76
|
+
|
77
|
+
Use in your views to render the JavaScript widget.
|
78
|
+
|
79
|
+
### `verify_recaptcha`
|
80
|
+
|
81
|
+
This method returns `true` or `false` after processing the response token from the hCaptcha widget.
|
82
|
+
This is usually called from your controller, as seen [above](#rails-installation).
|
83
|
+
|
84
|
+
Passing in the ActiveRecord object via `model: object` is optional. If you pass a `model`—and the
|
85
|
+
captcha fails to verify—an error will be added to the object for you to use (available as
|
86
|
+
`object.errors`).
|
87
|
+
|
88
|
+
Why isn't this a model validation? Because that violates MVC. You can use it like this, or how ever
|
89
|
+
you like.
|
90
|
+
|
91
|
+
Some of the options available:
|
92
|
+
|
93
|
+
| Option | Description |
|
94
|
+
|----------------|-------------|
|
95
|
+
| `:model` | Model to set errors.
|
96
|
+
| `:attribute` | Model attribute to receive errors. (default: `:base`)
|
97
|
+
| `:message` | Custom error message.
|
98
|
+
| `:secret_key` | Override the secret API key from the configuration.
|
99
|
+
| `:timeout` | The number of seconds to wait for hCaptcha servers before give up. (default: `3`)
|
100
|
+
| `:response` | Custom response parameter. (default: `params['g-recaptcha-response']`)
|
101
|
+
| `:hostname` | Expected hostname or a callable that validates the hostname, see [domain validation](https://developers.google.com/recaptcha/docs/domain_validation) and [hostname](https://developers.google.com/recaptcha/docs/verify#api-response) docs. (default: `nil`, but can be changed by setting `config.hostname`)
|
102
|
+
| `:env` | Current environment. The request to verify will be skipped if the environment is specified in configuration under `skip_verify_env`
|
103
|
+
|
104
|
+
## I18n support
|
105
|
+
|
106
|
+
hCaptcha supports the I18n gem (it comes with English translations)
|
107
|
+
To override or add new languages, add to `config/locales/*.yml`
|
108
|
+
|
109
|
+
```yaml
|
110
|
+
# config/locales/en.yml
|
111
|
+
en:
|
112
|
+
recaptcha:
|
113
|
+
errors:
|
114
|
+
verification_failed: 'hCaptcha was incorrect, please try again.'
|
115
|
+
recaptcha_unreachable: 'hCaptcha verification server error, please try again.'
|
116
|
+
```
|
117
|
+
|
118
|
+
## Testing
|
119
|
+
|
120
|
+
By default, hCaptcha is skipped in "test" and "cucumber" env. To enable it during test:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
Recaptcha.configuration.skip_verify_env.delete("test")
|
124
|
+
```
|
125
|
+
|
126
|
+
## Alternative API key setup
|
127
|
+
|
128
|
+
### Recaptcha.configure
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
# config/initializers/recaptcha.rb
|
132
|
+
Recaptcha.configure do |config|
|
133
|
+
config.site_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
|
134
|
+
config.secret_key = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
|
135
|
+
# Uncomment the following line if you are using a proxy server:
|
136
|
+
# config.proxy = 'http://myproxy.com.au:8080'
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
140
|
+
### Recaptcha.with_configuration
|
141
|
+
|
142
|
+
For temporary overwrites (not thread safe).
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
Recaptcha.with_configuration(site_key: '12345') do
|
146
|
+
# Do stuff with the overwritten site_key.
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
### Per call
|
151
|
+
|
152
|
+
Pass in keys as options at runtime, for code base with multiple hCaptcha setups:
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
recaptcha_tags site_key: '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
|
156
|
+
|
157
|
+
# and
|
158
|
+
|
159
|
+
verify_recaptcha secret_key: '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
|
160
|
+
```
|
data/lib/hcaptcha.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'net/http'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
require 'hcaptcha/configuration'
|
8
|
+
require 'hcaptcha/helpers'
|
9
|
+
require 'hcaptcha/adapters/controller_methods'
|
10
|
+
require 'hcaptcha/adapters/view_methods'
|
11
|
+
if defined?(Rails)
|
12
|
+
require 'hcaptcha/railtie'
|
13
|
+
end
|
14
|
+
|
15
|
+
module Hcaptcha
|
16
|
+
DEFAULT_TIMEOUT = 3
|
17
|
+
RESPONSE_LIMIT = 32767
|
18
|
+
|
19
|
+
class HcaptchaError < StandardError
|
20
|
+
end
|
21
|
+
|
22
|
+
class VerifyError < HcaptchaError
|
23
|
+
end
|
24
|
+
|
25
|
+
# Gives access to the current Configuration.
|
26
|
+
def self.configuration
|
27
|
+
@configuration ||= Configuration.new
|
28
|
+
end
|
29
|
+
|
30
|
+
# Allows easy setting of multiple configuration options. See Configuration
|
31
|
+
# for all available options.
|
32
|
+
#--
|
33
|
+
# The temp assignment is only used to get a nicer rdoc. Feel free to remove
|
34
|
+
# this hack.
|
35
|
+
#++
|
36
|
+
def self.configure
|
37
|
+
config = configuration
|
38
|
+
yield(config)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.with_configuration(config)
|
42
|
+
original_config = {}
|
43
|
+
|
44
|
+
config.each do |key, value|
|
45
|
+
original_config[key] = configuration.send(key)
|
46
|
+
configuration.send("#{key}=", value)
|
47
|
+
end
|
48
|
+
|
49
|
+
yield if block_given?
|
50
|
+
ensure
|
51
|
+
original_config.each { |key, value| configuration.send("#{key}=", value) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.skip_env?(env)
|
55
|
+
configuration.skip_verify_env.include?(env || configuration.default_env)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.invalid_response?(resp)
|
59
|
+
resp.empty? || resp.length > RESPONSE_LIMIT
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.verify_via_api_call(response, options)
|
63
|
+
secret_key = options.fetch(:secret_key) { configuration.secret_key! }
|
64
|
+
verify_hash = { 'secret' => secret_key, 'response' => response }
|
65
|
+
verify_hash['remoteip'] = options[:remote_ip] if options.key?(:remote_ip)
|
66
|
+
|
67
|
+
reply = api_verification(verify_hash, timeout: options[:timeout])
|
68
|
+
reply['success'].to_s == 'true' &&
|
69
|
+
hostname_valid?(reply['hostname'], options[:hostname]) &&
|
70
|
+
action_valid?(reply['action'], options[:action]) &&
|
71
|
+
score_above_threshold?(reply['score'], options[:minimum_score])
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.hostname_valid?(hostname, validation)
|
75
|
+
validation ||= configuration.hostname
|
76
|
+
|
77
|
+
case validation
|
78
|
+
when nil, FalseClass then true
|
79
|
+
when String then validation == hostname
|
80
|
+
else validation.call(hostname)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.action_valid?(action, expected_action)
|
85
|
+
case expected_action
|
86
|
+
when nil, FalseClass then true
|
87
|
+
else action == expected_action
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns true iff score is greater or equal to (>=) minimum_score, or if no minimum_score was specified
|
92
|
+
def self.score_above_threshold?(score, minimum_score)
|
93
|
+
return true if minimum_score.nil?
|
94
|
+
return false if score.nil?
|
95
|
+
|
96
|
+
case minimum_score
|
97
|
+
when nil, FalseClass then true
|
98
|
+
else score >= minimum_score
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.api_verification(verify_hash, timeout: DEFAULT_TIMEOUT)
|
103
|
+
http = if configuration.proxy
|
104
|
+
proxy_server = URI.parse(configuration.proxy)
|
105
|
+
Net::HTTP::Proxy(proxy_server.host, proxy_server.port, proxy_server.user, proxy_server.password)
|
106
|
+
else
|
107
|
+
Net::HTTP
|
108
|
+
end
|
109
|
+
query = URI.encode_www_form(verify_hash)
|
110
|
+
uri = URI.parse(configuration.verify_url + '?' + query)
|
111
|
+
http_instance = http.new(uri.host, uri.port)
|
112
|
+
http_instance.read_timeout = http_instance.open_timeout = timeout
|
113
|
+
http_instance.use_ssl = true if uri.port == 443
|
114
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
115
|
+
JSON.parse(http_instance.request(request).body)
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hcaptcha
|
4
|
+
module Adapters
|
5
|
+
module ControllerMethods
|
6
|
+
private
|
7
|
+
|
8
|
+
# Your private API can be specified in the +options+ hash or preferably
|
9
|
+
# using the Configuration.
|
10
|
+
def verify_hcaptcha(options = {})
|
11
|
+
options = { model: options } unless options.is_a? Hash
|
12
|
+
return true if Hcaptcha.skip_env?(options[:env])
|
13
|
+
|
14
|
+
model = options[:model]
|
15
|
+
attribute = options.fetch(:attribute, :base)
|
16
|
+
hcaptcha_response = options[:response] || hcaptcha_response_token(options[:action])
|
17
|
+
|
18
|
+
begin
|
19
|
+
verified = if Hcaptcha.invalid_response?(hcaptcha_response)
|
20
|
+
false
|
21
|
+
else
|
22
|
+
unless options[:skip_remote_ip]
|
23
|
+
remoteip = (request.respond_to?(:remote_ip) && request.remote_ip) || (env && env['REMOTE_ADDR'])
|
24
|
+
options = options.merge(remote_ip: remoteip.to_s) if remoteip
|
25
|
+
end
|
26
|
+
|
27
|
+
Hcaptcha.verify_via_api_call(hcaptcha_response, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
if verified
|
31
|
+
flash.delete(:hcaptcha_error) if hcaptcha_flash_supported? && !model
|
32
|
+
true
|
33
|
+
else
|
34
|
+
hcaptcha_error(
|
35
|
+
model,
|
36
|
+
attribute,
|
37
|
+
options.fetch(:message) { Hcaptcha::Helpers.to_error_message(:verification_failed) }
|
38
|
+
)
|
39
|
+
false
|
40
|
+
end
|
41
|
+
rescue Timeout::Error
|
42
|
+
if Hcaptcha.configuration.handle_timeouts_gracefully
|
43
|
+
hcaptcha_error(
|
44
|
+
model,
|
45
|
+
attribute,
|
46
|
+
options.fetch(:message) { Hcaptcha::Helpers.to_error_message(:hcaptcha_unreachable) }
|
47
|
+
)
|
48
|
+
false
|
49
|
+
else
|
50
|
+
raise HcaptchaError, 'Hcaptcha unreachable.'
|
51
|
+
end
|
52
|
+
rescue StandardError => e
|
53
|
+
raise HcaptchaError, e.message, e.backtrace
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def verify_hcaptcha!(options = {})
|
58
|
+
verify_hcaptcha(options) || raise(VerifyError)
|
59
|
+
end
|
60
|
+
|
61
|
+
def hcaptcha_error(model, attribute, message)
|
62
|
+
if model
|
63
|
+
model.errors.add(attribute, message)
|
64
|
+
elsif hcaptcha_flash_supported?
|
65
|
+
flash[:hcaptcha_error] = message
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def hcaptcha_flash_supported?
|
70
|
+
request.respond_to?(:format) && request.format == :html && respond_to?(:flash)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Extracts response token from params. params['h-captcha-response'] should either be a
|
74
|
+
# string or a hash with the action name(s) as keys. If it is a hash, then `action` is used as
|
75
|
+
# the key.
|
76
|
+
# @return [String] A response token if one was passed in the params; otherwise, `''`
|
77
|
+
def hcaptcha_response_token(action = nil)
|
78
|
+
response_param = params['h-captcha-response']
|
79
|
+
if response_param&.respond_to?(:to_h) # Includes ActionController::Parameters
|
80
|
+
response_param[action].to_s
|
81
|
+
else
|
82
|
+
response_param.to_s
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hcaptcha
|
4
|
+
# This class enables detailed configuration of the hcaptcha services.
|
5
|
+
#
|
6
|
+
# By calling
|
7
|
+
#
|
8
|
+
# Hcaptcha.configuration # => instance of Hcaptcha::Configuration
|
9
|
+
#
|
10
|
+
# or
|
11
|
+
# Hcaptcha.configure do |config|
|
12
|
+
# config # => instance of Hcaptcha::Configuration
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# you are able to perform configuration updates.
|
16
|
+
#
|
17
|
+
# Your are able to customize all attributes listed below. All values have
|
18
|
+
# sensitive default and will very likely not need to be changed.
|
19
|
+
#
|
20
|
+
# Please note that the site and secret key for the hCaptcha API Access
|
21
|
+
# have no useful default value. The keys may be set via the Shell enviroment
|
22
|
+
# or using this configuration. Settings within this configuration always take
|
23
|
+
# precedence.
|
24
|
+
#
|
25
|
+
# Setting the keys with this Configuration
|
26
|
+
#
|
27
|
+
# Hcaptcha.configure do |config|
|
28
|
+
# config.site_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
|
29
|
+
# config.secret_key = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
class Configuration
|
33
|
+
DEFAULTS = {
|
34
|
+
'server_url' => 'https://hcaptcha.com/1/api.js',
|
35
|
+
'verify_url' => 'https://hcaptcha.com/siteverify'
|
36
|
+
}.freeze
|
37
|
+
|
38
|
+
attr_accessor :default_env, :skip_verify_env, :secret_key, :site_key, :proxy, :handle_timeouts_gracefully, :hostname
|
39
|
+
attr_writer :api_server_url, :verify_url
|
40
|
+
|
41
|
+
def initialize #:nodoc:
|
42
|
+
@default_env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || (Rails.env if defined? Rails.env)
|
43
|
+
@skip_verify_env = %w[test cucumber]
|
44
|
+
@handle_timeouts_gracefully = true
|
45
|
+
|
46
|
+
@secret_key = ENV['HCAPTCHA_SECRET_KEY']
|
47
|
+
@site_key = ENV['HCAPTCHA_SITE_KEY']
|
48
|
+
@verify_url = nil
|
49
|
+
@api_server_url = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def secret_key!
|
53
|
+
secret_key || raise(HcaptchaError, "No secret key specified.")
|
54
|
+
end
|
55
|
+
|
56
|
+
def site_key!
|
57
|
+
site_key || raise(HcaptchaError, "No site key specified.")
|
58
|
+
end
|
59
|
+
|
60
|
+
def api_server_url
|
61
|
+
@api_server_url || DEFAULTS.fetch('server_url')
|
62
|
+
end
|
63
|
+
|
64
|
+
def verify_url
|
65
|
+
@verify_url || DEFAULTS.fetch('verify_url')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hcaptcha
|
4
|
+
module Helpers
|
5
|
+
DEFAULT_ERRORS = {
|
6
|
+
hcaptcha_unreachable: 'Oops, we failed to validate your hCaptcha response. Please try again.',
|
7
|
+
verification_failed: 'hCaptcha verification failed, please try again.'
|
8
|
+
}.freeze
|
9
|
+
|
10
|
+
def self.hcaptcha(options)
|
11
|
+
if options.key?(:stoken)
|
12
|
+
raise(HcaptchaError, "Secure Token is deprecated. Please remove 'stoken' from your calls to hcaptcha_tags.")
|
13
|
+
end
|
14
|
+
if options.key?(:ssl)
|
15
|
+
raise(HcaptchaError, "SSL is now always true. Please remove 'ssl' from your calls to hcaptcha_tags.")
|
16
|
+
end
|
17
|
+
|
18
|
+
html, tag_attributes = components(options.dup)
|
19
|
+
html << %(<div #{tag_attributes}></div>\n)
|
20
|
+
|
21
|
+
html << <<-HTML
|
22
|
+
<div class="h-captcha" data-sitekey="#{Hcaptcha.configuration.site_key!}" data-theme="dark"></div>
|
23
|
+
HTML
|
24
|
+
|
25
|
+
html.respond_to?(:html_safe) ? html.html_safe : html
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.to_error_message(key)
|
29
|
+
default = DEFAULT_ERRORS.fetch(key) { raise ArgumentError "Unknown hCaptcha error - #{key}" }
|
30
|
+
to_message("hcaptcha.errors.#{key}", default)
|
31
|
+
end
|
32
|
+
|
33
|
+
if defined?(I18n)
|
34
|
+
def self.to_message(key, default)
|
35
|
+
I18n.translate(key, default: default)
|
36
|
+
end
|
37
|
+
else
|
38
|
+
def self.to_message(_key, default)
|
39
|
+
default
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private_class_method def self.components(options)
|
44
|
+
html = +''
|
45
|
+
attributes = {}
|
46
|
+
|
47
|
+
options = options.dup
|
48
|
+
class_attribute = options.delete(:class)
|
49
|
+
site_key = options.delete(:site_key)
|
50
|
+
hl = options.delete(:hl)
|
51
|
+
onload = options.delete(:onload)
|
52
|
+
render = options.delete(:render)
|
53
|
+
script_async = options.delete(:script_async)
|
54
|
+
script_defer = options.delete(:script_defer)
|
55
|
+
nonce = options.delete(:nonce)
|
56
|
+
skip_script = (options.delete(:script) == false) || (options.delete(:external_script) == false)
|
57
|
+
ui = options.delete(:ui)
|
58
|
+
|
59
|
+
data_attribute_keys = [:badge, :theme, :type, :callback, :expired_callback, :error_callback, :size]
|
60
|
+
data_attribute_keys << :tabindex unless ui == :button
|
61
|
+
data_attributes = {}
|
62
|
+
data_attribute_keys.each do |data_attribute|
|
63
|
+
value = options.delete(data_attribute)
|
64
|
+
data_attributes["data-#{data_attribute.to_s.tr('_', '-')}"] = value if value
|
65
|
+
end
|
66
|
+
|
67
|
+
site_key ||= Hcaptcha.configuration.site_key!
|
68
|
+
script_url = Hcaptcha.configuration.api_server_url
|
69
|
+
query_params = hash_to_query(
|
70
|
+
hl: hl,
|
71
|
+
onload: onload,
|
72
|
+
render: render
|
73
|
+
)
|
74
|
+
script_url += "?#{query_params}" unless query_params.empty?
|
75
|
+
async_attr = "async" if script_async != false
|
76
|
+
defer_attr = "defer" if script_defer != false
|
77
|
+
nonce_attr = " nonce='#{nonce}'" if nonce
|
78
|
+
html << %(<script src="#{script_url}" #{async_attr} #{defer_attr} #{nonce_attr}></script>\n) unless skip_script
|
79
|
+
attributes["data-sitekey"] = site_key
|
80
|
+
attributes.merge! data_attributes
|
81
|
+
|
82
|
+
# The remaining options will be added as attributes on the tag.
|
83
|
+
attributes["class"] = "hcaptcha #{class_attribute}"
|
84
|
+
tag_attributes = attributes.merge(options).map { |k, v| %(#{k}="#{v}") }.join(" ")
|
85
|
+
|
86
|
+
[html, tag_attributes]
|
87
|
+
end
|
88
|
+
|
89
|
+
private_class_method def self.hash_to_query(hash)
|
90
|
+
hash.delete_if { |_, val| val.nil? || val.empty? }.to_a.map { |pair| pair.join('=') }.join('&')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hcaptcha
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
ActiveSupport.on_load(:action_view) do
|
6
|
+
include Hcaptcha::Adapters::ViewMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
ActiveSupport.on_load(:action_controller) do
|
10
|
+
include Hcaptcha::Adapters::ControllerMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
initializer 'hcaptcha' do |app|
|
14
|
+
Hcaptcha::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
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hcaptcha
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 7.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Christopher Harrison
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-07-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mocha
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: i18n
|
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
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: maxitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry-byebug
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: bump
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: webmock
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rubocop
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: Ruby helpers for hCaptcha
|
140
|
+
email:
|
141
|
+
- chris.harrison@nexusmods.com
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- CHANGELOG.md
|
147
|
+
- LICENSE
|
148
|
+
- README.md
|
149
|
+
- lib/hcaptcha.rb
|
150
|
+
- lib/hcaptcha/adapters/controller_methods.rb
|
151
|
+
- lib/hcaptcha/adapters/view_methods.rb
|
152
|
+
- lib/hcaptcha/configuration.rb
|
153
|
+
- lib/hcaptcha/helpers.rb
|
154
|
+
- lib/hcaptcha/rails.rb
|
155
|
+
- lib/hcaptcha/railtie.rb
|
156
|
+
- lib/hcaptcha/version.rb
|
157
|
+
- rails/locales/en.yml
|
158
|
+
homepage: https://github.com/Nexus-Mods/hcaptcha
|
159
|
+
licenses:
|
160
|
+
- MIT
|
161
|
+
metadata:
|
162
|
+
source_code_uri: https://github.com/Nexus-Mods/hcaptcha
|
163
|
+
post_install_message:
|
164
|
+
rdoc_options: []
|
165
|
+
require_paths:
|
166
|
+
- lib
|
167
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
168
|
+
requirements:
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: 2.3.0
|
172
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
|
+
requirements:
|
174
|
+
- - ">="
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: '0'
|
177
|
+
requirements: []
|
178
|
+
rubygems_version: 3.1.2
|
179
|
+
signing_key:
|
180
|
+
specification_version: 4
|
181
|
+
summary: Ruby helpers for hCaptcha
|
182
|
+
test_files: []
|