Skip to content
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

Recovery code support for webauthn #3859

Merged
merged 11 commits into from Jun 26, 2023

Conversation

ericherscovich
Copy link
Contributor

What problem are you solving?

This PR allows recovery codes to be used with webauthn. If a user generates webauthn credentials after #3821 was merged, or has OTP enabled, they will have recovery codes which can be used. On both prompts, this functionality is now supported.

If a user has webauthn only enabled, and has recovery codes, this is what they will see:
Screenshot 2023-06-08 at 1 49 20 PM

Contributes to #3800

What approach did you choose and why?

There is a case where a webauthn user wouldn't have recovery codes. That is, if they had webauthn only enabled before #3821 was merged. So, there is protection in rendering, in order to not render the recovery code prompt if a user does not have these recovery codes. The recovery code field simply uses the existing OTP field.

@codecov
Copy link

codecov bot commented Jun 13, 2023

Codecov Report

Merging #3859 (42fe8ef) into master (bbb1af8) will decrease coverage by 0.01%.
The diff coverage is 100.00%.

@@            Coverage Diff             @@
##           master    #3859      +/-   ##
==========================================
- Coverage   98.79%   98.79%   -0.01%     
==========================================
  Files         216      216              
  Lines        5404     5394      -10     
==========================================
- Hits         5339     5329      -10     
  Misses         65       65              
Impacted Files Coverage Δ
app/controllers/email_confirmations_controller.rb 100.00% <100.00%> (ø)
app/controllers/multifactor_auths_controller.rb 100.00% <100.00%> (ø)
app/controllers/passwords_controller.rb 100.00% <100.00%> (ø)
app/controllers/sessions_controller.rb 100.00% <100.00%> (ø)
app/models/concerns/user_webauthn_methods.rb 100.00% <100.00%> (ø)

Copy link
Member

@jenshenny jenshenny left a comment

Choose a reason for hiding this comment

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

Did some initial testing with the sign in flow and it works well! I have some initial questions and suggestions

app/controllers/email_confirmations_controller.rb Outdated Show resolved Hide resolved
@@ -13,7 +13,7 @@ def create

if @user && (@user.mfa_enabled? || @user.webauthn_credentials.any?)
setup_webauthn_authentication(form_url: webauthn_create_session_path, session_options: { "user" => @user.id })
Copy link
Member

Choose a reason for hiding this comment

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

session_options: { "user" => @user.id }

could we use session[:mfa_user] instead now in webauthn_create?

(Can punt this to later)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I think we can rely on session[:mfa_user] instead. But will save that for a separate issue.

Copy link
Member

Choose a reason for hiding this comment

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

Is there a difference between mfa_prompt and this view? We could possibly consolidate the two views into one if there aren't any differences.

(Can punt this to later)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No there isn't. The only difference is prompt has a hard-coded callback, whereas mfa_prompt requires you to pass one through. I chatted about this with Betty, and we think we should switch to using just mfa_prompt so we don't have two of essentially the same file. But, that change is outside the scope of this PR.

app/views/sessions/prompt.html.erb Outdated Show resolved Hide resolved
test/integration/email_confirmation_test.rb Outdated Show resolved Hide resolved
test/functional/passwords_controller_test.rb Show resolved Hide resolved
<% if @user.totp_enabled? %>
<%= label_tag :otp, t('multifactor_auths.otp_or_recovery'), class: 'form__label' %>
<%= text_field_tag :otp, '', class: 'form__input', autofocus: true, autocomplete: :off %>
<% elsif @user.webauthn_only_with_recovery? %>
Copy link
Member

Choose a reason for hiding this comment

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

Hot take: could we render this even if there aren't any recovery codes? A user could have generated codes and used them all... I don't think we should be expose any details of if the user has any codes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Chatted about this with Jenny, and we decided to keep it the way it is - specifically to not show the recovery code prompt if the user doesn't have any. The justification for this, is that it takes up half of the page, and would do so while serving the user no functionality. So if a user doesn't have recovery codes, it would just be confusing to have half the page prompting them for it. The simplest design philosophy for users is not showing them stuff that is irrelevant to them, which is what we are doing.

<%= label_tag :otp, t('multifactor_auths.otp_or_recovery'), class: 'form__label' %>
<%= text_field_tag :otp, '', class: 'form__input', autofocus: true, autocomplete: :off %>
<% elsif @user.webauthn_only_with_recovery? %>
<%= label_tag :otp, t('multifactor_auths.recovery_code'), class: 'form__label' %>
Copy link
Member

Choose a reason for hiding this comment

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

nit: do we need the label tag here? It looks a bit redundant that the heading is also Recovery code
Screenshot 2023-06-14 at 10 45 22 AM

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe for accessibility it's better to keep the label tag there.

Copy link
Member

Choose a reason for hiding this comment

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

I think the label will still be associated with the text field even if hidden?

Copy link
Contributor

Choose a reason for hiding this comment

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

Herm ... I agree with Jenny. It does feel redundant 😅 . I think Eric & I had noted this while prototyping too. Staring at this longer, I'm inclined to advise dropping the label as well.

From an a11y standpoint, we can remove the label and add the aria-label attribute on the input (e.g. aria-label="Recovery code").

Copy link
Member

@jenshenny jenshenny Jun 26, 2023

Choose a reason for hiding this comment

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

Added aria label! 93a7934

Copy link
Member

@jenshenny jenshenny left a comment

Choose a reason for hiding this comment

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

Tested these flows with 1. no MFA, 2. webauthn no recovery, 3. only totp, 4. totp and webauthn, 5. webauthn with recovery.

  • signin
  • password reset
  • email confirmations
  • update mfa level

Can you squash the commits since it's close to merge?

@ericherscovich ericherscovich force-pushed the eric/authn-recovery-codes branch 3 times, most recently from 441d039 to 426a15b Compare June 22, 2023 18:17
Copy link
Member

@jenshenny jenshenny left a comment

Choose a reason for hiding this comment

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

LGTM! Tested all the flows and they work as expected. As Eric is out for most of this week, I'll address the remaining comments.

test/functional/multifactor_auths_controller_test.rb Outdated Show resolved Hide resolved
test/functional/multifactor_auths_controller_test.rb Outdated Show resolved Hide resolved
Copy link
Member

@simi simi left a comment

Choose a reason for hiding this comment

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

It seems there are few last bits needed to be tweaked on UI and wording side. Feel free to merge once you're happy @jenshenny @bettymakes.

@jenshenny jenshenny merged commit 74e7294 into rubygems:master Jun 26, 2023
13 checks passed
@rubygems-org-shipit rubygems-org-shipit bot temporarily deployed to staging June 27, 2023 10:48 Inactive
@rubygems-org-shipit rubygems-org-shipit bot temporarily deployed to production June 27, 2023 17:11 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants