+ <% t('in_person_proofing.body.state_id.id_types').each do | id_type | %>
+
+ <%= id_type %>
+
+ <% end %>
+
+<% end %>
+
<%= simple_form_for :doc_auth,
url: url_for,
method: 'put',
diff --git a/app/views/idv/personal_key/show.html.erb b/app/views/idv/personal_key/show.html.erb
index d68ae996442..26ecfd4c694 100644
--- a/app/views/idv/personal_key/show.html.erb
+++ b/app/views/idv/personal_key/show.html.erb
@@ -9,4 +9,8 @@
<% title t('titles.idv.personal_key') %>
-<%= render 'shared/personal_key', code: @code, update_path: idv_personal_key_path %>
+<%= render 'shared/personal_key',
+ code: @code,
+ personal_key_generated_at: @personal_key_generated_at,
+ update_path: idv_personal_key_path
+%>
diff --git a/app/views/idv/ssn/show.html.erb b/app/views/idv/ssn/show.html.erb
new file mode 100644
index 00000000000..f744c8ccedb
--- /dev/null
+++ b/app/views/idv/ssn/show.html.erb
@@ -0,0 +1,91 @@
+<%#
+Renders a page asking the user to enter their SSN or update their SSN if they had previously entered it.
+
+locals:
+* success_alert_enabled: whether or not to display a "We've successfully verified your ID" success alert
+* updating_ssn: true if the user is updating their SSN instead of providing it for the first time. This
+ will render a different page heading and different navigation buttons in the page footer
+%>
+<% content_for(:pre_flash_content) do %>
+ <%= render StepIndicatorComponent.new(
+ steps: Idv::Flows::DocAuthFlow::STEP_INDICATOR_STEPS,
+ current_step: :verify_info,
+ locale_scope: 'idv',
+ class: 'margin-x-neg-2 margin-top-neg-4 tablet:margin-x-neg-6 tablet:margin-top-neg-4',
+ ) %>
+<% end %>
+
+<% title t('titles.doc_auth.ssn') %>
+
+<% if success_alert_enabled %>
+ <%= render AlertComponent.new(
+ type: :success,
+ class: 'margin-bottom-4',
+ ) do %>
+ <%= t('doc_auth.headings.capture_complete') %>
+ <% end %>
+<% end %>
+
+<% if updating_ssn %>
+ <%= render PageHeadingComponent.new.with_content(t('doc_auth.headings.ssn_update')) %>
+<% else %>
+ <%= render PageHeadingComponent.new.with_content(t('doc_auth.headings.ssn')) %>
+<% end %>
+
+
<%= render AccordionComponent.new do |c| %>
diff --git a/app/views/users/personal_keys/show.html.erb b/app/views/users/personal_keys/show.html.erb
index f443fd7b135..3e5e066920f 100644
--- a/app/views/users/personal_keys/show.html.erb
+++ b/app/views/users/personal_keys/show.html.erb
@@ -1,3 +1,9 @@
<% title t('titles.personal_key') %>
-<%= render('shared/personal_key', code: @code, update_path: manage_personal_key_path) %>
+<%= render(
+ 'shared/personal_key',
+ code: @code,
+ personal_key_generated_at: @personal_key_generated_at,
+ update_path: manage_personal_key_path,
+ )
+%>
diff --git a/config/application.yml.default b/config/application.yml.default
index 39df9509e4d..90352964ee5 100644
--- a/config/application.yml.default
+++ b/config/application.yml.default
@@ -70,6 +70,7 @@ country_phone_number_overrides: '{}'
doc_auth_error_dpi_threshold: 290
doc_auth_error_sharpness_threshold: 40
doc_auth_error_glare_threshold: 40
+doc_auth_ssn_controller_enabled: false
database_pool_extra_connections_for_worker: 4
database_pool_idp: 5
database_statement_timeout: 2_500
diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml
index 66358817487..e26ef019972 100644
--- a/config/locales/idv/en.yml
+++ b/config/locales/idv/en.yml
@@ -121,6 +121,8 @@ en:
ssn_label_html: Social Security number
state: State
zipcode: ZIP Code
+ images:
+ come_back_later: Letter with a check mark
index:
id:
need_html: If you’re creating an account, you’ll need a current
diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml
index bce914c90df..f551a873284 100644
--- a/config/locales/idv/es.yml
+++ b/config/locales/idv/es.yml
@@ -130,6 +130,8 @@ es:
ssn_label_html: Número de Seguro Social
state: Estado
zipcode: Código postal
+ images:
+ come_back_later: Carta con una marca de verificación
index:
id:
need_html: Si está creando una cuenta, necesitará una identificación
diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml
index e07e6ccaaf1..603f3a911c0 100644
--- a/config/locales/idv/fr.yml
+++ b/config/locales/idv/fr.yml
@@ -135,6 +135,8 @@ fr:
ssn_label_html: Numéro de sécurité sociale
state: État
zipcode: Code postal
+ images:
+ come_back_later: Lettre avec un crochet
index:
id:
need_html: Si vous créez un compte, vous aurez besoin d’un identifiant
diff --git a/config/locales/in_person_proofing/en.yml b/config/locales/in_person_proofing/en.yml
index 38298e735c1..9342ce86453 100644
--- a/config/locales/in_person_proofing/en.yml
+++ b/config/locales/in_person_proofing/en.yml
@@ -80,6 +80,10 @@ en:
verify_step_enter_pii: Enter your name, date of birth, state-issued ID number,
address and Social Security number.
state_id:
+ alert_message: 'Your state-issued ID must not be expired. Accepted forms of ID are:'
+ id_types:
+ - State Driver’s License
+ - State Non-Driver’s Identification Card
info_html: Enter information exactly as it appears on your state-issued
ID. We will use this information to confirm it matches your
ID in person.
diff --git a/config/locales/in_person_proofing/es.yml b/config/locales/in_person_proofing/es.yml
index 628ae3d5c99..21f038496cb 100644
--- a/config/locales/in_person_proofing/es.yml
+++ b/config/locales/in_person_proofing/es.yml
@@ -91,6 +91,11 @@ es:
identificación emitido por el estado, dirección y número de la
Seguridad Social.
state_id:
+ alert_message: 'Su identificación emitida por el estado no debe estar vencida.
+ Se aceptan las siguientes formas de identificación:'
+ id_types:
+ - Licencia para conducir estatal
+ - Identificación estatal que no sea la licencia para conducir
info_html: Ingrese la información exactamente como aparece en su cédula
de identidad emitida por el estado. Utilizaremos esta
información para confirmar que coincide con su cédula en persona.
diff --git a/config/locales/in_person_proofing/fr.yml b/config/locales/in_person_proofing/fr.yml
index a7c7094671e..4a6b33fc8a3 100644
--- a/config/locales/in_person_proofing/fr.yml
+++ b/config/locales/in_person_proofing/fr.yml
@@ -92,6 +92,11 @@ fr:
document d’identité délivré par l’État, votre adresse et votre numéro
de sécurité sociale.
state_id:
+ alert_message: 'Votre carte d’identité délivrée par l’État ne doit pas être
+ périmée. Les pièces d’identité acceptées sont:'
+ id_types:
+ - Permis de conduire national
+ - Carte d’identité nationale de non-conducteur
info_html: Saisissez les informations exactement comme elles figurent
sur votre document d’identité nationale. Nous utiliserons ces
informations pour confirmer qu’elles correspondent à votre pièce
diff --git a/config/routes.rb b/config/routes.rb
index 497774d2ffb..8f31eb5ac83 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -308,6 +308,8 @@
post '/forgot_password' => 'forgot_password#update'
get '/otp_delivery_method' => 'otp_delivery_method#new'
put '/otp_delivery_method' => 'otp_delivery_method#create'
+ get '/ssn' => 'ssn#show'
+ put '/ssn' => 'ssn#update'
get '/verify_info' => 'verify_info#show'
put '/verify_info' => 'verify_info#update'
get '/phone' => 'phone#new'
diff --git a/lib/identity_config.rb b/lib/identity_config.rb
index b206c48da18..ebd4f974ec1 100644
--- a/lib/identity_config.rb
+++ b/lib/identity_config.rb
@@ -166,6 +166,7 @@ def self.build_store(config_map)
config.add(:doc_auth_max_submission_attempts_before_native_camera, type: :integer)
config.add(:doc_auth_max_capture_attempts_before_tips, type: :integer)
config.add(:doc_auth_s3_request_timeout, type: :integer)
+ config.add(:doc_auth_ssn_controller_enabled, type: :boolean)
config.add(:doc_auth_vendor, type: :string)
config.add(:doc_auth_vendor_randomize, type: :boolean)
config.add(:doc_auth_vendor_randomize_percent, type: :integer)
diff --git a/package.json b/package.json
index 4fe4fd52077..e2df37ddbaa 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,7 @@
"fast-glob": "^3.2.7",
"foundation-emails": "^2.3.1",
"identity-style-guide": "^6.7.0",
- "intl-tel-input": "^17.0.8",
+ "intl-tel-input": "^17.0.19",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"source-map-loader": "^4.0.0",
diff --git a/spec/components/base_component_spec.rb b/spec/components/base_component_spec.rb
index 32e9ddfc70d..052bac13ead 100644
--- a/spec/components/base_component_spec.rb
+++ b/spec/components/base_component_spec.rb
@@ -26,7 +26,7 @@ def call
''
end
- def self._sidecar_files(extensions)
+ def self.sidecar_files(extensions)
files = []
files << '/components/example_component_with_script_js.js' if extensions.include?('js')
files << '/components/example_component_with_script_ts.ts' if extensions.include?('ts')
@@ -39,7 +39,7 @@ def call
render(ExampleComponentWithScript.new)
end
- def self._sidecar_files(extensions)
+ def self.sidecar_files(extensions)
if extensions.include?('js')
['/components/example_component_with_script_rendering_other_component_with_script.js']
else
@@ -49,7 +49,7 @@ def self._sidecar_files(extensions)
end
class NestedExampleComponentWithScript < ExampleComponentWithScript
- def self._sidecar_files(extensions)
+ def self.sidecar_files(extensions)
if extensions.include?('js')
['/components/nested_example_component_with_script.js']
else
diff --git a/spec/components/previews/base_component_preview.rb b/spec/components/previews/base_component_preview.rb
index ac576c699ed..69b5465dd0c 100644
--- a/spec/components/previews/base_component_preview.rb
+++ b/spec/components/previews/base_component_preview.rb
@@ -1,3 +1,4 @@
+# @hidden
class BaseComponentPreview < ViewComponent::Preview
private
diff --git a/spec/components/previews/step_indicator_component_preview.rb b/spec/components/previews/step_indicator_component_preview.rb
index 6064e079bca..9d3f9611509 100644
--- a/spec/components/previews/step_indicator_component_preview.rb
+++ b/spec/components/previews/step_indicator_component_preview.rb
@@ -8,7 +8,7 @@ def default
{ name: :third_step, title: 'Third Step' },
{ name: :fourth_step, title: 'Fourth Step' },
],
- current_step: :second,
+ current_step: :second_step,
)
end
# @!endgroup
diff --git a/spec/components/previews/validated_field_component_preview.rb b/spec/components/previews/validated_field_component_preview.rb
index 83fc3721d5c..7d154e3d6c9 100644
--- a/spec/components/previews/validated_field_component_preview.rb
+++ b/spec/components/previews/validated_field_component_preview.rb
@@ -50,8 +50,8 @@ def required_checkbox
# @display form true
# @param label text
# @param required toggle
- # @param input_type select [~,Text,Email Address,Boolean]
- def workbench(label: 'Input', required: true, input_type: 'Text')
+ # @param input_type select [~,String,Email,Boolean]
+ def workbench(label: 'Input', required: true, input_type: 'String')
render(
ValidatedFieldComponent.new(
form: form_builder,
diff --git a/spec/controllers/concerns/idv_step_concern_spec.rb b/spec/controllers/concerns/idv_step_concern_spec.rb
index da0675f12a4..1ddf2d87459 100644
--- a/spec/controllers/concerns/idv_step_concern_spec.rb
+++ b/spec/controllers/concerns/idv_step_concern_spec.rb
@@ -12,44 +12,6 @@ class StepController < ApplicationController
end
end
- describe '#confirm_idv_session_started' do
- controller Idv::StepController do
- before_action :confirm_idv_session_started
-
- def show
- render plain: 'Hello'
- end
- end
-
- before(:each) do
- stub_sign_in(user)
- routes.draw do
- get 'show' => 'idv/step#show'
- end
- end
-
- context 'user has not started IdV session' do
- it 'redirects to a previous step url' do
- get :show
-
- expect(response).to redirect_to(idv_verify_info_url)
- end
- end
-
- context 'user has started IdV session' do
- before do
- idv_session.applicant = { first_name: 'Jane' }
- allow(subject).to receive(:idv_session).and_return(idv_session)
- end
-
- it 'allows request' do
- get :show
-
- expect(response.body).to eq 'Hello'
- end
- end
- end
-
describe '#confirm_idv_needed' do
controller Idv::StepController do
before_action :confirm_idv_needed
@@ -70,7 +32,6 @@ def show
before do
allow(user).to receive(:active_profile).and_return(Profile.new)
allow(subject).to receive(:current_user).and_return(user)
- allow(subject).to receive(:confirm_idv_session_started).and_return(true)
end
it 'redirects to activated page' do
@@ -83,7 +44,6 @@ def show
context 'user does not have active profile' do
before do
allow(subject).to receive(:current_user).and_return(user)
- allow(subject).to receive(:confirm_idv_session_started).and_return(true)
end
it 'does not redirect to activated page' do
diff --git a/spec/controllers/idv/phone_controller_spec.rb b/spec/controllers/idv/phone_controller_spec.rb
index d13815ae777..12659a8d800 100644
--- a/spec/controllers/idv/phone_controller_spec.rb
+++ b/spec/controllers/idv/phone_controller_spec.rb
@@ -17,7 +17,7 @@
expect(subject).to have_actions(
:before,
:confirm_two_factor_authenticated,
- :confirm_idv_session_started,
+ :confirm_idv_applicant_created,
)
end
end
@@ -66,6 +66,22 @@
end
end
+ context 'when the user has not finished the verify step' do
+ before do
+ subject.idv_session.applicant = nil
+ subject.idv_session.profile_confirmation = nil
+ subject.idv_session.resolution_successful = nil
+
+ allow(controller).to receive(:confirm_idv_applicant_created).and_call_original
+ end
+
+ it 'redirects to the verify step' do
+ get :new
+
+ expect(response).to redirect_to idv_verify_info_url
+ end
+ end
+
context 'when the user is throttled' do
before do
Throttle.new(throttle_type: :proof_address, user: user).increment_to_throttled!
diff --git a/spec/controllers/idv/review_controller_spec.rb b/spec/controllers/idv/review_controller_spec.rb
index 1c325cb56f5..a0b99b9c079 100644
--- a/spec/controllers/idv/review_controller_spec.rb
+++ b/spec/controllers/idv/review_controller_spec.rb
@@ -34,7 +34,7 @@
expect(subject).to have_actions(
:before,
:confirm_two_factor_authenticated,
- :confirm_idv_session_started,
+ :confirm_idv_applicant_created,
:confirm_idv_steps_complete,
)
end
@@ -192,7 +192,7 @@ def show
describe '#new' do
before do
stub_sign_in(user)
- allow(subject).to receive(:confirm_idv_session_started).and_return(true)
+ allow(subject).to receive(:confirm_idv_applicant_created).and_return(true)
end
context 'user has completed all steps' do
@@ -264,7 +264,7 @@ def show
describe '#create' do
before do
stub_sign_in(user)
- allow(subject).to receive(:confirm_idv_session_started).and_return(true)
+ allow(subject).to receive(:confirm_idv_applicant_created).and_return(true)
end
context 'user fails to supply correct password' do
diff --git a/spec/controllers/idv/ssn_controller_spec.rb b/spec/controllers/idv/ssn_controller_spec.rb
new file mode 100644
index 00000000000..b12d9dc9c73
--- /dev/null
+++ b/spec/controllers/idv/ssn_controller_spec.rb
@@ -0,0 +1,175 @@
+require 'rails_helper'
+
+describe Idv::SsnController do
+ include IdvHelper
+
+ let(:flow_session) do
+ { 'document_capture_session_uuid' => 'fd14e181-6fb1-4cdc-92e0-ef66dad0df4e',
+ 'pii_from_doc' => Idp::Constants::MOCK_IDV_APPLICANT.dup,
+ :threatmetrix_session_id => 'c90ae7a5-6629-4e77-b97c-f1987c2df7d0',
+ :flow_path => 'standard' }
+ end
+
+ let(:user) { build(:user, :with_phone, with: { phone: '+1 (415) 555-0130' }) }
+
+ before do
+ allow(subject).to receive(:flow_session).and_return(flow_session)
+ stub_sign_in(user)
+ end
+
+ describe 'before_actions' do
+ it 'checks that feature flag is enabled' do
+ expect(subject).to have_actions(
+ :before,
+ :render_404_if_ssn_controller_disabled,
+ )
+ end
+
+ it 'includes authentication before_action' do
+ expect(subject).to have_actions(
+ :before,
+ :confirm_two_factor_authenticated,
+ )
+ end
+
+ it 'checks that the previous step is complete' do
+ expect(subject).to have_actions(
+ :before,
+ :confirm_pii_from_doc,
+ )
+ end
+ end
+
+ context 'when doc_auth_ssn_controller_enabled' do
+ before do
+ allow(IdentityConfig.store).to receive(:doc_auth_ssn_controller_enabled).
+ and_return(true)
+ stub_analytics
+ stub_attempts_tracker
+ allow(@analytics).to receive(:track_event)
+ end
+
+ describe '#show' do
+ let(:analytics_name) { 'IdV: doc auth ssn visited' }
+ let(:analytics_args) do
+ {
+ analytics_id: 'Doc Auth',
+ flow_path: 'standard',
+ irs_reproofing: false,
+ step: 'ssn',
+ step_count: 1,
+ }
+ end
+
+ context 'when doc_auth_ssn_controller_enabled' do
+ before do
+ allow(IdentityConfig.store).to receive(:doc_auth_ssn_controller_enabled).
+ and_return(true)
+ end
+
+ it 'renders the show template' do
+ get :show
+
+ expect(response).to render_template :show
+ end
+
+ it 'sends analytics_visited event' do
+ get :show
+
+ expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args)
+ end
+
+ it 'sends correct step count to analytics' do
+ get :show
+ get :show
+ analytics_args[:step_count] = 2
+
+ expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args)
+ end
+ end
+ end
+
+ describe '#update' do
+ context 'with valid ssn' do
+ let(:ssn) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN[:ssn] }
+ let(:params) { { doc_auth: { ssn: ssn } } }
+ let(:analytics_name) { 'IdV: doc auth ssn submitted' }
+ let(:analytics_args) do
+ {
+ analytics_id: 'Doc Auth',
+ flow_path: 'standard',
+ irs_reproofing: false,
+ step: 'ssn',
+ step_count: 1,
+ }
+ end
+
+ it 'merges ssn into pii session value' do
+ put :update, params: params
+
+ expect(flow_session['pii_from_doc'][:ssn]).to eq(ssn)
+ end
+
+ it 'sends analytics_submitted event with correct step count' do
+ get :show
+ put :update, params: params
+
+ expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args)
+ end
+
+ it 'logs attempts api event' do
+ expect(@irs_attempts_api_tracker).to receive(:idv_ssn_submitted).with(
+ ssn: ssn,
+ )
+ put :update, params: params
+ end
+
+ context 'with existing session applicant' do
+ it 'clears applicant' do
+ subject.idv_session.applicant = Idp::Constants::MOCK_IDV_APPLICANT
+
+ put :update, params: params
+
+ expect(subject.idv_session.applicant).to be_blank
+ end
+ end
+
+ it 'adds a threatmetrix session id to flow session' do
+ subject.extra_view_variables
+ expect(flow_session[:threatmetrix_session_id]).to_not eq(nil)
+ end
+
+ it 'does not change threatmetrix_session_id when updating ssn' do
+ flow_session['pii_from_doc'][:ssn] = ssn
+ put :update, params: params
+ session_id = flow_session[:threatmetrix_session_id]
+ subject.extra_view_variables
+ expect(flow_session[:threatmetrix_session_id]).to eq(session_id)
+ end
+ end
+
+ context 'when pii_from_doc is not present' do
+ it 'marks previous step as incomplete' do
+ flow_session.delete('pii_from_doc')
+ flow_session['Idv::Steps::DocumentCaptureStep'] = true
+ put :update
+ expect(flow_session['Idv::Steps::DocumentCaptureStep']).to eq nil
+ expect(response.status).to eq 302
+ end
+ end
+ end
+ end
+
+ context 'when doc_auth_ssn_controller_enabled is false' do
+ before do
+ allow(IdentityConfig.store).to receive(:doc_auth_ssn_controller_enabled).
+ and_return(false)
+ end
+
+ it 'returns 404' do
+ get :show
+
+ expect(response.status).to eq(404)
+ end
+ end
+end
diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb
index bea98420782..ac56fda9afd 100644
--- a/spec/controllers/idv/verify_info_controller_spec.rb
+++ b/spec/controllers/idv/verify_info_controller_spec.rb
@@ -100,10 +100,36 @@
).increment_to_throttled!
end
- it 'redirects to ssn failure url' do
- get :show
+ context 'when using new ssn controller' do
+ before do
+ allow(IdentityConfig.store).to receive(:doc_auth_ssn_controller_enabled).
+ and_return(true)
+ end
- expect(response).to redirect_to idv_session_errors_ssn_failure_url
+ it 'redirects to ssn controller when ssn info is missing' do
+ flow_session[:pii_from_doc][:ssn] = nil
+
+ get :show
+
+ expect(response).to redirect_to(idv_ssn_url)
+ end
+ end
+
+ context 'when the user is ssn throttled' do
+ before do
+ Throttle.new(
+ target: Pii::Fingerprinter.fingerprint(
+ Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN[:ssn],
+ ),
+ throttle_type: :proof_ssn,
+ ).increment_to_throttled!
+ end
+
+ it 'redirects to ssn failure url' do
+ get :show
+
+ expect(response).to redirect_to idv_session_errors_ssn_failure_url
+ end
end
end
diff --git a/spec/controllers/users/backup_code_setup_controller_spec.rb b/spec/controllers/users/backup_code_setup_controller_spec.rb
index 3c5a8a4a16e..e51d17b457d 100644
--- a/spec/controllers/users/backup_code_setup_controller_spec.rb
+++ b/spec/controllers/users/backup_code_setup_controller_spec.rb
@@ -32,6 +32,17 @@
expect(user.backup_code_configurations.length).to eq BackupCodeGenerator::NUMBER_OF_CODES
end
+ it 'creating backup codes revokes remember device cookies' do
+ user = create(:user, :signed_up)
+ stub_sign_in(user)
+ expect(user.remember_device_revoked_at).to eq nil
+
+ freeze_time do
+ post :create
+ expect(user.reload.remember_device_revoked_at).to eq Time.zone.now
+ end
+ end
+
it 'deletes backup codes' do
user = build(:user, :signed_up, :with_authentication_app, :with_backup_code)
stub_sign_in(user)
@@ -43,6 +54,17 @@
expect(user.backup_code_configurations.length).to eq 0
end
+ it 'deleting backup codes revokes remember device cookies' do
+ user = build(:user, :signed_up, :with_authentication_app, :with_backup_code)
+ stub_sign_in(user)
+ expect(user.remember_device_revoked_at).to eq nil
+
+ freeze_time do
+ post :delete
+ expect(user.reload.remember_device_revoked_at).to eq Time.zone.now
+ end
+ end
+
it 'does not deletes backup codes if they are the only mfa' do
user = build(:user, :with_backup_code)
stub_sign_in(user)
diff --git a/spec/controllers/users/edit_phone_controller_spec.rb b/spec/controllers/users/edit_phone_controller_spec.rb
index e2f2296de56..c253f7efa8e 100644
--- a/spec/controllers/users/edit_phone_controller_spec.rb
+++ b/spec/controllers/users/edit_phone_controller_spec.rb
@@ -81,6 +81,15 @@
expect(PhoneConfiguration.find_by(id: phone_configuration.id)).to eq(nil)
end
+ it 'revokes remember device cookies' do
+ stub_sign_in(user.reload)
+ expect(user.remember_device_revoked_at).to eq nil
+ freeze_time do
+ delete :destroy, params: { id: phone_configuration.id }
+ expect(user.reload.remember_device_revoked_at).to eq Time.zone.now
+ end
+ end
+
context 'when the user will not have enough phone configurations after deleting' do
let(:user) { create(:user, :with_phone) }
let(:phone_configuration) { user.phone_configurations.first }
diff --git a/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb b/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb
index ff067b336a0..4b39f1596b1 100644
--- a/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb
+++ b/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb
@@ -242,9 +242,11 @@
end
it 'resets the remember device revocation date/time' do
- delete :delete, params: { id: piv_cac_configuration_id }
- expect(subject.current_user.reload.remember_device_revoked_at.to_i).to \
- be_within(1).of(Time.zone.now.to_i)
+ expect(user.remember_device_revoked_at).to eq nil
+ freeze_time do
+ delete :delete, params: { id: piv_cac_configuration_id }
+ expect(user.reload.remember_device_revoked_at).to eq Time.zone.now
+ end
end
it 'removes the piv/cac information from the user session' do
diff --git a/spec/controllers/users/totp_setup_controller_spec.rb b/spec/controllers/users/totp_setup_controller_spec.rb
index cfcf61a74c6..7428ca25156 100644
--- a/spec/controllers/users/totp_setup_controller_spec.rb
+++ b/spec/controllers/users/totp_setup_controller_spec.rb
@@ -398,6 +398,18 @@
expect(@analytics).to have_received(:track_event).with('TOTP: User Disabled')
expect(subject).to have_received(:create_user_event).with(:authenticator_disabled)
end
+
+ it 'revokes remember device cookies' do
+ user = create(:user, :signed_up, :with_phone)
+ totp_app = user.auth_app_configurations.create(otp_secret_key: 'foo', name: 'My Auth App')
+ user.save
+ stub_sign_in(user)
+ expect(user.remember_device_revoked_at).to eq nil
+ freeze_time do
+ delete :disable, params: { id: totp_app.id }
+ expect(user.reload.remember_device_revoked_at).to eq Time.zone.now
+ end
+ end
end
context 'when totp is the last mfa method' do
diff --git a/spec/controllers/users/webauthn_setup_controller_spec.rb b/spec/controllers/users/webauthn_setup_controller_spec.rb
index aa570edd5e8..f36a3475f82 100644
--- a/spec/controllers/users/webauthn_setup_controller_spec.rb
+++ b/spec/controllers/users/webauthn_setup_controller_spec.rb
@@ -119,6 +119,14 @@
).to eq 1
end
+ it 'revokes remember device cookies' do
+ expect(user.remember_device_revoked_at).to eq nil
+ freeze_time do
+ delete :delete, params: { id: webauthn_configuration.id }
+ expect(user.reload.remember_device_revoked_at).to eq Time.zone.now
+ end
+ end
+
it 'tracks the delete in analytics' do
result = {
success: true,
diff --git a/spec/features/idv/doc_auth/document_capture_step_spec.rb b/spec/features/idv/doc_auth/document_capture_step_spec.rb
index bce2bbdefa5..a630d6094c0 100644
--- a/spec/features/idv/doc_auth/document_capture_step_spec.rb
+++ b/spec/features/idv/doc_auth/document_capture_step_spec.rb
@@ -207,6 +207,20 @@
end
end
+ context 'when new ssn controller is enabled' do
+ before do
+ allow(IdentityConfig.store).to receive(:doc_auth_ssn_controller_enabled).
+ and_return(true)
+ end
+ it 'redirects to ssn controller' do
+ expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_id'))
+
+ attach_and_submit_images
+
+ expect(page).to have_current_path(idv_ssn_url)
+ end
+ end
+
def next_step
idv_doc_auth_ssn_step
end
diff --git a/spec/features/idv/doc_auth/ssn_spec.rb b/spec/features/idv/doc_auth/ssn_spec.rb
new file mode 100644
index 00000000000..d1d7c6b373d
--- /dev/null
+++ b/spec/features/idv/doc_auth/ssn_spec.rb
@@ -0,0 +1,45 @@
+require 'rails_helper'
+
+feature 'doc auth ssn step', :js do
+ include IdvStepHelper
+ include DocAuthHelper
+ include DocCaptureHelper
+
+ before do
+ allow(IdentityConfig.store).to receive(:proofing_device_profiling).and_return(:enabled)
+ allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_org_id).and_return('test_org')
+ allow(IdentityConfig.store).to receive(:doc_auth_ssn_controller_enabled).and_return(true)
+
+ sign_in_and_2fa_user
+ complete_doc_auth_steps_before_ssn_step
+ end
+
+ it 'proceeds to the next page with valid info' do
+ expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_info'))
+
+ fill_out_ssn_form_ok
+
+ match = page.body.match(/session_id=(?[^"&]+)/)
+ session_id = match && match[:session_id]
+ expect(session_id).to be_present
+
+ select 'Review', from: 'mock_profiling_result'
+
+ expect(page.find_field(t('idv.form.ssn_label_html'))['aria-invalid']).to eq('false')
+ click_idv_continue
+
+ expect(page).to have_current_path(idv_verify_info_url)
+
+ profiling_result = Proofing::Mock::DeviceProfilingBackend.new.profiling_result(session_id)
+ expect(profiling_result).to eq('review')
+ end
+
+ it 'does not proceed to the next page with invalid info' do
+ fill_out_ssn_form_fail
+ click_idv_continue
+
+ expect(page.find_field(t('idv.form.ssn_label_html'))['aria-invalid']).to eq('true')
+
+ expect(page).to have_current_path(idv_ssn_url)
+ end
+end
diff --git a/spec/features/idv/doc_auth/verify_info_step_spec.rb b/spec/features/idv/doc_auth/verify_info_step_spec.rb
index 64cd6c07a85..078cb82e81f 100644
--- a/spec/features/idv/doc_auth/verify_info_step_spec.rb
+++ b/spec/features/idv/doc_auth/verify_info_step_spec.rb
@@ -374,4 +374,32 @@
expect(page).to have_current_path(idv_phone_path)
end
end
+
+ context 'with ssn_controller enabled' do
+ before do
+ allow(IdentityConfig.store).to receive(:doc_auth_ssn_controller_enabled).
+ and_return(true)
+ allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics)
+ sign_in_and_2fa_user
+ complete_doc_auth_steps_before_verify_step
+ end
+
+ it 'uses ssn controller to enter a new ssn and displays updated info' do
+ click_link t('idv.buttons.change_ssn_label')
+ expect(page).to have_current_path(idv_ssn_path)
+
+ fill_in t('idv.form.ssn_label_html'), with: '900456789'
+ click_button t('forms.buttons.submit.update')
+
+ expect(fake_analytics).to have_logged_event(
+ 'IdV: doc auth redo_ssn submitted',
+ )
+
+ expect(page).to have_current_path(idv_verify_info_path)
+
+ expect(page).to have_text('9**-**-***9')
+ check t('forms.ssn.show')
+ expect(page).to have_text('900-45-6789')
+ end
+ end
end
diff --git a/spec/features/phone/add_phone_spec.rb b/spec/features/phone/add_phone_spec.rb
index 554f9dd24fc..ae08ae2058d 100644
--- a/spec/features/phone/add_phone_spec.rb
+++ b/spec/features/phone/add_phone_spec.rb
@@ -139,10 +139,15 @@
)
end
- scenario 'adding a phone that is already on the user account shows error message' do
+ scenario 'adding a phone that is already on the user account shows error message', js: true do
user = create(:user, :signed_up)
phone = user.phone_configurations.first.phone
+ # Regression handling: The fake phone number generator uses well-formatted numbers, which isn't
+ # how a user would likely enter their number, and would give detail to the phone initialization
+ # which wouldn't exist for typical user input. Emulate the user input by removing format hints.
+ phone = phone.sub(/^\+1\s*/, '').gsub(/\D/, '')
+
sign_in_and_2fa_user(user)
within('.sidenav') do
click_on t('account.navigation.add_phone_number')
@@ -151,6 +156,7 @@
click_continue
expect(page).to have_content(I18n.t('errors.messages.phone_duplicate'))
+ expect(page).to have_css('.iti__selected-flag .iti__flag.iti__us', visible: :all)
end
let(:telephony_gem_voip_number) { '+12255551000' }
diff --git a/spec/features/remember_device/revocation_spec.rb b/spec/features/remember_device/revocation_spec.rb
index a05b53fb282..d55c8921211 100644
--- a/spec/features/remember_device/revocation_spec.rb
+++ b/spec/features/remember_device/revocation_spec.rb
@@ -7,133 +7,6 @@
allow(IdentityConfig.store).to receive(:otp_delivery_blocklist_maxretry).and_return(1000)
end
- context 'phone' do
- let(:user) { create(:user, :signed_up) }
-
- it 'revokes remember device when removed' do
- create(:webauthn_configuration, user: user) # The user needs multiple methods to delete phone
-
- sign_in_with_remember_device_and_sign_out
-
- sign_in_user(user)
- click_link(
- t('forms.buttons.manage'),
- href: manage_phone_path(id: user.phone_configurations.first.id),
- )
- click_on t('forms.phone.buttons.delete')
- first(:link, t('links.sign_out')).click
-
- expect_mfa_to_be_required_for_user(user)
- end
- end
-
- context 'webauthn' do
- let(:user) { create(:user, :signed_up, :with_webauthn) }
-
- it 'revokes remember device when removed' do
- sign_in_with_remember_device_and_sign_out
-
- sign_in_user(user)
- visit account_two_factor_authentication_path
- click_on t('account.index.webauthn_delete')
- click_on t('account.index.webauthn_confirm_delete')
- first(:link, t('links.sign_out')).click
-
- expect_mfa_to_be_required_for_user(user)
- end
- end
-
- context 'webauthn platform' do
- let(:user) { create(:user, :signed_up, :with_webauthn_platform) }
-
- it 'revokes remember device when removed' do
- sign_in_with_remember_device_and_sign_out
-
- sign_in_user(user)
- visit account_two_factor_authentication_path
- click_on t('account.index.webauthn_platform_delete')
- click_on t('account.index.webauthn_platform_confirm_delete')
- first(:link, t('links.sign_out')).click
-
- expect_mfa_to_be_required_for_user(user)
- end
- end
-
- context 'piv/cac' do
- let(:user) { create(:user, :signed_up, :with_piv_or_cac) }
-
- it 'revokes remember device when removed' do
- sign_in_with_remember_device_and_sign_out
-
- sign_in_user(user)
- visit account_two_factor_authentication_path
- page.find('.remove-piv').click
- click_on t('account.index.piv_cac_confirm_delete')
- first(:link, t('links.sign_out')).click
-
- expect_mfa_to_be_required_for_user(user)
- end
- end
-
- context 'totp' do
- let(:user) { create(:user, :signed_up, :with_authentication_app) }
-
- it 'revokes remember device when removed' do
- sign_in_with_remember_device_and_sign_out
-
- sign_in_user(user)
- visit account_two_factor_authentication_path
- page.find('.remove-auth-app').click # Delete
- click_on t('account.index.totp_confirm_delete')
- first(:link, t('links.sign_out')).click
-
- expect_mfa_to_be_required_for_user(user)
- end
- end
-
- context 'backup codes' do
- let(:user) { create(:user, :signed_up, :with_authentication_app, :with_backup_code) }
-
- it 'revokes remember device when regenerated' do
- sign_in_with_remember_device_and_sign_out
-
- sign_in_user(user)
- visit account_two_factor_authentication_path
- click_on t('forms.backup_code.regenerate')
- click_on t('account.index.backup_code_confirm_regenerate')
- expect(page).to have_content(t('forms.backup_code.subtitle'))
- click_continue
- first(:link, t('links.sign_out')).click
-
- expect_mfa_to_be_required_for_user(user)
- end
-
- it 'revokes remember device when removed' do
- user.backup_code_configurations.destroy_all
- sign_in_with_remember_device_and_sign_out
-
- sign_in_user(user)
- visit account_two_factor_authentication_path
- click_on t('forms.backup_code.generate')
- click_continue
- click_continue
-
- expect(user.reload.backup_code_configurations).to_not be_empty
-
- click_link(
- t('forms.buttons.delete'),
- href: backup_code_delete_path,
- )
- click_on t('account.index.backup_code_confirm_delete')
-
- expect(user.reload.backup_code_configurations).to be_empty
-
- first(:link, t('links.sign_out')).click
-
- expect_mfa_to_be_required_for_user(user)
- end
- end
-
context 'clicking forget browsers' do
let(:user) { create(:user, :signed_up) }
diff --git a/spec/forms/openid_connect_authorize_form_spec.rb b/spec/forms/openid_connect_authorize_form_spec.rb
index bb76a2dd5fd..05f45dfd74b 100644
--- a/spec/forms/openid_connect_authorize_form_spec.rb
+++ b/spec/forms/openid_connect_authorize_form_spec.rb
@@ -356,6 +356,98 @@
end
end
+ describe '#aal' do
+ context 'when DEFAULT_AAL passed' do
+ before do
+ default = Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF
+ IdentityConfig.store.valid_authn_contexts.push(default)
+ end
+
+ after do
+ IdentityConfig.store.valid_authn_contexts.pop
+ end
+
+ let(:acr_values) { Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF }
+
+ it 'returns 0' do
+ expect(form.aal).to eq(Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF)
+ end
+ end
+
+ context 'when AAL1 passed' do
+ before do
+ aal1 = Saml::Idp::Constants::AAL1_AUTHN_CONTEXT_CLASSREF
+ IdentityConfig.store.valid_authn_contexts.push(aal1)
+ end
+
+ after do
+ IdentityConfig.store.valid_authn_contexts.pop
+ end
+
+ let(:acr_values) { Saml::Idp::Constants::AAL1_AUTHN_CONTEXT_CLASSREF }
+
+ it 'returns 1' do
+ expect(form.aal).to eq(1)
+ end
+ end
+
+ context 'when AAL2 passed' do
+ let(:acr_values) { Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF }
+
+ it 'returns 2' do
+ expect(form.aal).to eq(2)
+ end
+ end
+
+ context 'when AAL2_PHISHING_RESISTANT passed' do
+ let(:acr_values) { Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF }
+
+ it 'returns 2' do
+ expect(form.aal).to eq(2)
+ end
+ end
+
+ context 'when AAL2_HSPD12 passed' do
+ let(:acr_values) { Saml::Idp::Constants::AAL2_HSPD12_AUTHN_CONTEXT_CLASSREF }
+
+ it 'returns 2' do
+ expect(form.aal).to eq(2)
+ end
+ end
+
+ context 'when AAL3 passed' do
+ let(:acr_values) { Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF }
+
+ it 'returns 3' do
+ expect(form.aal).to eq(3)
+ end
+ end
+
+ context 'when AAL3_HSPD12 passed' do
+ let(:acr_values) { Saml::Idp::Constants::AAL3_HSPD12_AUTHN_CONTEXT_CLASSREF }
+
+ it 'returns 3' do
+ expect(form.aal).to eq(3)
+ end
+ end
+ end
+
+ describe '#aal' do
+ context 'when IAL and AAL passed' do
+ aal2 = Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF
+ ial2 = Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF
+
+ let(:acr_values) do
+ "#{aal2} #{ial2}"
+ end
+
+ it 'returns ial and aal' do
+ expect(form.aal).to eq(2)
+ expect(form.ial).to eq(2)
+ end
+ end
+ end
+
describe '#verified_within' do
context 'without a verified_within' do
let(:verified_within) { nil }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 2ccfdf73bf5..71e197a8f67 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -510,6 +510,70 @@
end
end
+ describe '#personal_key_generated_at' do
+ let(:user) do
+ build(:user, encrypted_recovery_code_digest_generated_at: digest_generated_at)
+ end
+ let(:digest_generated_at) { nil }
+
+ context 'the user has a encrypted_recovery_code_digest_generated_at date' do
+ let(:digest_generated_at) { 1.day.ago }
+
+ it 'returns the date in the digest' do
+ expect(
+ user.personal_key_generated_at,
+ ).to be_within(1.second).of(digest_generated_at)
+ end
+ end
+
+ context 'the user does not have a encrypted_recovery_code_digest_generated_at but is proofed' do
+ let!(:profile) do
+ create(
+ :profile,
+ :active,
+ :verified,
+ user: user,
+ )
+ end
+
+ it 'returns the date the user was proofed' do
+ expect(
+ user.personal_key_generated_at,
+ ).to be_within(1.second).of(profile.verified_at)
+ end
+ end
+
+ context 'the user has no encrypted_recovery_code_digest_generated_at and is not proofed' do
+ it 'returns nil' do
+ expect(user.personal_key_generated_at).to be_nil
+ end
+ end
+
+ context 'the user has no active profile but has a previously verified profile' do
+ let!(:password_reset_profile) do
+ create(
+ :profile,
+ :password_reset,
+ user: user,
+ )
+ end
+
+ let!(:verification_cancelled_profile) do
+ create(
+ :profile,
+ :verification_cancelled,
+ user: user,
+ )
+ end
+
+ it 'returns the date of the previously verified profile' do
+ expect(
+ user.personal_key_generated_at,
+ ).to be_within(1.second).of(password_reset_profile.verified_at)
+ end
+ end
+ end
+
describe '#should_receive_in_person_completion_survey?' do
let!(:user) { create(:user) }
let(:service_provider) { create(:service_provider) }
diff --git a/spec/presenters/openid_connect_user_info_presenter_spec.rb b/spec/presenters/openid_connect_user_info_presenter_spec.rb
index 4411d4a0422..c380b2875da 100644
--- a/spec/presenters/openid_connect_user_info_presenter_spec.rb
+++ b/spec/presenters/openid_connect_user_info_presenter_spec.rb
@@ -17,6 +17,7 @@
user: create(:user, profiles: [profile]),
service_provider: service_provider.issuer,
scope: scope,
+ aal: 2,
)
end
@@ -26,12 +27,17 @@
subject(:user_info) { presenter.user_info }
it 'has basic attributes' do
+ ial = Saml::Idp::Constants::AUTHN_CONTEXT_IAL_TO_CLASSREF[identity.ial]
+ aal = Saml::Idp::Constants::AUTHN_CONTEXT_AAL_TO_CLASSREF[identity.aal]
+
aggregate_failures do
expect(user_info[:sub]).to eq(identity.uuid)
expect(user_info[:iss]).to eq(root_url)
expect(user_info[:email]).to eq(identity.user.email_addresses.first.email)
expect(user_info[:email_verified]).to eq(true)
expect(user_info[:all_emails]).to eq([identity.user.email_addresses.first.email])
+ expect(user_info[:ial]).to eq(ial)
+ expect(user_info[:aal]).to eq(aal)
end
end
diff --git a/spec/support/controller_helper.rb b/spec/support/controller_helper.rb
index 70a2ea6b009..ac1eba140fd 100644
--- a/spec/support/controller_helper.rb
+++ b/spec/support/controller_helper.rb
@@ -47,7 +47,7 @@ def stub_idv_steps_before_verify_step(user)
dob: 50.years.ago.to_date.to_s,
ssn: '666-12-1234',
}.with_indifferent_access
- allow(subject).to receive(:confirm_idv_session_started).and_return(true)
+ allow(subject).to receive(:confirm_idv_applicant_created).and_return(true)
allow(subject).to receive(:idv_session).and_return(idv_session)
allow(subject).to receive(:user_session).and_return(user_session)
end
@@ -67,7 +67,7 @@ def stub_verify_steps_one_and_two(user)
ssn: '666-12-1234',
}.with_indifferent_access
idv_session.profile_confirmation = true
- allow(subject).to receive(:confirm_idv_session_started).and_return(true)
+ allow(subject).to receive(:confirm_idv_applicant_created).and_return(true)
allow(subject).to receive(:idv_session).and_return(idv_session)
allow(subject).to receive(:user_session).and_return(user_session)
end
@@ -81,7 +81,7 @@ def stub_user_with_applicant_data(user, applicant)
)
idv_session.applicant = applicant.with_indifferent_access
idv_session.profile_confirmation = true
- allow(subject).to receive(:confirm_idv_session_started).and_return(true)
+ allow(subject).to receive(:confirm_idv_applicant_created).and_return(true)
allow(subject).to receive(:idv_session).and_return(idv_session)
allow(subject).to receive(:user_session).and_return(user_session)
end
diff --git a/spec/views/partials/personal_key/_key.html.erb_spec.rb b/spec/views/partials/personal_key/_key.html.erb_spec.rb
new file mode 100644
index 00000000000..3f697be8584
--- /dev/null
+++ b/spec/views/partials/personal_key/_key.html.erb_spec.rb
@@ -0,0 +1,44 @@
+require 'rails_helper'
+
+describe 'partials/personal_key/_key.html.erb' do
+ subject(:rendered) do
+ render partial: 'key', locals: locals
+ end
+
+ let(:personal_key) { 'XXXX-XXXX-XXXX-XXXX' }
+
+ context 'with local personal_key_generated_at' do
+ let(:personal_key_generated_at) { Time.zone.parse('2020-04-09T14:03:00Z').utc }
+ let(:locals) do
+ {
+ code: personal_key,
+ personal_key_generated_at: personal_key_generated_at,
+ show_save_buttons: false,
+ }
+ end
+
+ it 'displays the specified date' do
+ expect(rendered).to have_css(
+ 'lg-time[data-timestamp="2020-04-09T14:03:00Z"][data-format]',
+ text: 'April 9, 2020 at 2:03 PM',
+ )
+ end
+
+ it 'displays personal key block' do
+ expect(rendered).to have_css('.personal-key-block__code')
+ end
+ end
+
+ context 'without local personal_key_generated_at' do
+ let(:locals) do
+ {
+ code: personal_key,
+ show_save_buttons: false,
+ }
+ end
+
+ it 'displays personal key block' do
+ expect(rendered).to have_css('.personal-key-block__code')
+ end
+ end
+end
diff --git a/spec/views/reactivate_account/index.html.erb_spec.rb b/spec/views/reactivate_account/index.html.erb_spec.rb
index 819028de174..3aca5ed3a7a 100644
--- a/spec/views/reactivate_account/index.html.erb_spec.rb
+++ b/spec/views/reactivate_account/index.html.erb_spec.rb
@@ -1,9 +1,24 @@
require 'rails_helper'
describe 'reactivate_account/index.html.erb' do
- it 'displays a fallback warning alert when js is off' do
+ subject(:rendered) do
render
+ end
+
+ let(:personal_key_generated_at) { Time.zone.parse('2020-04-09T14:03:00Z').utc }
+
+ it 'displays a fallback warning alert when js is off' do
+ assign(:personal_key_generated_at, personal_key_generated_at)
expect(rendered).to have_content(t('instructions.account.reactivate.modal.copy'))
end
+
+ it 'displays the date the personal key was generated' do
+ assign(:personal_key_generated_at, personal_key_generated_at)
+
+ expect(rendered).to have_css(
+ 'lg-time[data-timestamp="2020-04-09T14:03:00Z"][data-format]',
+ text: 'April 9, 2020 at 2:03 PM',
+ )
+ end
end
diff --git a/spec/views/shared/_personal_key.html.erb_spec.rb b/spec/views/shared/_personal_key.html.erb_spec.rb
index e99b12bfd1a..4aaeb30300a 100644
--- a/spec/views/shared/_personal_key.html.erb_spec.rb
+++ b/spec/views/shared/_personal_key.html.erb_spec.rb
@@ -2,8 +2,14 @@
RSpec.describe 'shared/_personal_key.html.erb' do
let(:personal_key) { RandomPhrase.new(num_words: 4).to_s }
+ let(:personal_key_generated_at) { Time.zone.today }
- subject(:rendered) { render 'shared/personal_key', code: personal_key, update_path: '/test' }
+ subject(:rendered) do
+ render 'shared/personal_key',
+ code: personal_key,
+ personal_key_generated_at: personal_key_generated_at,
+ update_path: '/test'
+ end
describe 'download link' do
it 'has the download attribute and a data: url for the personal key' do
diff --git a/yarn.lock b/yarn.lock
index d5c140ab952..fc21062e5bc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4026,10 +4026,10 @@ interpret@^2.2.0:
resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==
-intl-tel-input@^17.0.8:
- version "17.0.12"
- resolved "https://registry.yarnpkg.com/intl-tel-input/-/intl-tel-input-17.0.12.tgz#057c35b57871bd6d6932ac28428d086e63d6cc89"
- integrity sha512-jl3DkDQg/aaIPK2hrvtgX2eYgtkz5LxCQW57Ru1Hpdt9MA9VE8PnGUllaMsAm6SeJODHBdMok7XFZR8/M1yytg==
+intl-tel-input@^17.0.19:
+ version "17.0.19"
+ resolved "https://registry.yarnpkg.com/intl-tel-input/-/intl-tel-input-17.0.19.tgz#4c277e3bf02069fac2ef3821a62a3d7e8b55740a"
+ integrity sha512-GBNoUT4JVgm2e1N+yFMaBQ24g5EQfZhDznGneCM9IEZwfKsMUAUa1dS+v0wOiKpRAZ5IPNLJMIEEFGgqlCI22A==
ipaddr.js@1.9.1:
version "1.9.1"