Skip to content

Commit

Permalink
LG-14052: Add routes for mismatched WebAuthn platform attachment (#11795
Browse files Browse the repository at this point in the history
)

* LG-14052: Add routes for mismatched WebAuthn platform attachment

changelog: Upcoming Features, Multi-Factor Authentication, Convert Security Key to Face or Touch Unlock when detected as platform authenticator

* Reverse unintended locale string changes
  • Loading branch information
aduth authored Jan 24, 2025
1 parent 9d0909f commit bfc5404
Show file tree
Hide file tree
Showing 21 changed files with 671 additions and 4 deletions.
1 change: 1 addition & 0 deletions app/assets/images/webauthn-mismatch/webauthn-checked.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/assets/images/webauthn-mismatch/webauthn-unchecked.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions app/controllers/concerns/mfa_deletion_concern.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module MfaDeletionConcern
include RememberDeviceConcern

def handle_successful_mfa_deletion(event_type:)
create_user_event(event_type)
revoke_remember_device(current_user)
event = PushNotification::RecoveryInformationChangedEvent.new(user: current_user)
PushNotification::HttpPush.deliver(event)
nil
end
end
82 changes: 82 additions & 0 deletions app/controllers/users/webauthn_setup_mismatch_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# frozen_string_literal: true

module Users
class WebauthnSetupMismatchController < ApplicationController
include MfaSetupConcern
include MfaDeletionConcern
include SecureHeadersConcern
include ReauthenticationRequiredConcern

before_action :confirm_user_authenticated_for_2fa_setup
before_action :apply_secure_headers_override
before_action :confirm_recently_authenticated_2fa
before_action :validate_session_mismatch_id

def show
analytics.webauthn_setup_mismatch_visited(
configuration_id: configuration.id,
platform_authenticator: platform_authenticator?,
)

@presenter = WebauthnSetupMismatchPresenter.new(configuration:)
end

def update
analytics.webauthn_setup_mismatch_submitted(
configuration_id: configuration.id,
platform_authenticator: platform_authenticator?,
confirmed_mismatch: true,
)

redirect_to next_setup_path || after_mfa_setup_path
end

def destroy
result = ::TwoFactorAuthentication::WebauthnDeleteForm.new(
user: current_user,
configuration_id: webauthn_mismatch_id,
skip_multiple_mfa_validation: in_multi_mfa_selection_flow?,
).submit

analytics.webauthn_setup_mismatch_submitted(**result.to_h, confirmed_mismatch: false)

if result.success?
handle_successful_mfa_deletion(event_type: :webauthn_key_removed)
redirect_to retry_setup_url
else
flash.now[:error] = result.first_error_message
@presenter = WebauthnSetupMismatchPresenter.new(configuration:)
render :show
end
end

private

def retry_setup_url
# These are intentionally inverted: if the authenticator was set up as a platform
# authenticator but was flagged as a mismatch, it implies that the user had originally
# intended to add a security key.
if platform_authenticator?
webauthn_setup_url
else
webauthn_setup_url(platform: true)
end
end

def webauthn_mismatch_id
user_session[:webauthn_mismatch_id]
end

def configuration
return @configuration if defined?(@configuration)
@configuration = current_user.webauthn_configurations.find_by(id: webauthn_mismatch_id)
end

def validate_session_mismatch_id
return if configuration.present?
redirect_to next_setup_path || after_mfa_setup_path
end

delegate :platform_authenticator?, to: :configuration
end
end
12 changes: 10 additions & 2 deletions app/forms/two_factor_authentication/webauthn_delete_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ class WebauthnDeleteForm
validate :validate_configuration_exists
validate :validate_has_multiple_mfa

def initialize(user:, configuration_id:)
def initialize(user:, configuration_id:, skip_multiple_mfa_validation: false)
@user = user
@configuration_id = configuration_id
@skip_multiple_mfa_validation = skip_multiple_mfa_validation
end

def submit
Expand All @@ -34,6 +35,10 @@ def configuration

private

attr_reader :skip_multiple_mfa_validation

alias_method :skip_multiple_mfa_validation?, :skip_multiple_mfa_validation

