Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/controllers/concerns/two_factor_authenticatable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ module TwoFactorAuthenticatable
def handle_second_factor_locked_user
analytics.track_event(Analytics::MULTI_FACTOR_AUTH_MAX_ATTEMPTS)

render 'two_factor_authentication/shared/max_login_attempts_reached'

sign_out

render 'two_factor_authentication/shared/max_login_attempts_reached'
end

def check_already_authenticated
Expand Down
2 changes: 1 addition & 1 deletion app/views/layouts/application.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ html lang="#{I18n.locale}"
= render 'shared/footer_lite'

#session-timeout-cntnr
- if user_fully_authenticated?
- if current_user
= auto_session_timeout_js
-else
= auto_session_expired_js
Expand Down
35 changes: 22 additions & 13 deletions spec/features/two_factor_authentication/sign_in_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,25 +105,34 @@
expect(page).not_to have_css('.progress-steps')
end

scenario 'user who enters OTP incorrectly 3 times is locked out for OTP validity period' do
user = create(:user, :signed_up)
sign_in_before_2fa(user)
click_button t('forms.buttons.submit.default')
context 'user enters OTP incorrectly 3 times', js: true do
it 'locks the user out and leaves user on the page during entire lockout period' do
allow(Figaro.env).to receive(:session_check_frequency).and_return('1')
allow(Figaro.env).to receive(:session_check_delay).and_return('2')

3.times do
fill_in('code', with: 'bad-code')
user = create(:user, :signed_up)
sign_in_before_2fa(user)
click_button t('forms.buttons.submit.default')
end

expect(page).to have_content t('titles.account_locked')
3.times do
fill_in('code', with: 'bad-code')
click_button t('forms.buttons.submit.default')
end

# let 10 minutes (otp validity period) magically pass
user.update(second_factor_locked_at: Time.zone.now - (Devise.direct_otp_valid_for + 1.second))
expect(page).to have_content t('titles.account_locked')
expect(page).to have_content('4 minutes and 54 seconds')
expect(page).to have_content('4 minutes and 53 seconds')

sign_in_before_2fa(user)
click_button t('forms.buttons.submit.default')
# let lockout period expire
user.update(
second_factor_locked_at: Time.zone.now - (Devise.direct_otp_valid_for + 1.second)
)

expect(page).to have_content t('devise.two_factor_authentication.header_text')
sign_in_before_2fa(user)
click_button t('forms.buttons.submit.default')

expect(page).to have_content t('devise.two_factor_authentication.header_text')
end
end

context 'user signs in while locked out' do
Expand Down
15 changes: 15 additions & 0 deletions spec/features/users/sign_in_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,21 @@
end
end

context 'user only signs in via email and password', js: true do
it 'displays the session timeout warning' do
allow(Figaro.env).to receive(:session_check_frequency).and_return('1')
allow(Figaro.env).to receive(:session_check_delay).and_return('2')
allow(Figaro.env).to receive(:session_timeout_warning_seconds).
and_return(Devise.timeout_in.to_s)

user = create(:user, :signed_up)
sign_in_user(user)
visit user_two_factor_authentication_path

expect(page).to have_css('#session-timeout-msg')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this expectation test the change we are making in this PR?

we want to leave the user on the account locked page for the duration of the lockout period.

It seems like it might but I am not sure. Perhaps adding more info to the spec description would clarify?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expectation seems correct, but I think the 'why' of this PR might be slightly confusing. It restores the session warning modal when the user has started the 2FA process, but not finished it.

When I updated the account locked screen, I removed that modal from the sign in flow entirely (i.e. a user will only see if after they have successfully 2FA'd).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This expectation is testing a separate change. I suppose I should have split them up. One change was fixing the bug that redirected the user to the sign in page after being on the lockout page for about a minute. This change here is to make sure the session timeout modal appears on the 2FA page, which was the previous behavior before @el-mapache changed it in #772, but we didn't have a test for it.

@el-mapache, correct me if I'm wrong, but it sounded like you made this change because you thought it had something to do with the redirection from the lockout page to the sign in page. Is that correct, or was there a separate reason for this change?

Copy link
Copy Markdown
Contributor

@el-mapache el-mapache Dec 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made the change because previously, on the account locked page, the user would be signed out and returned to the sign in screen early, as you note in your PR description.

When this happened, the session timeout modal would briefly appear. I used the user_fully_authenticated? method so that that session timeout wouldn't appear unless the user had successfully completed the 2FA process.

I thought this was acceptable behavior because the user would still have their session automatically expired if they left their 2FA process open for an extended period of time without completing the sign in process.

Sorry to make the extra work 😬

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries. The change I made here will solve the problem you mentioned without having to change the conditional in application.html.slim. Only showing the timeout warning modal to fully authed users will negatively affect UX in my opinion, which is why I think we should revert it back.

Thoughts?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, i agree! I was conflating that modal with a user being logged in, not just having an active session. Your solution is definitely cleaner since it uses the same logic we had and lets the framework render the correct partial based on the data it's provided.

end
end

context 'signed out' do
it 'links to current page after session expires', js: true do
allow(Devise).to receive(:timeout_in).and_return(0)
Expand Down