diff --git a/app/assets/stylesheets/components/_util.scss b/app/assets/stylesheets/components/_util.scss index b215d5fb4ff..ee4462107ea 100644 --- a/app/assets/stylesheets/components/_util.scss +++ b/app/assets/stylesheets/components/_util.scss @@ -4,8 +4,6 @@ .no-hover-decoration:hover { text-decoration: none; } -.invisible { visibility: hidden; } - .hidden { display: none; } .truncate-inline { diff --git a/app/javascript/packages/personal-key-input/index.js b/app/javascript/packages/personal-key-input/index.js new file mode 100644 index 00000000000..6c36cc9bd7d --- /dev/null +++ b/app/javascript/packages/personal-key-input/index.js @@ -0,0 +1,21 @@ +import base32Decode from 'base32-decode'; +import base32Encode from 'base32-encode'; + +/** + * Coerce mistaken user input from 'problem' letters: + * https://en.wikipedia.org/wiki/Base32#Crockford.27s_Base32 + * + * @param {string} value User-provided text input + * + * @return {string} Encoded input + */ +export function encodeInput(value) { + value = value.replace(/-/g, ''); + value = base32Encode(base32Decode(value, 'Crockford'), 'Crockford'); + + // Add back the dashes + value = (value.match(/.{4}/g) || [value]).join('-'); + + // And uppercase + return value.toUpperCase(); +} diff --git a/app/javascript/packages/personal-key-input/index.spec.js b/app/javascript/packages/personal-key-input/index.spec.js new file mode 100644 index 00000000000..552cd52e419 --- /dev/null +++ b/app/javascript/packages/personal-key-input/index.spec.js @@ -0,0 +1,19 @@ +import { encodeInput } from './index.js'; + +describe('personal-key-input', () => { + describe('encodeInput', () => { + it('removes Crockford-encoded characters', () => { + expect(encodeInput('LlIi-1111-1111-1111')).to.equal('1111-1111-1111-1111'); + + expect(encodeInput('LlII-0000-0000-0000')).to.equal('1111-0000-0000-0000'); + + expect(encodeInput('1234-1234-1234-1234')).to.equal('1234-1234-1234-1234'); + + expect(encodeInput('7P41-1JFN-W7JA-DVR2')).to.equal('7P41-1JFN-W7JA-DVR2'); + + expect(encodeInput('')).to.be.a('string'); + + expect(encodeInput('abc')).to.be.a('string'); + }); + }); +}); diff --git a/app/javascript/packages/personal-key-input/package.json b/app/javascript/packages/personal-key-input/package.json new file mode 100644 index 00000000000..a98c03debac --- /dev/null +++ b/app/javascript/packages/personal-key-input/package.json @@ -0,0 +1,10 @@ +{ + "name": "@18f/identity-personal-key-input", + "private": true, + "version": "1.0.0", + "type": "module", + "dependencies": { + "base32-decode": "^1.0.0", + "base32-encode": "^1.1.1" + } +} diff --git a/app/javascript/packs/personal-key-page-controller.js b/app/javascript/packs/personal-key-page-controller.js index 1aa2ba32bb6..2200cbf7eab 100644 --- a/app/javascript/packs/personal-key-page-controller.js +++ b/app/javascript/packs/personal-key-page-controller.js @@ -1,9 +1,8 @@ -import base32Crockford from 'base32-crockford-browser'; +import { encodeInput } from '@18f/identity-personal-key-input'; const modalSelector = '#personal-key-confirm'; const modal = new window.LoginGov.Modal({ el: modalSelector }); -const personalKeyContainer = document.getElementById('personal-key'); const personalKeyWords = [].slice.call(document.querySelectorAll('[data-personal-key]')); const formEl = document.getElementById('confirm-key'); const input = formEl.querySelector('input[type="text"]'); @@ -47,19 +46,6 @@ function resetForm() { unsetInvalidHTML(); } -function formatInput(value) { - // Coerce mistaken user input from 'problem' letters: - // https://en.wikipedia.org/wiki/Base32#Crockford.27s_Base32 - value = base32Crockford.decode(value); - value = base32Crockford.encode(value); - - // Add back the dashes - value = value.toString().match(/.{4}/g).join('-'); - - // And uppercase - return value.toUpperCase(); -} - function handleSubmit(event) { event.preventDefault(); @@ -69,7 +55,7 @@ function handleSubmit(event) { return; } - const value = formatInput(input.value); + const value = encodeInput(input.value); if (value === personalKey) { unsetInvalidHTML(); @@ -88,7 +74,6 @@ function show(event) { modal.on('show', function () { input.focus(); - personalKeyContainer.classList.add('invisible'); }); modal.show(); @@ -97,7 +82,6 @@ function show(event) { function hide() { modal.on('hide', function () { resetForm(); - personalKeyContainer.classList.remove('invisible'); }); modal.hide(); diff --git a/package.json b/package.json index bb89f6de802..eb7bd5b7932 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "build": "true" }, "dependencies": { - "base32-crockford-browser": "^1.0.0", "basscss-sass": "^3.0.0", "classlist-polyfill": "^1.2.0", "cleave.js": "^1.5.3", diff --git a/spec/features/users/regenerate_personal_key_spec.rb b/spec/features/users/regenerate_personal_key_spec.rb index 9cfd7f4eaf4..44dcea2cb2d 100644 --- a/spec/features/users/regenerate_personal_key_spec.rb +++ b/spec/features/users/regenerate_personal_key_spec.rb @@ -72,8 +72,6 @@ end context 'with javascript enabled', js: true do - let(:invisible_selector) { generate_class_selector('invisible') } - it 'prompts the user to enter their personal key to confirm they have it' do sign_in_and_2fa_user(user) visit account_two_factor_authentication_path @@ -140,7 +138,6 @@ def sign_up_and_view_personal_key def expect_confirmation_modal_to_appear_with_first_code_field_in_focus expect(page).not_to have_xpath("//div[@id='personal-key-confirm'][@class='display-none']") - expect(page).not_to have_xpath("//#{invisible_selector}[@id='personal-key']") expect(page.evaluate_script('document.activeElement.name')).to eq 'personal_key' end diff --git a/yarn.lock b/yarn.lock index 64244413fa6..969dfc87dc6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1838,12 +1838,15 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base32-crockford-browser@^1.0.0: +base32-decode@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/base32-crockford-browser/-/base32-crockford-browser-1.0.0.tgz#3684970a5826ba1430f01e719cf923b00d773dd8" - integrity sha1-NoSXClgmuhQw8B5xnPkjsA13Pdg= - dependencies: - optimist ">=0.1.0" + resolved "https://registry.yarnpkg.com/base32-decode/-/base32-decode-1.0.0.tgz#2a821d6a664890c872f20aa9aca95a4b4b80e2a7" + integrity sha512-KNWUX/R7wKenwE/G/qFMzGScOgVntOmbE27vvc6GrniDGYb6a5+qWcuoXl8WIOQL7q0TpK7nZDm1Y04Yi3Yn5g== + +base32-encode@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/base32-encode/-/base32-encode-1.1.1.tgz#d022d86aca0002a751bbe1bf20eb4a9b1cef4e95" + integrity sha512-eqa0BeGghj3guezlasdHJhr3+J5ZbbQvxeprkcDMbRQrjlqOT832IUDT4Al4ofAwekFYMqkkM9KMUHs9Cu0HKA== base64-js@^1.0.2: version "1.3.1" @@ -5951,11 +5954,6 @@ minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= - minipass-collect@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" @@ -6469,14 +6467,6 @@ opn@^5.5.0: dependencies: is-wsl "^1.1.0" -optimist@>=0.1.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= - dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" - optimize-css-assets-webpack-plugin@^5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz#e2f1d4d94ad8c0af8967ebd7cf138dcb1ef14572" @@ -9742,11 +9732,6 @@ word-wrap@^1.2.3, word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= - worker-farm@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"