def validate_configuration_exists
return if configuration.present?
errors.add(
Expand All @@ -44,7 +49,10 @@ def validate_configuration_exists
end

def validate_has_multiple_mfa
return if !configuration || MfaPolicy.new(user).multiple_factors_enabled?
return if skip_multiple_mfa_validation? ||
!configuration ||
MfaPolicy.new(user).multiple_factors_enabled?

errors.add(
:configuration_id,
:only_method,
Expand Down
47 changes: 47 additions & 0 deletions app/presenters/webauthn_setup_mismatch_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

class WebauthnSetupMismatchPresenter
include ActionView::Helpers::TranslationHelper

attr_reader :configuration

def initialize(configuration:)
@configuration = configuration
end

def heading
if platform_authenticator?
t('webauthn_setup_mismatch.heading.webauthn_platform')
else
t('webauthn_setup_mismatch.heading.webauthn')
end
end

def description
if platform_authenticator?
t('webauthn_setup_mismatch.description.webauthn_platform')
else
t('webauthn_setup_mismatch.description.webauthn')
end
end

def correct_image_path
if platform_authenticator?
'webauthn-mismatch/webauthn-platform-checked.svg'
else
'webauthn-mismatch/webauthn-checked.svg'
end
end

def incorrect_image_path
if platform_authenticator?
'webauthn-mismatch/webauthn-unchecked.svg'
else
'webauthn-mismatch/webauthn-platform-unchecked.svg'
end
end

private

delegate :platform_authenticator?, to: :configuration
end
49 changes: 48 additions & 1 deletion app/services/analytics_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7706,7 +7706,54 @@ def webauthn_platform_recommended_visited
track_event(:webauthn_platform_recommended_visited)
end

# @param [Hash] platform_authenticator
# @param [Boolean] platform_authenticator Whether authentication method was registered as platform
# authenticator
# @param [Number] configuration_id Database ID of WebAuthn configuration
# @param [Boolean] confirmed_mismatch Whether user chose to confirm and continue with interpreted
# platform attachment
# @param [Boolean] success Whether the deletion was successful, if user chose to undo interpreted
# platform attachment
# @param [Hash] error_details Details for errors that occurred in unsuccessful deletion
# User submitted confirmation screen after setting up WebAuthn with transports mismatched with the
# expected platform attachment
def webauthn_setup_mismatch_submitted(
configuration_id:,
platform_authenticator:,
confirmed_mismatch:,
success: nil,
error_details: nil,
**extra
)
track_event(
:webauthn_setup_mismatch_submitted,
configuration_id:,
platform_authenticator:,
confirmed_mismatch:,
success:,
error_details:,
**extra,
)
end

# @param [Boolean] platform_authenticator Whether authentication method was registered as platform
# authenticator
# @param [Number] configuration_id Database ID of WebAuthn configuration
# User visited confirmation screen after setting up WebAuthn with transports mismatched with the
# expected platform attachment
def webauthn_setup_mismatch_visited(
configuration_id:,
platform_authenticator:,
**extra
)
track_event(
:webauthn_setup_mismatch_visited,
configuration_id:,
platform_authenticator:,
**extra,
)
end

# @param [Boolean] platform_authenticator
# @param [Boolean] success
# @param [Hash, nil] errors
# @param [Boolean] in_account_creation_flow Whether user is going through account creation flow
Expand Down
42 changes: 42 additions & 0 deletions app/views/users/webauthn_setup_mismatch/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<% self.title = @presenter.heading %>

<div class="display-flex flex-align-center margin-bottom-4">
<%= image_tag(
@presenter.correct_image_path,
width: 104,
height: 116,
alt: '',
aria: { hidden: true },
) %>
<%= image_tag(
@presenter.incorrect_image_path,
width: 64,
height: 71,
class: 'margin-left-2',
alt: '',
aria: { hidden: true },
) %>
</div>

<%= render PageHeadingComponent.new.with_content(@presenter.heading) %>

<p><%= @presenter.description %></p>

<p><%= t('webauthn_setup_mismatch.description_undo') %>

<div class="margin-top-5 margin-bottom-2">
<%= render ButtonComponent.new(
url: webauthn_setup_mismatch_url,
method: :patch,
big: true,
wide: true,
).with_content(t('forms.buttons.continue')) %>
</div>

<%= render ButtonComponent.new(
url: webauthn_setup_mismatch_url,
method: :delete,
big: true,
wide: true,
outline: true,
).with_content(t('webauthn_setup_mismatch.undo')) %>
Loading

0 comments on commit bfc5404

Please sign in to comment.