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
13 changes: 12 additions & 1 deletion app/controllers/sign_up/completions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,18 @@ def verified_at
def pii_to_displayable_attributes
{
full_name: full_name,
social_security_number: pii[:ssn],
social_security_number: render_to_string(
partial: 'shared/masked_text',
locals: {
text: SsnFormatter.format(pii[:ssn]),
masked_text: SsnFormatter.format_masked(pii[:ssn]),
accessible_masked_text: t(
'idv.accessible_labels.masked_ssn',
first_number: pii[:ssn][0],
last_number: pii[:ssn][-1],
),
},
),
address: address,
birthdate: dob,
phone: PhoneFormatter.format(pii[:phone].to_s),
Expand Down
28 changes: 28 additions & 0 deletions app/javascript/packages/masked-text-toggle/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class MaskedTextToggle {
Copy link
Copy Markdown
Contributor Author

@aduth aduth Aug 5, 2021

Choose a reason for hiding this comment

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

I'd kinda hoped to be able to skip JavaScript altogether for this and use a input:checked ~ .sibling CSS trick, but given the arrangement of the checkbox relative to (after) the masked text, it doesn't seem feasible without resorting to inaccessible options like order.

/**
* @param {HTMLInputElement} toggle
*/
constructor(toggle) {
this.elements = {
toggle,
texts: /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll(
`#${toggle.getAttribute('aria-controls')} .masked-text__text`,
)),
};
}

bind() {
this.elements.toggle.addEventListener('change', () => this.toggleTextVisibility());
this.toggleTextVisibility();
}

toggleTextVisibility() {
const { toggle, texts } = this.elements;
const isMasked = !toggle.checked;
texts.forEach((text) => {
text.classList.toggle('display-none', text.dataset.masked !== isMasked.toString());
});
}
}

export default MaskedTextToggle;
5 changes: 5 additions & 0 deletions app/javascript/packages/masked-text-toggle/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "@18f/identity-masked-text-toggle",
"private": true,
"version": "1.0.0"
}
4 changes: 4 additions & 0 deletions app/javascript/packs/masked-text-toggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import MaskedTextToggle from '@18f/identity-masked-text-toggle';

const wrappers = document.querySelectorAll('.masked-text__toggle');
wrappers.forEach((toggle) => new MaskedTextToggle(/** @type {HTMLInputElement} */ (toggle)).bind());
15 changes: 15 additions & 0 deletions app/services/ssn_formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module SsnFormatter
def self.format(ssn)
normalized_ssn = normalize(ssn)
"#{normalized_ssn[0..2]}-#{normalized_ssn[3..4]}-#{normalized_ssn[5..8]}"
end

def self.format_masked(ssn)
normalized_ssn = normalize(ssn)
"#{normalized_ssn[0]}**-**-***#{normalized_ssn[-1]}"
end

def self.normalize(ssn)
ssn.to_s.gsub(/\D/, '')[0..8]
end
end
27 changes: 14 additions & 13 deletions app/views/idv/doc_auth/verify.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,22 @@
class: 'usa-button usa-button--unstyled',
) %>
</div>
<%= t('doc_auth.forms.ssn') %>
<div class='sm-col sm-col-8 padding-x-0 margin-top-0 margin-bottom-4'>
<%= tag.input value: flow_session[:pii_from_doc][:ssn],
class: 'block col-12 field password ssn ssn-toggle bg-white',
aria: { label: t('doc_auth.forms.ssn'), invalid: false, required: false },
readonly: true,
maxlength: 11,
pattern: "^\d{3}-?\d{2}-?\d{4}$",
size: "11",
type: "password",
name: "doc_auth[ssn]",
id: "doc_auth_ssn" %>
<div>
<%= t('doc_auth.forms.ssn') %>:
<%= render(
'shared/masked_text',
text: SsnFormatter.format(flow_session[:pii_from_doc][:ssn]),
masked_text: SsnFormatter.format_masked(flow_session[:pii_from_doc][:ssn]),
accessible_masked_text: t(
'idv.accessible_labels.masked_ssn',
first_number: flow_session[:pii_from_doc][:ssn][0],
last_number: flow_session[:pii_from_doc][:ssn][-1],
),
toggle_label: t('forms.ssn.show'),
) %>
</div>

