Commit 6fa2efdf authored by Drew Blessing's avatar Drew Blessing

Add reCAPTCHA to password reset and confirmation email forms

The global password reset and confirmation email forms previously
had no reCAPTCHA and could be abused. This adds reCAPTCHA to both
to reduce/prevent abuse.

Changelog: security
parent 4866cbed
# frozen_string_literal: true
module GitlabRecaptcha
extend ActiveSupport::Concern
include Recaptcha::Verify
include RecaptchaHelper
def load_recaptcha
recaptcha_enabled? && Gitlab::Recaptcha.load_configurations!
end
def check_recaptcha
return unless load_recaptcha
return if verify_recaptcha
flash[:alert] = _('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.')
flash.delete :recaptcha_error
self.resource = resource_class.new
render action: 'new'
end
end
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
class ConfirmationsController < Devise::ConfirmationsController class ConfirmationsController < Devise::ConfirmationsController
include AcceptsPendingInvitations include AcceptsPendingInvitations
include GitlabRecaptcha
prepend_before_action :check_recaptcha, only: :create
before_action :load_recaptcha, only: :new
feature_category :users feature_category :users
...@@ -31,6 +35,12 @@ class ConfirmationsController < Devise::ConfirmationsController ...@@ -31,6 +35,12 @@ class ConfirmationsController < Devise::ConfirmationsController
end end
end end
def check_recaptcha
return unless resource_params[:email].present?
super
end
def after_sign_in(resource) def after_sign_in(resource)
after_sign_in_path_for(resource) after_sign_in_path_for(resource)
end end
......
# frozen_string_literal: true # frozen_string_literal: true
class PasswordsController < Devise::PasswordsController class PasswordsController < Devise::PasswordsController
include GitlabRecaptcha
skip_before_action :require_no_authentication, only: [:edit, :update] skip_before_action :require_no_authentication, only: [:edit, :update]
prepend_before_action :check_recaptcha, only: :create
before_action :load_recaptcha, only: :new
before_action :resource_from_email, only: [:create] before_action :resource_from_email, only: [:create]
before_action :check_password_authentication_available, only: [:create] before_action :check_password_authentication_available, only: [:create]
before_action :throttle_reset, only: [:create] before_action :throttle_reset, only: [:create]
...@@ -59,6 +63,12 @@ class PasswordsController < Devise::PasswordsController ...@@ -59,6 +63,12 @@ class PasswordsController < Devise::PasswordsController
alert: _("Password authentication is unavailable.") alert: _("Password authentication is unavailable.")
end end
def check_recaptcha
return unless resource_params[:email].present?
super
end
def throttle_reset def throttle_reset
return unless resource && resource.recently_sent_password_reset? return unless resource && resource.recently_sent_password_reset?
......
# frozen_string_literal: true # frozen_string_literal: true
module RecaptchaHelper module RecaptchaHelper
def show_recaptcha_sign_up? def recaptcha_enabled?
!!Gitlab::Recaptcha.enabled? !!Gitlab::Recaptcha.enabled?
end end
alias_method :show_recaptcha_sign_up?, :recaptcha_enabled?
end end
RecaptchaHelper.prepend_mod RecaptchaHelper.prepend_mod
...@@ -7,7 +7,12 @@ ...@@ -7,7 +7,12 @@
.form-group .form-group
= f.label :email = f.label :email
= f.email_field :email, class: "form-control gl-form-input", required: true, title: _('Please provide a valid email address.'), value: nil = f.email_field :email, class: "form-control gl-form-input", required: true, title: _('Please provide a valid email address.'), value: nil
.clearfix
%div
- if recaptcha_enabled?
= recaptcha_tags nonce: content_security_policy_nonce
.gl-mt-5
= f.submit _("Resend"), class: 'gl-button btn btn-confirm' = f.submit _("Resend"), class: 'gl-button btn btn-confirm'
.clearfix.prepend-top-20 .clearfix.prepend-top-20
......
...@@ -8,7 +8,12 @@ ...@@ -8,7 +8,12 @@
= f.email_field :email, class: "form-control gl-form-input", required: true, value: params[:user_email], autofocus: true, title: _('Please provide a valid email address.') = f.email_field :email, class: "form-control gl-form-input", required: true, value: params[:user_email], autofocus: true, title: _('Please provide a valid email address.')
.form-text.text-muted .form-text.text-muted
= _('Requires your primary GitLab email address.') = _('Requires your primary GitLab email address.')
.clearfix
%div
- if recaptcha_enabled?
= recaptcha_tags nonce: content_security_policy_nonce
.gl-mt-5
= f.submit _("Reset password"), class: "gl-button btn-confirm btn" = f.submit _("Reset password"), class: "gl-button btn-confirm btn"
.clearfix.prepend-top-20 .clearfix.prepend-top-20
......
...@@ -123,4 +123,45 @@ RSpec.describe ConfirmationsController do ...@@ -123,4 +123,45 @@ RSpec.describe ConfirmationsController do
end end
end end
end end
describe '#create' do
let(:user) { create(:user) }
subject(:perform_request) { post(:create, params: { user: { email: user.email } }) }
context 'when reCAPTCHA is disabled' do
before do
stub_application_setting(recaptcha_enabled: false)
end
it 'successfully sends password reset when reCAPTCHA is not solved' do
perform_request
expect(response).to redirect_to(dashboard_projects_path)
end
end
context 'when reCAPTCHA is enabled' do
before do
stub_application_setting(recaptcha_enabled: true)
end
it 'displays an error when the reCAPTCHA is not solved' do
Recaptcha.configuration.skip_verify_env.delete('test')
perform_request
expect(response).to render_template(:new)
expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
end
it 'successfully sends password reset when reCAPTCHA is solved' do
Recaptcha.configuration.skip_verify_env << 'test'
perform_request
expect(response).to redirect_to(dashboard_projects_path)
end
end
end
end end
...@@ -91,4 +91,47 @@ RSpec.describe PasswordsController do ...@@ -91,4 +91,47 @@ RSpec.describe PasswordsController do
end end
end end
end end
describe '#create' do
let(:user) { create(:user) }
subject(:perform_request) { post(:create, params: { user: { email: user.email } }) }
context 'when reCAPTCHA is disabled' do
before do
stub_application_setting(recaptcha_enabled: false)
end
it 'successfully sends password reset when reCAPTCHA is not solved' do
perform_request
expect(response).to redirect_to(new_user_session_path)
expect(flash[:notice]).to include 'If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes.'
end
end
context 'when reCAPTCHA is enabled' do
before do
stub_application_setting(recaptcha_enabled: true)
end
it 'displays an error when the reCAPTCHA is not solved' do
Recaptcha.configuration.skip_verify_env.delete('test')
perform_request
expect(response).to render_template(:new)
expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
end
it 'successfully sends password reset when reCAPTCHA is solved' do
Recaptcha.configuration.skip_verify_env << 'test'
perform_request
expect(response).to redirect_to(new_user_session_path)
expect(flash[:notice]).to include 'If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes.'
end
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User confirmation' do
describe 'resend confirmation instructions' do
context 'when recaptcha is enabled' do
before do
stub_application_setting(recaptcha_enabled: true)
allow(Gitlab::Recaptcha).to receive(:load_configurations!)
visit new_user_confirmation_path
end
it 'renders recaptcha' do
expect(page).to have_css('.g-recaptcha')
end
end
context 'when recaptcha is not enabled' do
before do
stub_application_setting(recaptcha_enabled: false)
visit new_user_confirmation_path
end
it 'does not render recaptcha' do
expect(page).not_to have_css('.g-recaptcha')
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User password' do
describe 'send password reset' do
context 'when recaptcha is enabled' do
before do
stub_application_setting(recaptcha_enabled: true)
allow(Gitlab::Recaptcha).to receive(:load_configurations!)
visit new_user_password_path
end
it 'renders recaptcha' do
expect(page).to have_css('.g-recaptcha')
end
end
context 'when recaptcha is not enabled' do
before do
stub_application_setting(recaptcha_enabled: false)
visit new_user_password_path
end
it 'does not render recaptcha' do
expect(page).not_to have_css('.g-recaptcha')
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment