katalyst-google-apis 1.0.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/LICENSE.txt +21 -0
- data/README.md +76 -0
- data/app/assets/javascripts/controllers/recaptcha_controller.js +46 -0
- data/app/helpers/katalyst/google_apis/form_builder.rb +15 -0
- data/app/helpers/katalyst/google_apis/govuk_form_builder.rb +16 -0
- data/app/services/katalyst/google_apis/credentials.rb +99 -0
- data/app/services/katalyst/google_apis/recaptcha/assessment_service.rb +77 -0
- data/app/validators/recaptcha_validator.rb +32 -0
- data/config/importmap.rb +4 -0
- data/config/locales/en.yml +7 -0
- data/lib/katalyst/google_apis/config.rb +23 -0
- data/lib/katalyst/google_apis/engine.rb +25 -0
- data/lib/katalyst/google_apis/matchers/validate_recaptcha_for_matcher.rb +62 -0
- data/lib/katalyst/google_apis/matchers.rb +10 -0
- data/lib/katalyst/google_apis.rb +30 -0
- data/lib/katalyst-google-apis.rb +3 -0
- metadata +84 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6a4c525102b4817651ef073cf9da2c39a679a331c900eaf84bcde18d92a3141b
|
4
|
+
data.tar.gz: 62c1ced1409c7bb4a14531834a373c2f0b495b41be66cbc6859cadfbb1d9c9f4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 69060627b5f85a9ea12f7a74a4dd1b847d8ce88fe2b26d9e3c15025ae6e4b85af1a1e1a4f44288270493735674480a0cab3a851f00124326b52a59433c3909bb
|
7
|
+
data.tar.gz: 1e8c146a0e5e9c2146aeb5a034f3fea27f75116734b3d7c8ddfcadcac9d16efd5ddff0dc31c811d728528c00c0886038ec8aa6a3890af221e662cf72134da556
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 Katalyst Interactive
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# Katalyst::GoogleApis
|
2
|
+
|
3
|
+
Katalyst Google APIs provides a simple interface for integrating Google REST
|
4
|
+
APIs into Rails projects, with built-in support for AWS ECS OIDC authentication
|
5
|
+
and Recaptcha validation.
|
6
|
+
|
7
|
+
This project is an alternative approach to using the gRPC libraries provided by
|
8
|
+
Google, which require specific fork behaviour that (as of 2025) are
|
9
|
+
[not compatible with puma](https://github.com/puma/puma/issues/3503).
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Install the gem as usual
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem "katalyst-google-apis"
|
17
|
+
```
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Configure your google service account and project in an initializer:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
Katalyst::GoogleApis.configure do |config|
|
25
|
+
config.project_id = "prj-..."
|
26
|
+
config.project_number = "..."
|
27
|
+
config.service_account_email = ENV.fetch("GOOGLE_SERVICE_ACCOUNT_EMAIL", "sa-...@....iam.gserviceaccount.com")
|
28
|
+
config.identity_pool = "...-pool"
|
29
|
+
config.identity_provider = "...-provider"
|
30
|
+
|
31
|
+
# Recaptcha configuration
|
32
|
+
# site_keys appear in the frontend, they white-list domains so do not need to be kept secret
|
33
|
+
config.recaptcha.site_key = ENV.fetch("RECAPTCHA_SITE_KEY", "...")
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
These can also be configured using ENV variables, see Katalyst::GoogleApis::Config for details.
|
38
|
+
|
39
|
+
### Enterprise Recaptcha
|
40
|
+
|
41
|
+
Add a token field and validation to your model:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
attr_accessor :recaptcha_token
|
45
|
+
validates :recaptcha_token, recaptcha: true, on: :create
|
46
|
+
```
|
47
|
+
|
48
|
+
Add to permitted params in your controller, and add the field to your views:
|
49
|
+
|
50
|
+
```erb
|
51
|
+
<%= form.govuk_recaptcha_field :recaptcha_token %>
|
52
|
+
```
|
53
|
+
|
54
|
+
Test by adding `require "katalyst/google_apis/matchers"` to your `spec/rails_helpers.rb`.
|
55
|
+
|
56
|
+
You can test any or all of the following in your model:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
it { is_expected.to validate_recaptcha_for(:recaptcha_token, expected: :recaptcha_blank).on(:create) }
|
60
|
+
it { is_expected.to validate_recaptcha_for(:recaptcha_token, expected: :recaptcha_invalid).on(:create) }
|
61
|
+
it { is_expected.to validate_recaptcha_for(:recaptcha_token, expected: :recaptcha_action_mismatch).on(:create) }
|
62
|
+
it { is_expected.to validate_recaptcha_for(:recaptcha_token, expected: :recaptcha_suspicious).on(:create) }
|
63
|
+
```
|
64
|
+
|
65
|
+
## Development
|
66
|
+
|
67
|
+
After checking out the repo, run `bin/setup` to install dependencies. We do not
|
68
|
+
currently have a dummy app and specs, so test against an existing project.
|
69
|
+
|
70
|
+
## Contributing
|
71
|
+
|
72
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/katalyst/google-apis.
|
73
|
+
|
74
|
+
## License
|
75
|
+
|
76
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
2
|
+
|
3
|
+
const config = (window["___grecaptcha_cfg"] ||= {});
|
4
|
+
config["fns"] ||= [];
|
5
|
+
|
6
|
+
const recaptcha = (window["grecaptcha"] ||= {});
|
7
|
+
const enterprise = (recaptcha["enterprise"] ||= {});
|
8
|
+
|
9
|
+
enterprise.ready = (f) => {
|
10
|
+
(config["fns"] ||= []).push(f);
|
11
|
+
};
|
12
|
+
|
13
|
+
function init() {
|
14
|
+
if (document.head.querySelector("script[src*='recaptcha/enterprise']"))
|
15
|
+
return;
|
16
|
+
|
17
|
+
const script = document.createElement("script");
|
18
|
+
script.setAttribute(
|
19
|
+
"src",
|
20
|
+
"https://www.google.com/recaptcha/enterprise.js?render=explicit",
|
21
|
+
);
|
22
|
+
script.toggleAttribute("async", true);
|
23
|
+
script.toggleAttribute("defer", true);
|
24
|
+
|
25
|
+
document.head.appendChild(script);
|
26
|
+
}
|
27
|
+
|
28
|
+
export default class RecaptchaController extends Controller {
|
29
|
+
connect() {
|
30
|
+
init();
|
31
|
+
|
32
|
+
enterprise.ready(() => {
|
33
|
+
enterprise.render(this.element, {
|
34
|
+
sitekey: this.element.dataset.sitekey,
|
35
|
+
action: this.element.dataset.action,
|
36
|
+
callback: (response) => {
|
37
|
+
this.responseTarget.value = response;
|
38
|
+
},
|
39
|
+
});
|
40
|
+
});
|
41
|
+
}
|
42
|
+
|
43
|
+
get responseTarget() {
|
44
|
+
return this.element.nextElementSibling;
|
45
|
+
}
|
46
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module GoogleApis
|
5
|
+
module FormBuilder
|
6
|
+
def recaptcha_field(attribute = :recaptcha_token, action: object_name,
|
7
|
+
sitekey: GoogleApis.config.recaptcha.site_key)
|
8
|
+
safe_join([
|
9
|
+
content_tag(:div, "", data: { action:, controller: "recaptcha", sitekey: }),
|
10
|
+
hidden_field(attribute),
|
11
|
+
])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module GoogleApis
|
5
|
+
module GOVUKFormBuilder
|
6
|
+
def govuk_recaptcha_field(attribute = :recaptcha_token, **)
|
7
|
+
GOVUKDesignSystemFormBuilder::Containers::FormGroup.new(self, object_name, attribute).html do
|
8
|
+
safe_join([
|
9
|
+
GOVUKDesignSystemFormBuilder::Elements::ErrorMessage.new(self, object_name, attribute),
|
10
|
+
recaptcha_field(attribute, **),
|
11
|
+
])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "aws-sdk-core"
|
4
|
+
|
5
|
+
module Katalyst
|
6
|
+
module GoogleApis
|
7
|
+
class Credentials < Google::Auth::ExternalAccount::AwsCredentials
|
8
|
+
def initialize(**)
|
9
|
+
super(Config.new(**).to_h)
|
10
|
+
|
11
|
+
@aws_provider = ::Aws::CredentialProviderChain.new.resolve
|
12
|
+
end
|
13
|
+
|
14
|
+
# Override the default implementation that only supports EC2 credentials.
|
15
|
+
def fetch_security_credentials
|
16
|
+
# Note: Aws::CredentialProviderChain is a private API, but because it is
|
17
|
+
# consumed directly by AWS utilities we assume it's stable.
|
18
|
+
# This approach would not be required if Google's base class supported
|
19
|
+
# resolving credentials from ECS environments.
|
20
|
+
credentials = @aws_provider.credentials
|
21
|
+
|
22
|
+
# Short-lived credentials for the AWS ECS instance role
|
23
|
+
# These are used to authenticate the call to Google Cloud to authenticate
|
24
|
+
# to the GC service account using OIDC based on the AWS ECS identity.
|
25
|
+
{
|
26
|
+
access_key_id: credentials.access_key_id,
|
27
|
+
secret_access_key: credentials.secret_access_key,
|
28
|
+
session_token: credentials.session_token,
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def region
|
33
|
+
@region ||= @aws_provider.client.config.region
|
34
|
+
end
|
35
|
+
|
36
|
+
class Config
|
37
|
+
include ActiveModel::Model
|
38
|
+
include ActiveModel::Attributes
|
39
|
+
|
40
|
+
attribute :scope, :string, default: "https://www.googleapis.com/auth/cloud-platform"
|
41
|
+
attribute :service_account_email, :string
|
42
|
+
attribute :project_number, :integer
|
43
|
+
attribute :identity_pool, :string
|
44
|
+
attribute :identity_provider, :string
|
45
|
+
attribute :token_lifetime_seconds, :integer, default: 3600
|
46
|
+
|
47
|
+
def audience
|
48
|
+
["//iam.googleapis.com", *{
|
49
|
+
projects: project_number,
|
50
|
+
locations: "global",
|
51
|
+
workloadIdentityPools: identity_pool,
|
52
|
+
providers: identity_provider,
|
53
|
+
}].join("/")
|
54
|
+
end
|
55
|
+
|
56
|
+
def service_account_impersonation_url
|
57
|
+
["https://iamcredentials.googleapis.com/v1", *{
|
58
|
+
projects: "-",
|
59
|
+
serviceAccounts: "#{service_account_email}:generateAccessToken",
|
60
|
+
}].join("/")
|
61
|
+
end
|
62
|
+
|
63
|
+
def regional_cred_verification_url
|
64
|
+
"https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
|
65
|
+
end
|
66
|
+
|
67
|
+
def subject_token_type
|
68
|
+
"urn:ietf:params:aws:token-type:aws4_request"
|
69
|
+
end
|
70
|
+
|
71
|
+
def token_url
|
72
|
+
"https://sts.googleapis.com/v1/token"
|
73
|
+
end
|
74
|
+
|
75
|
+
def universe_domain
|
76
|
+
"googleapis.com"
|
77
|
+
end
|
78
|
+
|
79
|
+
def type
|
80
|
+
"external_account"
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_h
|
84
|
+
{
|
85
|
+
scope:,
|
86
|
+
universe_domain:,
|
87
|
+
type:,
|
88
|
+
audience:,
|
89
|
+
subject_token_type:,
|
90
|
+
token_url:,
|
91
|
+
service_account_impersonation_url:,
|
92
|
+
service_account_impersonation: { token_lifetime_seconds: },
|
93
|
+
credential_source: { regional_cred_verification_url: },
|
94
|
+
}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module GoogleApis
|
5
|
+
module Recaptcha
|
6
|
+
class AssessmentService
|
7
|
+
attr_accessor :response, :result, :error
|
8
|
+
|
9
|
+
def self.call(parent:, credentials: GoogleApis.credentials, **)
|
10
|
+
new(credentials:, parent:).call(**)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(credentials:, parent:)
|
14
|
+
@credentials = credentials
|
15
|
+
@parent = parent
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(assessment:)
|
19
|
+
@response = Curl.post(url, assessment.to_json) do |http|
|
20
|
+
http.headers["Content-Type"] = "application/json; UTF-8"
|
21
|
+
@credentials.apply!(http.headers)
|
22
|
+
end
|
23
|
+
|
24
|
+
@result = JSON.parse(@response.body, symbolize_names: true)
|
25
|
+
|
26
|
+
self
|
27
|
+
rescue Curl::Easy::Error => e
|
28
|
+
if defined?(Sentry)
|
29
|
+
Sentry.add_breadcrumb(sentry_breadcrumb(e))
|
30
|
+
else
|
31
|
+
Rails.logger.error(e)
|
32
|
+
end
|
33
|
+
|
34
|
+
@error = e
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def valid?
|
39
|
+
@result.present? && @result.dig(:tokenProperties, :valid)
|
40
|
+
end
|
41
|
+
|
42
|
+
def action
|
43
|
+
return nil unless valid?
|
44
|
+
|
45
|
+
@result.dig(:tokenProperties, :action)
|
46
|
+
end
|
47
|
+
|
48
|
+
def score
|
49
|
+
return nil unless valid?
|
50
|
+
|
51
|
+
@result.dig(:riskAnalysis, :score)
|
52
|
+
end
|
53
|
+
|
54
|
+
def inspect
|
55
|
+
"#<#{self.class.name} result: #{@result.inspect} error: #{@error.inspect}>"
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def url
|
61
|
+
"https://recaptchaenterprise.googleapis.com/v1/#{@parent}/assessments"
|
62
|
+
end
|
63
|
+
|
64
|
+
def sentry_breadcrumb(error)
|
65
|
+
Sentry::Breadcrumb.new(
|
66
|
+
type: "http",
|
67
|
+
category: "recaptcha",
|
68
|
+
url:,
|
69
|
+
method: "POST",
|
70
|
+
status_code: error.code,
|
71
|
+
reason: error.message,
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Example:
|
4
|
+
# validates :recaptcha_token, recaptcha: { score: 0.5 }, on: :create
|
5
|
+
class RecaptchaValidator < ActiveModel::EachValidator
|
6
|
+
def validate_each(record, attribute, token)
|
7
|
+
return if Katalyst::GoogleApis.config.recaptcha.test_mode
|
8
|
+
|
9
|
+
action = options.fetch(:action, record.class.model_name.param_key)
|
10
|
+
project_id = options.fetch(:project_id, Katalyst::GoogleApis.config.project_id)
|
11
|
+
score = options.fetch(:score, Katalyst::GoogleApis.config.recaptcha.score)
|
12
|
+
site_key = options.fetch(:site_key, Katalyst::GoogleApis.config.recaptcha.site_key)
|
13
|
+
|
14
|
+
if token.blank?
|
15
|
+
record.errors.add(attribute, :recaptcha_blank)
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
response = Katalyst::GoogleApis::Recaptcha::AssessmentService.call(
|
20
|
+
parent: "projects/#{project_id}",
|
21
|
+
assessment: { event: { site_key:, token: } },
|
22
|
+
)
|
23
|
+
|
24
|
+
if !response.valid?
|
25
|
+
record.errors.add(attribute, :recaptcha_invalid)
|
26
|
+
elsif response.action != action
|
27
|
+
record.errors.add(attribute, :recaptcha_action_mismatch)
|
28
|
+
elsif response.score < score
|
29
|
+
record.errors.add(attribute, :recaptcha_suspicious)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/config/importmap.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
en:
|
2
|
+
errors:
|
3
|
+
messages:
|
4
|
+
recaptcha_blank: "Please verify that you're not a robot."
|
5
|
+
recaptcha_invalid: "There was a problem verifying your submission. Please try again."
|
6
|
+
recaptcha_action_mismatch: "Something went wrong with the form validation. Please refresh the page and try again."
|
7
|
+
recaptcha_suspicious: "We couldn't verify your submission. Please try again or contact support if the problem persists."
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module GoogleApis
|
5
|
+
class Config
|
6
|
+
include ActiveSupport::Configurable
|
7
|
+
|
8
|
+
config_accessor(:project_id) { ENV.fetch("GOOGLE_PROJECT_ID", nil) }
|
9
|
+
config_accessor(:project_number) { ENV.fetch("GOOGLE_PROJECT_NUMBER", nil) }
|
10
|
+
config_accessor(:service_account_email) { ENV.fetch("GOOGLE_SERVICE_ACCOUNT_EMAIL", nil) }
|
11
|
+
config_accessor(:identity_pool) { ENV.fetch("GOOGLE_OIDC_IDENTITY_POOL", nil) }
|
12
|
+
config_accessor(:identity_provider) { ENV.fetch("GOOGLE_OIDC_IDENTITY_PROVIDER", nil) }
|
13
|
+
|
14
|
+
config_accessor(:recaptcha) do
|
15
|
+
defaults = ActiveSupport::OrderedOptions.new
|
16
|
+
defaults.site_key = ENV.fetch("GOOGLE_RECAPTCHA_SITE_KEY", nil)
|
17
|
+
defaults.score = ENV.fetch("GOOGLE_RECAPTCHA_SCORE", 0.5).to_f
|
18
|
+
defaults.test_mode = !ENV.fetch("VERIFY_RECAPTCHA", !Rails.env.local?) # rubocop:disable Rails/UnknownEnv
|
19
|
+
defaults
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/engine"
|
4
|
+
|
5
|
+
module Katalyst
|
6
|
+
module GoogleApis
|
7
|
+
class Engine < ::Rails::Engine
|
8
|
+
initializer "katalyst-google-apis.importmap", before: "importmap" do |app|
|
9
|
+
if app.config.respond_to?(:importmap)
|
10
|
+
app.config.importmap.paths << root.join("config/importmap.rb")
|
11
|
+
app.config.importmap.cache_sweepers << root.join("app/javascript/controllers")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
initializer "katalyst-google-apis.forms" do |app|
|
16
|
+
app.config.after_initialize do
|
17
|
+
ActionView::Helpers::FormBuilder.include(Katalyst::GoogleApis::FormBuilder)
|
18
|
+
if defined?(GOVUKDesignSystemFormBuilder)
|
19
|
+
GOVUKDesignSystemFormBuilder::Builder.include(Katalyst::GoogleApis::GOVUKFormBuilder)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module GoogleApis
|
5
|
+
module Matchers
|
6
|
+
def validate_recaptcha_for(attribute, expected: :recaptcha_suspicious)
|
7
|
+
matcher = ValidateRecaptchaForMatcher.new(attribute, expected:)
|
8
|
+
|
9
|
+
if (response = matcher.example_response(subject))
|
10
|
+
service = instance_double(Recaptcha::AssessmentService)
|
11
|
+
allow(service).to receive_messages(**response)
|
12
|
+
allow(Recaptcha::AssessmentService).to receive(:call).and_return(service)
|
13
|
+
end
|
14
|
+
|
15
|
+
matcher
|
16
|
+
end
|
17
|
+
|
18
|
+
class ValidateRecaptchaForMatcher < Shoulda::Matchers::ActiveModel::ValidationMatcher
|
19
|
+
def initialize(attribute, expected:)
|
20
|
+
super(attribute)
|
21
|
+
|
22
|
+
@expected_message = expected
|
23
|
+
end
|
24
|
+
|
25
|
+
def matches?(subject)
|
26
|
+
Katalyst::GoogleApis.config.recaptcha.test_mode = false
|
27
|
+
|
28
|
+
super
|
29
|
+
|
30
|
+
disallows_value_of(example_value, @expected_message)
|
31
|
+
ensure
|
32
|
+
Katalyst::GoogleApis.config.recaptcha.test_mode = true
|
33
|
+
end
|
34
|
+
|
35
|
+
def simple_description
|
36
|
+
"validate that :#{@attribute} includes reCAPTCHA validation"
|
37
|
+
end
|
38
|
+
|
39
|
+
def example_value
|
40
|
+
{
|
41
|
+
recaptcha_invalid: "<invalid-token>",
|
42
|
+
recaptcha_action_mismatch: "<action-mismatch-token>",
|
43
|
+
recaptcha_suspicious: "<suspicious-token>",
|
44
|
+
}[@expected_message]
|
45
|
+
end
|
46
|
+
|
47
|
+
def example_response(subject)
|
48
|
+
case @expected_message
|
49
|
+
when :recaptcha_invalid
|
50
|
+
{ valid?: false }
|
51
|
+
when :recaptcha_action_mismatch
|
52
|
+
{ valid?: true, action: "mismatch" }
|
53
|
+
when :recaptcha_suspicious
|
54
|
+
{ valid?: true, action: subject.class.model_name.param_key, score: 0.1 }
|
55
|
+
else
|
56
|
+
{ valid?: true, action: subject.class.model_name.param_key, score: 0.9 }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support"
|
4
|
+
require "katalyst/google_apis/engine"
|
5
|
+
|
6
|
+
module Katalyst
|
7
|
+
module GoogleApis
|
8
|
+
extend self
|
9
|
+
extend ActiveSupport::Autoload
|
10
|
+
|
11
|
+
autoload :Config
|
12
|
+
|
13
|
+
def config
|
14
|
+
@config ||= Config.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def configure
|
18
|
+
yield(config)
|
19
|
+
end
|
20
|
+
|
21
|
+
def credentials
|
22
|
+
@credentials ||= Credentials.new(
|
23
|
+
project_number: config.project_number,
|
24
|
+
service_account_email: config.service_account_email,
|
25
|
+
identity_pool: config.identity_pool,
|
26
|
+
identity_provider: config.identity_provider,
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: katalyst-google-apis
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Katalyst Interactive
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: activesupport
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: googleauth
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
email:
|
41
|
+
- developers@katalyst.com.au
|
42
|
+
executables: []
|
43
|
+
extensions: []
|
44
|
+
extra_rdoc_files: []
|
45
|
+
files:
|
46
|
+
- LICENSE.txt
|
47
|
+
- README.md
|
48
|
+
- app/assets/javascripts/controllers/recaptcha_controller.js
|
49
|
+
- app/helpers/katalyst/google_apis/form_builder.rb
|
50
|
+
- app/helpers/katalyst/google_apis/govuk_form_builder.rb
|
51
|
+
- app/services/katalyst/google_apis/credentials.rb
|
52
|
+
- app/services/katalyst/google_apis/recaptcha/assessment_service.rb
|
53
|
+
- app/validators/recaptcha_validator.rb
|
54
|
+
- config/importmap.rb
|
55
|
+
- config/locales/en.yml
|
56
|
+
- lib/katalyst-google-apis.rb
|
57
|
+
- lib/katalyst/google_apis.rb
|
58
|
+
- lib/katalyst/google_apis/config.rb
|
59
|
+
- lib/katalyst/google_apis/engine.rb
|
60
|
+
- lib/katalyst/google_apis/matchers.rb
|
61
|
+
- lib/katalyst/google_apis/matchers/validate_recaptcha_for_matcher.rb
|
62
|
+
homepage: https://github.com/katalyst/google-apis
|
63
|
+
licenses:
|
64
|
+
- MIT
|
65
|
+
metadata:
|
66
|
+
rubygems_mfa_required: 'true'
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3.4'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
requirements: []
|
81
|
+
rubygems_version: 3.6.7
|
82
|
+
specification_version: 4
|
83
|
+
summary: Google REST APIs for use in Rails projects
|
84
|
+
test_files: []
|