<div class="margin-top-6">
<div class="margin-top-5">
<%= render 'shared/spinner_button',
action_message: t('doc_auth.info.verifying'),
class: 'grid-col-12 tablet:grid-col-6' do %>
Expand Down
33 changes: 33 additions & 0 deletions app/views/shared/_masked_text.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<%#
locals:
* id: Optional field identifier.
* text: Original unmasked text.
* masked_text: Masked text.
* accessible_masked_text: A version of masked text appropriate for assistive technology.
* toggle_label: (Optional) Text to show on toggle. If not given, no toggle will be shown.
%>
<%
id = local_assigns.fetch(:id, "masked-text-#{SecureRandom.hex(6)}")
checkbox_id = "#{id}-checkbox"
%>
<%= tag.span(id: id) do %>
<span class="masked-text__text" data-masked="true">
<span class="usa-sr-only"><%= accessible_masked_text %></span>
<span aria-hidden="true" class="text-no-wrap"><%= masked_text %></span>
</span>
<span class="masked-text__text display-none" data-masked="false">
<%= text %>
</span>
<% end %>
<% if local_assigns[:toggle_label] %>
<div class="margin-top-2">
<%= tag.input(
type: 'checkbox',
id: checkbox_id,
aria: { controls: id },
class: 'masked-text__toggle usa-checkbox__input usa-checkbox__input--bordered',
) %>
<%= tag.label(toggle_label, for: checkbox_id, class: 'usa-checkbox__label') %>
</div>
<%= javascript_packs_tag_once 'masked-text-toggle' %>
<% end %>
8 changes: 4 additions & 4 deletions config/locales/doc_auth/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ en:
status_move_closer: Move camera closer to document
status_tap_to_capture: Automatic capture disabled
buttons:
change_address: change
change_ssn: change
change_address: Change
change_ssn: Change
continue: Continue
start_over: Start over
take_or_upload_picture: '<lg-take-photo>Take photo</lg-take-photo> or
Expand Down Expand Up @@ -134,15 +134,15 @@ en:
review_issues: Check your images and try again
secure_account: Secure your account
selfie: Take a photo of yourself
ssn: Please enter your Social Security number.
ssn: Enter your Social Security number
ssn_update: Update your Social Security number
take_picture: Take a photo with a phone
text_message: We sent a message to your phone
upload: How would you like to upload your state-issued ID?
upload_from_phone: Take a photo with a mobile phone to upload your ID
upload_from_phone_liveness_enabled: Use a mobile phone to add your ID and take a photo of yourself
upload_liveness_enabled: How would you like to verify your identity?
verify: Please verify your information
verify: Verify your information
verify_identity: Verify your identity
welcome: Verify your identity to securely access government services
info:
Expand Down
8 changes: 4 additions & 4 deletions config/locales/doc_auth/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ es:
status_move_closer: Acerca la cámara al documento
status_tap_to_capture: Captura automática desactivada
buttons:
change_address: cambio
change_ssn: cambio
change_address: Cambio
change_ssn: Cambio
continue: Continuar
start_over: Comenzar de nuevo
take_or_upload_picture: '<lg-take-photo>Toma una foto</lg-take-photo> o
Expand Down Expand Up @@ -160,7 +160,7 @@ es:
review_issues: Revise sus imágenes e inténtelo de nuevo
secure_account: Asegure su cuenta
selfie: Tómese una foto
ssn: Por favor ingrese su número de Seguro Social.
ssn: Ingresa tu número de Seguro Social
ssn_update: Actualice su número de Seguro Social
take_picture: Toma una foto con un teléfono
text_message: Enviamos un mensaje a su teléfono
Expand All @@ -169,7 +169,7 @@ es:
upload_from_phone: Tome una foto con un teléfono móvil para cargar su identificación
upload_from_phone_liveness_enabled: Utilice un celular para agregar su documento de identidad y tomarse
upload_liveness_enabled: '¿Cómo te gustaría verificar su identidad?'
verify: Por favor verifica tu información
verify: Verifica tus datos
verify_identity: Verifique su identidad
welcome: Verifique su identidad para acceder de forma segura a los servicios
gubernamentales
Expand Down
8 changes: 4 additions & 4 deletions config/locales/doc_auth/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ fr:
status_move_closer: Rapprochez l'appareil photo du document
status_tap_to_capture: Capture automatique désactivée
buttons:
change_address: changement
change_ssn: changement
change_address: Changement
change_ssn: Changement
continue: Continuer
start_over: Recommencer
take_or_upload_picture: '<lg-take-photo>Prendre une photo</lg-take-photo> ou
Expand Down Expand Up @@ -165,7 +165,7 @@ fr:
review_issues: Vérifiez vos images et essayez à nouveau
secure_account: Sécuriser votre compte
selfie: Prenez une photo de vous-même
ssn: S’il vous plaît entrez votre numéro de Sécurité Sociale.
ssn: Saisissez votre numéro de sécurité sociale
ssn_update: Mettre à jour votre numéro de Sécurité Sociale
take_picture: Prendre une photo avec un téléphone
text_message: Nous avons envoyé un message à votre téléphone
Expand All @@ -176,7 +176,7 @@ fr:
upload_from_phone_liveness_enabled: Utilisez un téléphone portable pour ajouter
votre pièce d’identité et prendre une photo de vous-même
upload_liveness_enabled: Comment souhaitez-vous vérifier votre identité?
verify: S’il vous plaît vérifier vos informations
verify: Vérifier votre information
verify_identity: Vérifier votre identité
welcome: Vérifiez votre identité pour accéder en toute sécurité aux services
gouvernementaux
Expand Down
2 changes: 2 additions & 0 deletions config/locales/idv/en.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
en:
idv:
accessible_labels:
masked_ssn: secure text, starting with %{first_number} and ending with %{last_number}
buttons:
cancel: Cancel and return to your profile
continue_plain: Continue
Expand Down
3 changes: 3 additions & 0 deletions config/locales/idv/es.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
---
es:
idv:
accessible_labels:
masked_ssn: texto seguro, comenzando con %{first_number} y terminando con
%{last_number}
buttons:
cancel: Cancele y regrese a su perfil
continue_plain: Continuar
Expand Down
3 changes: 3 additions & 0 deletions config/locales/idv/fr.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
---
fr:
idv:
accessible_labels:
masked_ssn: Texte sécurisé, commençant par %{first_number} et finissant par
%{last_number}
buttons:
cancel: Annuler et retourner à votre profil
continue_plain: Continuer
Expand Down
6 changes: 3 additions & 3 deletions spec/controllers/sign_up/completions_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
let(:user) do
create(:user, profiles: [create(:profile, :verified, :active)])
end
let(:pii) { {} }
let(:pii) { { ssn: '123456789' } }

