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
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,17 @@ describe('PersonalKeyInput', () => {
expect(input.value).to.equal('1234-1234-1234-1234');
});

it('validates the input value against the expected value', async () => {
const { getByRole } = render(<PersonalKeyInput expectedValue="0000-0000-0000-0000" />);
it('validates the input value against the expected value (case-insensitive, crockford)', async () => {
const { getByRole } = render(<PersonalKeyInput expectedValue="abcd-0011-DEFG-1111" />);

const input = getByRole('textbox') as HTMLInputElement;

await userEvent.type(input, '0000-0000-0000-000');
await userEvent.type(input, 'ABCDoOlL-defg-iI1');
input.checkValidity();
expect(input.validationMessage).to.equal('users.personal_key.confirmation_error');

await userEvent.type(input, '0');
await userEvent.type(input, '1');
input.checkValidity();
expect(input.validationMessage).to.be.empty();
expect(input.validity.valid).to.be.true();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,22 @@ interface PersonalKeyInputProps {
onChange?: (nextValue: string) => void;
}

/**
* Normalize an input value for validation comparison.
*
* @param string Denormalized value.
*
* @return Normalized value.
*/
const normalize = (string: string) => string.toLowerCase().replace(/o/g, '0').replace(/[il]/g, '1');
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.

yeah it's silly to have a whole library that we can replicate with two regexes, this seems easy enough to maintain 👍


function PersonalKeyInput(
{ expectedValue, onChange = () => {} }: PersonalKeyInputProps,
ref: ForwardedRef<HTMLElement>,
) {
const validate = useCallback<ValidatedFieldValidator>(
(value) => {
if (expectedValue && value !== expectedValue) {
if (expectedValue && normalize(value) !== normalize(expectedValue)) {
throw new Error(t('users.personal_key.confirmation_error'));
}
},
Expand Down
6 changes: 1 addition & 5 deletions spec/support/features/personal_key_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ def trigger_reset_password_and_click_email_link(email)
end

def scrape_personal_key
new_personal_key_words = []
page.all(:css, '[data-personal-key]').each do |node|
new_personal_key_words << node.text
end
new_personal_key_words.join('-')
page.all(:css, '.separator-text__code').map(&:text).join('-')
end
end
11 changes: 3 additions & 8 deletions spec/support/features/session_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

module Features
module SessionHelper
include PersonalKeyHelper

VALID_PASSWORD = 'Val!d Pass w0rd'.freeze

def sign_up_with(email)
Expand Down Expand Up @@ -307,18 +309,11 @@ def sign_in_with_totp_enabled_user
end

def acknowledge_and_confirm_personal_key(js: true)
extra_characters_get_ignored = 'abc123qwerty'
code_words = []

page.all(:css, '[data-personal-key]').map do |node|
code_words << node.text
end

button_text = t('forms.buttons.continue')

click_on button_text, class: 'personal-key-continue' if js

fill_in 'personal_key', with: code_words.join.downcase + extra_characters_get_ignored
fill_in 'personal_key', with: scrape_personal_key

find_all('.personal-key-confirm', text: button_text).first.click
end
Expand Down
25 changes: 24 additions & 1 deletion spec/support/shared_examples_for_personal_keys.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
shared_examples_for 'personal key page' do
include XPathHelper
include PersonalKeyHelper

context 'informational text' do
context 'modal content' do
Expand Down Expand Up @@ -33,5 +33,28 @@
code = page.all('[data-personal-key]').map(&:text).join('-')
expect(copied_text).to eq(code)
end

it 'validates as case-insensitive, crockford-normalized, length-limited, dash-flexible' do
code_segments = scrape_personal_key.split('-')

# Include dash between some segments and not others
code = code_segments[0..1].join('-') + code_segments[2..3].join

# Randomize case
code = code.chars.map { |c| (rand 2) == 0 ? c.downcase : c.upcase }.join

# De-normalize Crockford encoding
code = code.sub('1', 'l').sub('0', 'O')

# Add extra characters
code += 'abc123qwerty'

click_acknowledge_personal_key
page.find(':focus').fill_in with: code

path_before_submit = current_path
within('[role=dialog]') { click_on t('forms.buttons.continue') }
expect(current_path).not_to eq path_before_submit
end
end
end