Commit f4ec906e authored by Drew Blessing's avatar Drew Blessing

Use devise paranoid mode and ensure the same message is returned every time

Skipped CI because it has already passed. Had to rebase due to CHANGELOG.
parent 7b50965e
...@@ -32,6 +32,9 @@ v 8.2.3 ...@@ -32,6 +32,9 @@ v 8.2.3
- Fix Error 500s when creating global milestones with Unicode characters (Stan Hu) - Fix Error 500s when creating global milestones with Unicode characters (Stan Hu)
- Update documentation for "Guest" permissions - Update documentation for "Guest" permissions
- Properly convert Emoji-only comments into Award Emojis - Properly convert Emoji-only comments into Award Emojis
- Enable devise paranoid mode to prevent user enumeration attack
v 8.2.3
- Webhook payload has an added, modified and removed properties for each commit - Webhook payload has an added, modified and removed properties for each commit
- Fix 500 error when creating a merge request that removes a submodule - Fix 500 error when creating a merge request that removes a submodule
......
...@@ -40,7 +40,9 @@ class PasswordsController < Devise::PasswordsController ...@@ -40,7 +40,9 @@ class PasswordsController < Devise::PasswordsController
def throttle_reset def throttle_reset
return unless resource && resource.recently_sent_password_reset? return unless resource && resource.recently_sent_password_reset?
redirect_to new_password_path(resource_name), # Throttle reset attempts, but return a normal message to
alert: I18n.t('devise.passwords.recently_reset') # avoid user enumeration attack.
redirect_to new_user_session_path,
notice: I18n.t('devise.passwords.send_paranoid_instructions')
end end
end end
...@@ -60,7 +60,7 @@ Devise.setup do |config| ...@@ -60,7 +60,7 @@ Devise.setup do |config|
# It will change confirmation, password recovery and other workflows # It will change confirmation, password recovery and other workflows
# to behave the same regardless if the e-mail provided was right or wrong. # to behave the same regardless if the e-mail provided was right or wrong.
# Does not affect registerable. # Does not affect registerable.
# config.paranoid = true config.paranoid = true
# ==> Configuration for :database_authenticatable # ==> Configuration for :database_authenticatable
# For bcrypt, this is the cost for hashing the password and defaults to 10. If # For bcrypt, this is the cost for hashing the password and defaults to 10. If
......
...@@ -30,7 +30,6 @@ en: ...@@ -30,7 +30,6 @@ en:
success: "Successfully authenticated from %{kind} account." success: "Successfully authenticated from %{kind} account."
passwords: passwords:
no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
recently_reset: "Instructions about how to reset your password have already been sent recently. Please wait a few minutes to try again."
send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
updated: "Your password has been changed successfully. You are now signed in." updated: "Your password has been changed successfully. You are now signed in."
......
...@@ -3,11 +3,12 @@ require 'spec_helper' ...@@ -3,11 +3,12 @@ require 'spec_helper'
feature 'Password reset', feature: true do feature 'Password reset', feature: true do
describe 'throttling' do describe 'throttling' do
it 'sends reset instructions when not previously sent' do it 'sends reset instructions when not previously sent' do
visit root_path user = create(:user)
forgot_password(create(:user)) forgot_password(user)
expect(page).to have_content(I18n.t('devise.passwords.send_instructions')) expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions'))
expect(current_path).to eq new_user_session_path expect(current_path).to eq new_user_session_path
expect(user.recently_sent_password_reset?).to be_truthy
end end
it 'sends reset instructions when previously sent more than a minute ago' do it 'sends reset instructions when previously sent more than a minute ago' do
...@@ -15,26 +16,25 @@ feature 'Password reset', feature: true do ...@@ -15,26 +16,25 @@ feature 'Password reset', feature: true do
user.send_reset_password_instructions user.send_reset_password_instructions
user.update_attribute(:reset_password_sent_at, 5.minutes.ago) user.update_attribute(:reset_password_sent_at, 5.minutes.ago)
visit root_path expect{ forgot_password(user) }.to change{ user.reset_password_sent_at }
forgot_password(user) expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions'))
expect(page).to have_content(I18n.t('devise.passwords.send_instructions'))
expect(current_path).to eq new_user_session_path expect(current_path).to eq new_user_session_path
end end
it "throttles multiple resets in a short timespan" do it 'throttles multiple resets in a short timespan' do
user = create(:user) user = create(:user)
user.send_reset_password_instructions user.send_reset_password_instructions
# Reload because PG handles datetime less precisely than Ruby/Rails
user.reload
visit root_path expect{ forgot_password(user) }.not_to change{ user.reset_password_sent_at }
forgot_password(user) expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions'))
expect(current_path).to eq new_user_session_path
expect(page).to have_content(I18n.t('devise.passwords.recently_reset'))
expect(current_path).to eq new_user_password_path
end end
end end
def forgot_password(user) def forgot_password(user)
visit root_path
click_on 'Forgot your password?' click_on 'Forgot your password?'
fill_in 'Email', with: user.email fill_in 'Email', with: user.email
click_button 'Reset password' click_button 'Reset password'
......
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