From 6e5ba065da4c33d1df785e601ec3f05388aa5810 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Wed, 12 Jul 2023 11:21:33 -0400 Subject: [PATCH] Remove WebAuthn support detection, error page changelog: Upcoming Features, Face or Touch Unlock, Remove unnecessary feature detection for WebAuthn --- .../webauthn_verification_controller.rb | 2 - app/javascript/packages/webauthn/index.ts | 1 - .../webauthn/is-webauthn-supported.spec.ts | 32 --------- .../webauthn/is-webauthn-supported.ts | 5 -- .../webauthn/webauthn-input-element.spec.ts | 70 +++++-------------- .../webauthn/webauthn-input-element.ts | 5 -- app/javascript/packs/webauthn-authenticate.ts | 45 +++++------- app/javascript/packs/webauthn-setup.ts | 10 +-- .../webauthn_authentication_presenter.rb | 8 --- .../webauthn_verification/error.html.erb | 13 ---- .../webauthn_verification/show.html.erb | 38 ++++------ .../locales/two_factor_authentication/en.yml | 3 - .../locales/two_factor_authentication/es.yml | 3 - .../locales/two_factor_authentication/fr.yml | 4 -- config/routes.rb | 1 - 15 files changed, 54 insertions(+), 186 deletions(-) delete mode 100644 app/javascript/packages/webauthn/is-webauthn-supported.spec.ts delete mode 100644 app/javascript/packages/webauthn/is-webauthn-supported.ts delete mode 100644 app/views/two_factor_authentication/webauthn_verification/error.html.erb diff --git a/app/controllers/two_factor_authentication/webauthn_verification_controller.rb b/app/controllers/two_factor_authentication/webauthn_verification_controller.rb index ec3503d4a81..768dcc93e2a 100644 --- a/app/controllers/two_factor_authentication/webauthn_verification_controller.rb +++ b/app/controllers/two_factor_authentication/webauthn_verification_controller.rb @@ -27,8 +27,6 @@ def confirm handle_webauthn_result(result) end - def error; end - private def handle_webauthn_result(result) diff --git a/app/javascript/packages/webauthn/index.ts b/app/javascript/packages/webauthn/index.ts index 85a1c01a9dc..99d4b40e820 100644 --- a/app/javascript/packages/webauthn/index.ts +++ b/app/javascript/packages/webauthn/index.ts @@ -1,4 +1,3 @@ -export { default as isWebauthnSupported } from './is-webauthn-supported'; export { default as enrollWebauthnDevice } from './enroll-webauthn-device'; export { default as extractCredentials } from './extract-credentials'; export { default as verifyWebauthnDevice } from './verify-webauthn-device'; diff --git a/app/javascript/packages/webauthn/is-webauthn-supported.spec.ts b/app/javascript/packages/webauthn/is-webauthn-supported.spec.ts deleted file mode 100644 index 46b4f776064..00000000000 --- a/app/javascript/packages/webauthn/is-webauthn-supported.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useDefineProperty } from '@18f/identity-test-helpers'; -import isWebauthnSupported from './is-webauthn-supported'; - -describe('isWebauthnSupported', () => { - const defineProperty = useDefineProperty(); - - context('browser does not support webauthn', () => { - beforeEach(() => { - defineProperty(navigator, 'credentials', { - configurable: true, - value: undefined, - }); - }); - - it('returns false', () => { - expect(isWebauthnSupported()).to.equal(false); - }); - }); - - context('browser supports webauthn', () => { - beforeEach(() => { - defineProperty(navigator, 'credentials', { - configurable: true, - value: { get: () => {} }, - }); - }); - - it('returns true', () => { - expect(isWebauthnSupported()).to.equal(true); - }); - }); -}); diff --git a/app/javascript/packages/webauthn/is-webauthn-supported.ts b/app/javascript/packages/webauthn/is-webauthn-supported.ts deleted file mode 100644 index 85b0e6c9a4e..00000000000 --- a/app/javascript/packages/webauthn/is-webauthn-supported.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type IsWebauthnSupported = () => boolean; - -const isWebauthnSupported: IsWebauthnSupported = () => !!navigator.credentials; - -export default isWebauthnSupported; diff --git a/app/javascript/packages/webauthn/webauthn-input-element.spec.ts b/app/javascript/packages/webauthn/webauthn-input-element.spec.ts index ef8df395b59..feb08c0106a 100644 --- a/app/javascript/packages/webauthn/webauthn-input-element.spec.ts +++ b/app/javascript/packages/webauthn/webauthn-input-element.spec.ts @@ -1,56 +1,38 @@ import sinon from 'sinon'; import quibble from 'quibble'; -import type { IsWebauthnSupported } from './is-webauthn-supported'; import type { IsWebauthnPasskeySupported } from './is-webauthn-passkey-supported'; describe('WebauthnInputElement', () => { - const isWebauthnSupported = sinon.stub< - Parameters, - ReturnType - >(); const isWebauthnPasskeySupported = sinon.stub< Parameters, ReturnType >(); before(async () => { - quibble('./is-webauthn-supported', isWebauthnSupported); quibble('./is-webauthn-passkey-supported', isWebauthnPasskeySupported); await import('./webauthn-input-element'); }); - beforeEach(() => { - isWebauthnSupported.reset(); - isWebauthnSupported.returns(false); - isWebauthnPasskeySupported.reset(); - isWebauthnPasskeySupported.returns(false); - }); - after(() => { quibble.reset(); }); - context('browser does not support webauthn', () => { + context('input for non-platform authenticator', () => { beforeEach(() => { - isWebauthnSupported.returns(false); document.body.innerHTML = ``; }); - it('stays hidden', () => { + it('becomes visible', () => { const element = document.querySelector('lg-webauthn-input')!; - expect(element.hidden).to.be.true(); + expect(element.hidden).to.be.false(); }); }); - context('browser supports webauthn', () => { - beforeEach(() => { - isWebauthnSupported.returns(true); - }); - - context('input for non-platform authenticator', () => { + context('input for platform authenticator', () => { + context('no passkey only restriction', () => { beforeEach(() => { - document.body.innerHTML = ``; + document.body.innerHTML = ``; }); it('becomes visible', () => { @@ -60,44 +42,30 @@ describe('WebauthnInputElement', () => { }); }); - context('input for platform authenticator', () => { - context('no passkey only restriction', () => { + context('passkey supported only', () => { + context('device does not support passkey', () => { beforeEach(() => { - document.body.innerHTML = ``; + isWebauthnPasskeySupported.returns(false); + document.body.innerHTML = ``; }); - it('becomes visible', () => { + it('stays hidden', () => { const element = document.querySelector('lg-webauthn-input')!; - expect(element.hidden).to.be.false(); + expect(element.hidden).to.be.true(); }); }); - context('passkey supported only', () => { - context('device does not support passkey', () => { - beforeEach(() => { - isWebauthnPasskeySupported.returns(false); - document.body.innerHTML = ``; - }); - - it('stays hidden', () => { - const element = document.querySelector('lg-webauthn-input')!; - - expect(element.hidden).to.be.true(); - }); + context('device supports passkey', () => { + beforeEach(() => { + isWebauthnPasskeySupported.returns(true); + document.body.innerHTML = ``; }); - context('device supports passkey', () => { - beforeEach(() => { - isWebauthnPasskeySupported.returns(true); - document.body.innerHTML = ``; - }); - - it('becomes visible', () => { - const element = document.querySelector('lg-webauthn-input')!; + it('becomes visible', () => { + const element = document.querySelector('lg-webauthn-input')!; - expect(element.hidden).to.be.false(); - }); + expect(element.hidden).to.be.false(); }); }); }); diff --git a/app/javascript/packages/webauthn/webauthn-input-element.ts b/app/javascript/packages/webauthn/webauthn-input-element.ts index 640206bafd3..e2b6796ffb2 100644 --- a/app/javascript/packages/webauthn/webauthn-input-element.ts +++ b/app/javascript/packages/webauthn/webauthn-input-element.ts @@ -1,5 +1,4 @@ import isWebauthnPasskeySupported from './is-webauthn-passkey-supported'; -import isWebauthnSupported from './is-webauthn-supported'; export class WebauthnInputElement extends HTMLElement { connectedCallback() { @@ -15,10 +14,6 @@ export class WebauthnInputElement extends HTMLElement { } isSupported(): boolean { - if (!isWebauthnSupported()) { - return false; - } - return !this.isPlatform || !this.isOnlyPasskeySupported || isWebauthnPasskeySupported(); } diff --git a/app/javascript/packs/webauthn-authenticate.ts b/app/javascript/packs/webauthn-authenticate.ts index fc8e7c35ed5..78eb490db92 100644 --- a/app/javascript/packs/webauthn-authenticate.ts +++ b/app/javascript/packs/webauthn-authenticate.ts @@ -1,9 +1,7 @@ -import { isWebauthnSupported, verifyWebauthnDevice } from '@18f/identity-webauthn'; +import { verifyWebauthnDevice } from '@18f/identity-webauthn'; import type { VerifyCredentialDescriptor } from '@18f/identity-webauthn'; function webauthn() { - const webauthnInProgressContainer = document.getElementById('webauthn-auth-in-progress')!; - const spinner = document.getElementById('spinner')!; spinner.classList.remove('display-none'); @@ -11,30 +9,25 @@ function webauthn() { (document.getElementById('credentials') as HTMLInputElement).value, ); - if (!isWebauthnSupported()) { - const href = webauthnInProgressContainer.getAttribute('data-webauthn-not-enabled-url')!; - window.location.href = href; - } else { - // if platform auth is not supported on device, we should take user to the error screen if theres no additional methods. - verifyWebauthnDevice({ - userChallenge: (document.getElementById('user_challenge') as HTMLInputElement).value, - credentials, + // if platform auth is not supported on device, we should take user to the error screen if theres no additional methods. + verifyWebauthnDevice({ + userChallenge: (document.getElementById('user_challenge') as HTMLInputElement).value, + credentials, + }) + .then((result) => { + (document.getElementById('credential_id') as HTMLInputElement).value = result.credentialId; + (document.getElementById('authenticator_data') as HTMLInputElement).value = + result.authenticatorData; + (document.getElementById('client_data_json') as HTMLInputElement).value = + result.clientDataJSON; + (document.getElementById('signature') as HTMLInputElement).value = result.signature; + }) + .catch((error: Error) => { + (document.getElementById('webauthn_error') as HTMLInputElement).value = error.name; }) - .then((result) => { - (document.getElementById('credential_id') as HTMLInputElement).value = result.credentialId; - (document.getElementById('authenticator_data') as HTMLInputElement).value = - result.authenticatorData; - (document.getElementById('client_data_json') as HTMLInputElement).value = - result.clientDataJSON; - (document.getElementById('signature') as HTMLInputElement).value = result.signature; - }) - .catch((error: Error) => { - (document.getElementById('webauthn_error') as HTMLInputElement).value = error.name; - }) - .then(() => { - (document.getElementById('webauthn_form') as HTMLFormElement).submit(); - }); - } + .then(() => { + (document.getElementById('webauthn_form') as HTMLFormElement).submit(); + }); } function webauthnButton() { diff --git a/app/javascript/packs/webauthn-setup.ts b/app/javascript/packs/webauthn-setup.ts index 42cadf772d8..09fbe0fa924 100644 --- a/app/javascript/packs/webauthn-setup.ts +++ b/app/javascript/packs/webauthn-setup.ts @@ -1,9 +1,4 @@ -import { - isWebauthnSupported, - enrollWebauthnDevice, - extractCredentials, - longToByteArray, -} from '@18f/identity-webauthn'; +import { enrollWebauthnDevice, extractCredentials, longToByteArray } from '@18f/identity-webauthn'; import { forceRedirect } from '@18f/identity-url'; import type { Navigate } from '@18f/identity-url'; @@ -32,9 +27,6 @@ export function reloadWithError( } function webauthn() { - if (!isWebauthnSupported()) { - reloadWithError('NotSupportedError'); - } const form = document.getElementById('webauthn_form') as HTMLFormElement; form.addEventListener('submit', (event) => { event.preventDefault(); diff --git a/app/presenters/two_factor_auth_code/webauthn_authentication_presenter.rb b/app/presenters/two_factor_auth_code/webauthn_authentication_presenter.rb index 619bd3020ac..1d68fc3d42d 100644 --- a/app/presenters/two_factor_auth_code/webauthn_authentication_presenter.rb +++ b/app/presenters/two_factor_auth_code/webauthn_authentication_presenter.rb @@ -81,14 +81,6 @@ def cancel_link end end - def webauthn_not_enabled_link - if platform_authenticator? - login_two_factor_webauthn_error_path - else - login_two_factor_options_path - end - end - def multiple_factors_enabled? service_provider_mfa_policy.multiple_factors_enabled? end diff --git a/app/views/two_factor_authentication/webauthn_verification/error.html.erb b/app/views/two_factor_authentication/webauthn_verification/error.html.erb deleted file mode 100644 index 661315946fc..00000000000 --- a/app/views/two_factor_authentication/webauthn_verification/error.html.erb +++ /dev/null @@ -1,13 +0,0 @@ -<%= render( - 'idv/shared/error', - heading: t('two_factor_authentication.webauthn_error.title'), - options: [ - { - text: t('links.contact_support', app_name: APP_NAME), - url: MarketingSite.contact_url, - new_tab: true, - }, - ], - ) do %> -

<%= t('two_factor_authentication.webauthn_error.error_page_text') %>

-<% end %> diff --git a/app/views/two_factor_authentication/webauthn_verification/show.html.erb b/app/views/two_factor_authentication/webauthn_verification/show.html.erb index c710f0d4b99..5d7d8e4d58b 100644 --- a/app/views/two_factor_authentication/webauthn_verification/show.html.erb +++ b/app/views/two_factor_authentication/webauthn_verification/show.html.erb @@ -24,29 +24,21 @@ <%= hidden_field_tag :webauthn_error, '', id: 'webauthn_error' %> <%= hidden_field_tag :platform, @presenter.platform_authenticator? %> - <%= content_tag( - :div, - id: 'webauthn-auth-in-progress', - data: { - webauthn_not_enabled_url: @presenter.webauthn_not_enabled_link, - }, - ) do %> - - - <% end %> + + <%= f.input( :remember_device, diff --git a/config/locales/two_factor_authentication/en.yml b/config/locales/two_factor_authentication/en.yml index 0f9d9fb32bb..1bf1fc26a9e 100644 --- a/config/locales/two_factor_authentication/en.yml +++ b/config/locales/two_factor_authentication/en.yml @@ -186,10 +186,7 @@ en: webauthn_authenticating: Authenticating your credentials… webauthn_error: additional_methods_link: choose another authentication method - error_page_text: You have face or touch unlock enabled for this account. Use the - same device and browser profile each time. multiple_methods: Face or touch unlock was unsuccessful. Please try again or %{link}. - title: We can’t identify your device webauthn_header_text: Connect your security key webauthn_piv_available: Use your PIV or CAC webauthn_platform_header_text: Use face or touch unlock diff --git a/config/locales/two_factor_authentication/es.yml b/config/locales/two_factor_authentication/es.yml index e1d9784b55b..ced88a84db3 100644 --- a/config/locales/two_factor_authentication/es.yml +++ b/config/locales/two_factor_authentication/es.yml @@ -200,11 +200,8 @@ es: webauthn_authenticating: Autenticando sus credenciales… webauthn_error: additional_methods_link: elija otro método de autenticación - error_page_text: Tiene habilitado el desbloqueo facial o táctil para esta - cuenta. Utilice cada vez el mismo dispositivo y perfil de navegador. multiple_methods: El desbloqueo facial o táctil no fue exitoso. Por favor, inténtelo de nuevo o %{link}. - title: No podemos reconocer su dispositivo webauthn_header_text: Conecte su llave de seguridad webauthn_piv_available: Utilice su PIV o CAC webauthn_platform_header_text: Usar desbloqueo facial o táctil diff --git a/config/locales/two_factor_authentication/fr.yml b/config/locales/two_factor_authentication/fr.yml index bd06ffa481e..5c58f46a4c4 100644 --- a/config/locales/two_factor_authentication/fr.yml +++ b/config/locales/two_factor_authentication/fr.yml @@ -206,12 +206,8 @@ fr: webauthn_authenticating: Authentification de vos informations d’identification… webauthn_error: additional_methods_link: choisir une autre méthode d’authentification - error_page_text: Vous avez activé le déverrouillage facial ou tactile pour ce - compte. Utilisez le même appareil et le même profil de navigateur chaque - fois. multiple_methods: Le déverrouillage facial ou tactile n’a pas fonctionné. Veuillez réessayer ou %{link}. - title: Nous ne pouvons pas identifier votre appareil webauthn_header_text: Connectez votre clé de sécurité webauthn_piv_available: Utilisez votre PIV ou CAC webauthn_platform_header_text: Utilisez le déverrouillage facial ou tactile diff --git a/config/routes.rb b/config/routes.rb index 57a66e9e30f..e991be27dc8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -115,7 +115,6 @@ get '/login/two_factor/piv_cac' => 'two_factor_authentication/piv_cac_verification#show' get '/login/two_factor/piv_cac/present_piv_cac' => 'two_factor_authentication/piv_cac_verification#redirect_to_piv_cac_service' get '/login/two_factor/webauthn' => 'two_factor_authentication/webauthn_verification#show' - get '/login/two_factor/webauthn_error' => 'two_factor_authentication/webauthn_verification#error' patch '/login/two_factor/webauthn' => 'two_factor_authentication/webauthn_verification#confirm' get 'login/two_factor/backup_code' => 'two_factor_authentication/backup_code_verification#show' post 'login/two_factor/backup_code' => 'two_factor_authentication/backup_code_verification#create'