before do
stub_sign_in(user)
Expand All @@ -48,7 +48,7 @@
end

context 'with american-style birthday data' do
let(:pii) { { dob: '12/31/1970' } }
let(:pii) { { ssn: '123456789', dob: '12/31/1970' } }

it 'renders data' do
get :show
Expand All @@ -57,7 +57,7 @@
end

context 'with international style birthday data' do
let(:pii) { { dob: '1970-01-01' } }
let(:pii) { { ssn: '123456789', dob: '1970-01-01' } }

it 'renders data' do
get :show
Expand Down
26 changes: 17 additions & 9 deletions spec/features/idv/doc_auth/verify_step_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,10 @@
expect(page).to have_content(t('doc_auth.headings.verify'))
end

it 'can toggle viewing the ssn' do
input_with_ssn_value = "input[value='666-66-1234']"
expect(page).to have_selector(input_with_ssn_value), visible: false

find('input.ssn-toggle').click
expect(page).to have_selector(input_with_ssn_value), visible: true

find('input.ssn-toggle').click
expect(page).to have_selector(input_with_ssn_value), visible: false
it 'masks the ssn' do
expect(page).to have_text('6**-**-***4')
expect(page.find('.masked-text__text', text: '666-66-1234')).
to match_css('.display-none').or have_ancestor('.display-none')
end

it 'proceeds to the next page upon confirmation' do
Expand Down Expand Up @@ -268,6 +263,19 @@
end
end

it 'can toggle viewing the ssn' do
expect(page).to have_text('6**-**-***4')
expect(page).not_to have_text('666-66-1234')

check t('forms.ssn.show'), allow_label_click: true
expect(page).to have_text('666-66-1234')
expect(page).not_to have_text('6**-**-***4')

uncheck t('forms.ssn.show'), allow_label_click: true
expect(page).to have_text('6**-**-***4')
expect(page).not_to have_text('666-66-1234')
end

it 'proceeds to the next page upon confirmation' do
click_idv_continue

Expand Down
4 changes: 2 additions & 2 deletions spec/features/users/sign_in_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@
click_submit_default

expect(current_path).to eq sign_up_completed_path
expect(page).to have_content('111223333')
expect(page).to have_content('1**-**-***3')

click_agree_and_continue

Expand Down Expand Up @@ -858,7 +858,7 @@
click_submit_default

expect(current_path).to eq sign_up_completed_path
expect(page).to have_content('111223333')
expect(page).to have_content('1**-**-***3')

click_agree_and_continue

Expand Down
Loading