Skip to content

Commit

Permalink
mask all ssn fields (#4279)
Browse files Browse the repository at this point in the history
* complete initial pass on new ssn field reqs

* add slight modification to new family policy method

* add event listener for accessibility

* complete spec for new endpoints

* remove unnecessary changes from enroll system config file

* adds ssn masks to my household section

* bearer failure

* bearer failure

* bearer failure

* fix failing spec in family policy

* remove console.logs

* make env var true on failing spec

* bearer failure

* readd bearer fix

* remove excessive line breaks

* remove unnecessary helper_method in family_members controller

* complete first pass at applicant ssn field masking

* remove ssn display in all admin tables

* making brief switch to update broker permissions

* complete initial pass on new ssn field reqs

* add event listener for accessibility

* remove unnecessary changes from enroll system config file

* adds ssn masks to my household section

* bearer failure

* bearer failure

* bearer failure

* bearer failure

* readd bearer fix

* remove excessive line breaks

* remove unnecessary helper_method in family_members controller

* complete first pass at applicant ssn field masking

* add addtl permissions and update specs

* remove unnecessary line break

* finalize permission details

* complete first iteration of ssn fields

* remove changes for testing drop enrollment members

* remove comment

* reformat explanatory comment

* remove unused attrs from ssn_form_presenter

* update dependent form input

* remove unused id from edit enrollment partial

* plan shopping options page fixes (#4274)

plan shopping options fixes

* dont have secondary modal for dependent table (#4273)

* don't open secondary modal when in comparison modal

* forgot to commit a file

* rubocop

* fix spec by undoing rubocop suggestions

* enrollment submitted logic fix

* more aptc logic

* aptc calculator adjust amount applied (#4286)

adjust the max amount of aptc to account for if the aptc is higher than the premium

* aptc calc results should have round values (#4288)

* fix rounding issue

* fix rounding

* use default cancel translation sting (#4290)

* fix issue with failing cucumber

* remove console log in coffee file

* remove family_helper changes

* remove changes with unemployment form

* add translations and update w/feedback from comments

* remove extra row column in table

* add admin css file

* small changes for rubocop

* reversed logic on column list

---------

Co-authored-by: Kristin Merbach <[email protected]>
Co-authored-by: kristinmerbach <[email protected]>
  • Loading branch information
3 people authored and charlienparker committed Sep 13, 2024
1 parent 5262b90 commit d345140
Show file tree
Hide file tree
Showing 26 changed files with 397 additions and 95 deletions.
11 changes: 10 additions & 1 deletion app/controllers/insured/consumer_roles_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ def update
mec_check(@person.hbx_id) if EnrollRegistry.feature_enabled?(:mec_check) && @person.send(:mec_check_eligible?)
@shop_coverage_result = EnrollRegistry.feature_enabled?(:shop_coverage_check) ? (check_shop_coverage.success? && check_shop_coverage.success.present?) : nil
@consumer_role.skip_consumer_role_callbacks = true
valid_params = {"skip_person_updated_event_callback" => true, "skip_lawful_presence_determination_callbacks" => true}.merge(params.require(:person).permit(*person_parameters_list))
valid_params = {"skip_person_updated_event_callback" => true, "skip_lawful_presence_determination_callbacks" => true}.merge(params.require(:person).permit(*existing_personal_parameters_list))

if update_vlp_documents(@consumer_role, 'person') && @consumer_role.update_by_person(valid_params)
@consumer_role.update_attribute(:is_applying_coverage, params[:person][:is_applying_coverage]) unless params[:person][:is_applying_coverage].nil?
Expand Down Expand Up @@ -473,6 +473,15 @@ def sanitize_contact_method
params.dig("person", "consumer_role_attributes").merge!("contact_method" => ConsumerRole::CONTACT_METHOD_MAPPING[contact_method])
end

def existing_personal_parameters_list
if EnrollRegistry.feature_enabled?(:mask_ssn_ui_fields)
# NOTE: with this update, the only place ssn/dob/no_ssn can be updated is the edit ssn dob feature, which is restricted to admin
person_parameters_list - [:dob, :ssn, :no_ssn]
else
person_parameters_list
end
end

def person_parameters_list
[
{ :addresses_attributes => [:kind, :address_1, :address_2, :city, :state, :zip, :county, :id, :_destroy] },
Expand Down
22 changes: 20 additions & 2 deletions app/controllers/insured/family_members_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,24 @@ def show
end
end

# NOTE: ensure that current user is either
# 1.) hbx admin, or
# 2.) the current user is the primary for the family AND the subject is a member of that family
def show_ssn
@family = Family.find(params[:family_id]) if params[:family_id]
authorize @family, :can_show_ssn?
@person = Person.find(params[:id]) unless @person.id == params[:id]

if @family.person_is_family_member?(@person)
payload = number_to_ssn(@person.ssn)
render json: { payload: payload, status: 200 }
else
render json: { message: "Unauthorized" }, status: 401
end
rescue Pundit::NotAuthorizedError, Mongoid::Errors::DocumentNotFound
render json: { message: "Unauthorized" }, status: 401
end

def edit
authorize @family, :edit?

Expand Down Expand Up @@ -301,8 +319,8 @@ def set_type_role_and_family
end
@family = @person.primary_family
when 'consumer'
@consumer_role = @person.consumer_role
@family = @consumer_role.person.primary_family
@consumer_role = @person&.consumer_role
@family = @consumer_role&.person&.primary_family
end

@family = Family.find(params[:family_id]) if params[:family_id]
Expand Down
13 changes: 12 additions & 1 deletion app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,18 @@ def number_to_ssn(number)
def number_to_obscured_ssn(number)
return unless number
number_to_ssn(number)
number.to_s.gsub!(/\w{3}-\w{2}/, '***-**')
if EnrollRegistry.feature_enabled?(:mask_ssn_ui_fields)
'●●●●●●●●●'
else
number.to_s.gsub!(/\w{3}-\w{2}/, '***-**')
end
end

def organize_ssn_params(form_object, family_member_id = nil)
return unless form_object.ssn

presenter = ::Presenters::SsnFormPresenter.new(form_object, family_member_id)
presenter.sanitize_ssn_params
end

# Formats a number into a nine-digit US Federal Entity Identification Number string (nn-nnnnnnn)
Expand Down
31 changes: 16 additions & 15 deletions app/javascript/controllers/person_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@ import { Controller } from "stimulus"
import axios from 'axios'

export default class extends Controller {
connect() {
document.addEventListener("keydown", this.handleKeyDown);
}

disconnect() {
document.removeEventListener("keydown", this.handleKeyDown);
}

handleKeyDown(event) {
event.preventDefault();
if (event.key === 'Enter' || event.key === ' ') {
event.target.click();
}
}

showSsn(event) {
event.stopImmediatePropagation();
let target = event.target;
Expand Down Expand Up @@ -38,26 +53,13 @@ export default class extends Controller {
populateHtmlElement(personId, payload) {
let ssnInputElement = document.querySelector(`.ssn-input-${personId}`);

if (ssnInputElement.tagName === 'INPUT') {
if (ssnInputElement.tagName === 'Input') {
ssnInputElement.value = payload;
ssnInputElement.setAttribute('value', payload);
} else {
ssnInputElement.textContent = payload;
}
}

depopulateHtmlElement(personId) {
if (personId == 'temp') return;
let ssnInputElement = document.querySelector(`.ssn-input-${personId}`);

if (ssnInputElement.tagName === 'INPUT') {
ssnInputElement.value = '';
ssnInputElement.setAttribute('value', '');
} else {
ssnInputElement.textContent = '';
}
}

hideSsn(event) {
const target = event.target;
const personId = target.getAttribute('data-id');
Expand All @@ -68,7 +70,6 @@ export default class extends Controller {
document.querySelector(`.ssn-eye-off-${personId}`).classList.remove('hidden');

document.querySelector(`.ssn-eye-off-${personId}`).focus();
this.depopulateHtmlElement(personId);
const ssnInput = document.querySelector(`.ssn-input-${personId}`);
if (ssnInput.getAttribute('data-admin-can-enable') !== null) {
ssnInput.disabled = true;
Expand Down
49 changes: 47 additions & 2 deletions app/javascript/css/forms.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
#personal_info {
input:not([type='checkbox']):not([type='radio']):not(.ssn-input),
select,
.input {
width: 180px;
}

.no-ssn-container {
width: 180px;

label {
font-weight: normal;
}
}
}

#new_user {
#pass_strength,
#pass_match {
Expand Down Expand Up @@ -69,7 +85,8 @@ dt.required:after,

input:not([type='submit']),
select,
.input {
.input,
#ssn-input-container {
box-sizing: border-box;
height: 36px;
padding: 6px 8px;
Expand All @@ -90,9 +107,37 @@ select,
}
}

input.fake-disabled-input:not([type='submit']):disabled {
#ssn-input-container {
width: 180px;
display: flex;
align-items: center;
justify-content: space-between;

.ssn-input {
padding: 0px;
border: unset;
width: 120px;
height: 34px;

&:hover {
border-color: unset;
}
}

img {
cursor: pointer;
}
}

input.fake-disabled-input:not([type='submit']):disabled,
#ssn-input-container.div-disabled {
background: var(--grey-030);
color: var(--default-font-color);

.ssn-input {
background: inherit;
color: inherit;
}
}

input[type='checkbox'],
Expand Down
20 changes: 0 additions & 20 deletions app/javascript/css/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -178,26 +178,6 @@ dl.parent.weight-n {
}
}

.tax_credit_field_container {
height: 44px !important;
}

#personal_info {
input:not([type='checkbox']):not([type='radio']),
select,
.input {
width: 180px;
}

.no-ssn-container {
width: 180px;

label {
font-weight: normal;
}
}
}

.ethnicity-container {
label {
font-weight: 400;
Expand Down
8 changes: 1 addition & 7 deletions app/models/presenters/ssn_form_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ def sanitize_new_consumer
obscure_ssn

@person_id = 'temp'

# enable users to change ssn on sign up if errors present
@disabled = candidate_ssn_field_disabled?
@disabled = true
end

def sanitize_person
Expand Down Expand Up @@ -82,9 +80,5 @@ def obscure_ssn(subject = @form_object)
clone_ssn = subject.ssn.dup
@obscured_ssn = number_to_obscured_ssn(clone_ssn)
end

def candidate_ssn_field_disabled?
!(@form_object.errors[:base].any? || @form_object.errors[:ssn_taken].any?)
end
end
end
2 changes: 1 addition & 1 deletion app/views/datatables/_drop_enrollment_member.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<td><%= person.full_name %></td>
<td>
<% if EnrollRegistry.feature_enabled?(:mask_ssn_ui_fields) %>
<%= render partial: 'shared/person/ssn/admin_ssn_block',
<%= render partial: 'shared/person/admin_ssn_block',
locals: { presenter: organize_ssn_params(person, member.family.id), include_label: false }
%>
<% else %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/exchanges/hbx_profiles/_edit_enrollment.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</div>
<div class="col-md-4 col-sm-4 col-xs-12 form-group form-group-lg no-pd">
<% if EnrollRegistry.feature_enabled?(:mask_ssn_ui_fields) %>
<%= render partial: 'shared/person/ssn/admin_ssn_field',
<%= render partial: 'shared/person/admin_ssn_field',
locals: { f: f, presenter: organize_ssn_params(f.object) }
%>
<% else %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<td><h5> <b>DOB:</b> <%=@person.dob%> </h5></td>
<td>
<% if EnrollRegistry.feature_enabled?(:mask_ssn_ui_fields) %>
<%= render partial: 'shared/person/ssn/admin_ssn_block',
<%= render partial: 'shared/person/admin_ssn_block',
locals: { presenter: organize_ssn_params(@person), include_label: true }
%>
<% else %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/hbx_admin/_demographics_info.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<td><%=@person.dob%></td>
<td>
<% if EnrollRegistry.feature_enabled?(:mask_ssn_ui_fields) %>
<%= render partial: 'shared/person/ssn/admin_ssn_block',
<%= render partial: 'shared/person/admin_ssn_block',
locals: { presenter: organize_ssn_params(@person, @family.id), include_label: false }
%>
<% else %>
Expand Down
16 changes: 11 additions & 5 deletions app/views/insured/family_members/_dependent_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,18 @@
<div class="d-flex mb-md-4 <%= 'new_dependent' unless is_editing %>">
<div class="col-md-6 col-lg-3 p-0">
<%= f.label :ssn, "Social Security" %>
<% if EnrollRegistry.feature_enabled?(:ssn_ui_validation) %>
<%= f.text_field :ssn, placeholder: "000-00-0000", class: "required keep-label mask-ssn",
pattern: "(?!666|000|9\\d{2})\\d{3}[\\- ]{0,1}(?!00)\\d{2}[\\- ]{0,1}(?!0{4})\\d{4}", oninvalid: "this.setCustomValidity('Invalid Social Security number.')",
oninput: "this.setCustomValidity('')" %>
<% if EnrollRegistry.feature_enabled?(:mask_ssn_ui_fields) %>
<%= render partial: 'shared/person/consumer_ssn_field',
locals: { f: f, presenter: organize_ssn_params(f.object, @person.primary_family.id) }
%>
<% else %>
<%= f.text_field :ssn, placeholder: "000-00-0000", class: "required" %>
<% if EnrollRegistry.feature_enabled?(:ssn_ui_validation) %>
<%= f.text_field :ssn, placeholder: "000-00-0000", class: "required keep-label mask-ssn",
pattern: "(?!666|000|9\\d{2})\\d{3}[\\- ]{0,1}(?!00)\\d{2}[\\- ]{0,1}(?!0{4})\\d{4}", oninvalid: "this.setCustomValidity('Invalid Social Security number.')",
oninput: "this.setCustomValidity('')", disabled: readonly %>
<% else %>
<%= f.text_field :ssn, placeholder: "000-00-0000", class: "required", disabled: readonly %>
<% end %>
<% end %>
</div>
<div class="col-md-6 col-lg-3 p-0 pt-md-4 no-ssn-container mr-auto">
Expand Down
18 changes: 12 additions & 6 deletions app/views/people/landing_pages/_personal.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,19 @@
<div class="d-flex mb-md-4 row col-sm">
<div class="mr-auto col-sm col-md-6 col-lg-3 p-0">
<%= f.label :ssn, "Social Security" %>
<% if EnrollRegistry.feature_enabled?(:ssn_ui_validation) %>
<%= f.text_field :ssn, placeholder: "000-00-0000", class: "required keep-label disabled",
pattern: "(?!666|000|9\\d{2})\\d{3}[\\- ]{0,1}(?!00)\\d{2}[\\- ]{0,1}(?!0{4})\\d{4}", oninvalid: "this.setCustomValidity('Invalid Social Security number.')",
oninput: "this.setCustomValidity('')", disabled: true, readonly: f.object.is_a?(Forms::EmployeeRole) || f.object.is_a?(Person) %>
<% if EnrollRegistry.feature_enabled?(:mask_ssn_ui_fields) %>
<%= render partial: 'shared/person/consumer_ssn_field',
locals: { f: f, presenter: organize_ssn_params(f.object) }
%>
<% else %>
<%= f.text_field :ssn, placeholder: "000-00-0000", class: "required", disabled: true,
readonly: f.object.is_a?(Forms::EmployeeRole) %>
<% if EnrollRegistry.feature_enabled?(:ssn_ui_validation) %>
<%= f.text_field :ssn, placeholder: "000-00-0000", class: "required keep-label",
pattern: "(?!666|000|9\\d{2})\\d{3}[\\- ]{0,1}(?!00)\\d{2}[\\- ]{0,1}(?!0{4})\\d{4}", oninvalid: "this.setCustomValidity('Invalid Social Security number.')",
oninput: "this.setCustomValidity('')", disabled: true, readonly: f.object.is_a?(Forms::EmployeeRole) %>
<% else %>
<%= f.text_field :ssn, placeholder: "000-00-0000", class: "required", disabled: true,
readonly: f.object.is_a?(Forms::EmployeeRole) %>
<% end %>
<% end %>
</div>
<div class="col-sm col-md-6 col-lg-3 p-0 pt-md-4 no-ssn-container mr-auto">
Expand Down
34 changes: 34 additions & 0 deletions app/views/shared/person/_admin_ssn_block.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

<% if presenter&.obscured_ssn&.size %>
<div data-controller='person' id='admin-ssn-input-field' class='admin-ssn-block d-flex align-items-center justify-content-start'>
<% if include_label %>
<h5 class='mr-1'><b><%= l10n('ssn') %>: </b></h5>
<% end %>
<span class="hidden ssn-input ssn-input-<%= presenter.person_id %>"></span>
<span class="ssn-facade ssn-facade-<%= presenter.person_id %> font-weight-bold">• • • • • • • • •</span>
<%= image_pack_tag 'icons/eye-off.svg',
class: "my-auto ssn-eye-off ssn-eye-off-#{presenter.person_id}",
alt: "view ssn icon",
tabindex: 0,
data: {
remote: true,
type: presenter.object_type,
id: presenter.person_id,
family_id: presenter.family_id,
action: "click->person#showSsn"
}
%>
<%= image_pack_tag "icons/eye-on.svg",
class: "hidden ssn-eye-on ssn-eye-on-#{presenter.person_id}",
alt: "hide ssn icon",
tabindex: 0,
remote: true,
data: {
id: presenter.person_id,
action: "click->person#hideSsn"
}
%>
</div>
<% elsif include_label %>
<h5><b>SSN:</b></h5>
<% end %>
Loading

0 comments on commit d345140

Please sign in to comment.