-
Notifications
You must be signed in to change notification settings - Fork 167
Refactor reCAPTCHA validator as form model #10540
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,21 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| class RecaptchaValidator | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we rename to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I thought about that. We probably should, wasn't sure if the amount of noise it might add to this pull request might be worth doing it incrementally (i.e. separate pull request), especially since there's subclasses that might also need to be moved. I did find that we have a couple other examples of form validators in
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some of the |
||
| include ActiveModel::Model | ||
| include ActionView::Helpers::TranslationHelper | ||
|
|
||
| VERIFICATION_ENDPOINT = 'https://www.google.com/recaptcha/api/siteverify' | ||
| RESULT_ERRORS = ['missing-input-secret', 'invalid-input-secret'].freeze | ||
|
|
||
| attr_reader :recaptcha_action, | ||
| :recaptcha_token, | ||
| :score_threshold, | ||
| :analytics, | ||
| :extra_analytics_properties | ||
|
|
||
| validate :validate_token_exists | ||
| validate :validate_recaptcha_result | ||
|
|
||
| RecaptchaResult = Struct.new(:success, :score, :errors, :reasons, keyword_init: true) do | ||
| alias_method :success?, :success | ||
|
|
||
|
|
@@ -33,20 +40,30 @@ def exempt? | |
| !score_threshold.positive? | ||
| end | ||
|
|
||
| def valid?(recaptcha_token) | ||
| return true if exempt? | ||
| return false if recaptcha_token.blank? | ||
| result = recaptcha_result(recaptcha_token) | ||
| log_analytics(result:) | ||
| recaptcha_result_valid?(result) | ||
| def submit(recaptcha_token) | ||
| @recaptcha_token = recaptcha_token | ||
| @recaptcha_result = recaptcha_result if !exempt? && recaptcha_token.present? | ||
|
|
||
| log_analytics(result: @recaptcha_result) if @recaptcha_result | ||
| FormResponse.new(success: valid?, errors:, serialize_error_details_only: true) | ||
| rescue Faraday::Error => error | ||
| log_analytics(error:) | ||
| true | ||
| FormResponse.new(success: true, serialize_error_details_only: true) | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def recaptcha_result(recaptcha_token) | ||
| def validate_token_exists | ||
| return if exempt? || recaptcha_token.present? | ||
| errors.add(:recaptcha_token, :blank, message: t('errors.messages.invalid_recaptcha_token')) | ||
| end | ||
|
|
||
| def validate_recaptcha_result | ||
| return if @recaptcha_result.blank? || recaptcha_result_valid?(@recaptcha_result) | ||
| errors.add(:recaptcha_token, :invalid, message: t('errors.messages.invalid_recaptcha_token')) | ||
| end | ||
|
|
||
| def recaptcha_result | ||
| response = faraday.post( | ||
| VERIFICATION_ENDPOINT, | ||
| URI.encode_www_form(secret: recaptcha_secret_key, response: recaptcha_token), | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does
errors.merge!work with an entire validator? I'd expect more likeThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what I initially expected as well, but it error'd because
ActiveModel::Errorsdoesn't have anerrorsmethod; it's expecting the model, not the errors.