diff --git a/app/components/captcha_submit_button_component.html.erb b/app/components/captcha_submit_button_component.html.erb index 175257297aa..88e7033c4ac 100644 --- a/app/components/captcha_submit_button_component.html.erb +++ b/app/components/captcha_submit_button_component.html.erb @@ -25,13 +25,14 @@ <% end %> <% else %> - <%= f.input(:recaptcha_token, as: :hidden) %> + <%= f.input(:recaptcha_token, as: :hidden, input_html: { value: '' }) %> <% end %> <%= render SpinnerButtonComponent.new( action_message: t('components.captcha_submit_button.action_message'), type: :submit, big: true, wide: true, + **button_options, ).with_content(content) %> <% if recaptcha_script_src.present? %> <%= content_tag(:script, '', src: recaptcha_script_src, async: true) %> diff --git a/app/components/captcha_submit_button_component.rb b/app/components/captcha_submit_button_component.rb index 9c1a30c5cd6..981dd374224 100644 --- a/app/components/captcha_submit_button_component.rb +++ b/app/components/captcha_submit_button_component.rb @@ -1,14 +1,15 @@ # frozen_string_literal: true class CaptchaSubmitButtonComponent < BaseComponent - attr_reader :form, :action, :tag_options + attr_reader :form, :action, :button_options, :tag_options alias_method :f, :form # @param [String] action https://developers.google.com/recaptcha/docs/v3#actions - def initialize(form:, action:, **tag_options) + def initialize(form:, action:, button_options:, **tag_options) @form = form @action = action + @button_options = button_options @tag_options = tag_options end diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index 7b1555de346..9a5d79fe510 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -31,6 +31,7 @@ def create session[:sign_in_flow] = :sign_in return process_locked_out_session if session_bad_password_count_max_exceeded? return process_locked_out_user if current_user && user_locked_out?(current_user) + return process_failed_captcha if !valid_captcha_result? rate_limit_password_failure = true self.resource = warden.authenticate!(auth_options) @@ -79,6 +80,40 @@ def process_locked_out_session redirect_to root_url end + def valid_captcha_result? + if cookies[:device] && (device = Device.find_by(cookie_uuid: cookies[:device])) + return true if device.user.email_addresses.lazy.map(&:email).include?(auth_params[:email]) + end + + response, _assessment_id = recaptcha_form.submit(params.require(:user)[:recaptcha_token]) + flash[:error] = response.first_error_message if !response.success? + response.success? + end + + def process_failed_captcha + warden.logout(:user) + warden.lock! + redirect_to root_url + end + + def recaptcha_form + @recaptcha_form ||= SignInRecaptchaForm.new(**recaptcha_form_args) + end + + def recaptcha_form_args + args = { analytics: } + if IdentityConfig.store.phone_recaptcha_mock_validator + args.merge( + form_class: RecaptchaMockForm, + score: params.require(:user)[:recaptcha_mock_score].to_f, + ) + elsif FeatureManagement.recaptcha_enterprise? + args.merge(form_class: RecaptchaEnterpriseForm) + else + args + end + end + def redirect_to_signin controller_info = 'users/sessions#create' analytics.invalid_authenticity_token(controller: controller_info) diff --git a/app/forms/sign_in_recaptcha_form.rb b/app/forms/sign_in_recaptcha_form.rb new file mode 100644 index 00000000000..79accdfddcb --- /dev/null +++ b/app/forms/sign_in_recaptcha_form.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class SignInRecaptchaForm + RECAPTCHA_ACTION = 'sign_in' + + attr_reader :form_class, :form_args + + delegate :submit, :errors, to: :form + + def initialize(form_class: RecaptchaForm, **form_args) + @form_class = form_class + @form_args = form_args + end + + private + + def form + @form ||= form_class.new( + score_threshold: IdentityConfig.store.sign_in_recaptcha_score_threshold, + recaptcha_action: RECAPTCHA_ACTION, + **form_args, + ) + end +end diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 4740a47101c..2b41fad879c 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -48,7 +48,12 @@ }, ) %> <%= hidden_field_tag :platform_authenticator_available, id: 'platform_authenticator_available' %> - <%= f.submit t('links.sign_in'), full_width: true, wide: false %> + + <%= render CaptchaSubmitButtonComponent.new( + form: f, + action: SignInRecaptchaForm::RECAPTCHA_ACTION, + button_options: { full_width: true }, + ).with_content(t('links.sign_in')) %> <% end %> <% if desktop_device? %>