diff --git a/app/controllers/concerns/verify_profile_concern.rb b/app/controllers/concerns/verify_profile_concern.rb index 598533a4fe9..972e9c85206 100644 --- a/app/controllers/concerns/verify_profile_concern.rb +++ b/app/controllers/concerns/verify_profile_concern.rb @@ -11,6 +11,7 @@ def account_or_verify_profile_url def account_or_verify_profile_route return 'account' unless profile_needs_verification? + return 'idv_usps' if usps_mail_bounced? 'verify_account' end @@ -18,4 +19,8 @@ def profile_needs_verification? return false if current_user.blank? current_user.decorate.pending_profile_requires_verification? end + + def usps_mail_bounced? + current_user.decorate.usps_mail_bounced? + end end diff --git a/app/controllers/idv/doc_auth_controller.rb b/app/controllers/idv/doc_auth_controller.rb index 24630f4153c..6812b0fc6a0 100644 --- a/app/controllers/idv/doc_auth_controller.rb +++ b/app/controllers/idv/doc_auth_controller.rb @@ -1,6 +1,8 @@ module Idv class DocAuthController < ApplicationController before_action :confirm_two_factor_authenticated + before_action :redirect_if_mail_bounced + before_action :redirect_if_pending_profile include IdvSession # remove if we retire the non docauth LOA3 flow include Flow::FlowStateMachine @@ -11,5 +13,13 @@ class DocAuthController < ApplicationController flow: Idv::Flows::DocAuthFlow, analytics_id: Analytics::DOC_AUTH, }.freeze + + def redirect_if_mail_bounced + redirect_to idv_usps_url if current_user.decorate.usps_mail_bounced? + end + + def redirect_if_pending_profile + redirect_to verify_account_url if current_user.decorate.pending_profile_requires_verification? + end end end diff --git a/app/controllers/idv/usps_controller.rb b/app/controllers/idv/usps_controller.rb index 8e78fc1b4c1..b690c0cc332 100644 --- a/app/controllers/idv/usps_controller.rb +++ b/app/controllers/idv/usps_controller.rb @@ -1,11 +1,13 @@ +# :reek:TooManyMethods module Idv - class UspsController < ApplicationController + class UspsController < ApplicationController # rubocop:disable Metrics/ClassLength include IdvSession before_action :confirm_two_factor_authenticated before_action :confirm_idv_needed before_action :confirm_user_completed_idv_profile_step before_action :confirm_mail_not_spammed + before_action :max_attempts_reached, only: [:update] def index @presenter = UspsPresenter.new(current_user) @@ -23,12 +25,67 @@ def create end end + def update + result = submit_form_and_perform_resolution + analytics.track_event(Analytics::IDV_USPS_ADDRESS_SUBMITTED, result.to_h) + result.success? ? resolution_success(pii) : failure + end + def usps_mail_service @_usps_mail_service ||= Idv::UspsMail.new(current_user) end private + def submit_form_and_perform_resolution + result = idv_form.submit(profile_params) + result = perform_resolution(pii) if result.success? + result + end + + def failure + redirect_to idv_usps_url unless performed? + end + + def pii + hash = {} + update_hash_with_address(hash) + update_hash_with_non_address_pii(hash) + hash + end + + def update_hash_with_address(hash) + profile_params.each { |key, value| hash[key] = value } + end + + def update_hash_with_non_address_pii(hash) + pii_h = pii_to_h + %w[first_name middle_name last_name dob phone ssn].each do |key| + hash[key] = pii_h[key] + end + end + + def pii_to_h + JSON.parse(user_session[:decrypted_pii]) + end + + def resolution_success(hash) + idv_session_settings(hash).each { |key, value| user_session['idv'][key] = value } + resend_letter + redirect_to idv_review_url + end + + def idv_session_settings(hash) + { 'vendor_phone_confirmation': false, + 'user_phone_confirmation': false, + 'resolution_successful': 'phone', + 'address_verification_mechanism': 'usps', + 'profile_confirmation': true, + 'params': hash, + 'applicant': hash, + 'uuid': current_user.uuid } + end + def confirm_mail_not_spammed redirect_to idv_review_url if idv_session.address_mechanism_chosen? && usps_mail_service.mail_spammed? @@ -54,5 +111,49 @@ def resend_letter return unless FeatureManagement.reveal_usps_code? session[:last_usps_confirmation_code] = confirmation_maker.otp end + + def idv_form + Idv::AddressForm.new( + user: current_user, + previous_params: idv_session.previous_profile_step_params, + ) + end + + def profile_params + params.require(:idv_form).permit(Idv::AddressForm::ATTRIBUTES) + end + + def perform_resolution(pii_from_doc) + idv_result = Idv::Agent.new(pii_from_doc).proof(:resolution) + success = idv_result[:success] + throttle_failure unless success + form_response(idv_result, success) + end + + def form_response(result, success) + FormResponse.new(success: success, errors: result[:errors]) + end + + def throttle_failure + attempter.increment + flash_error + end + + def flash_error + flash[:error] = error_message + redirect_to idv_usps_url + end + + def max_attempts_reached + flash_error if attempter.exceeded? + end + + def error_message + I18n.t('idv.failure.sessions.' + (attempter.exceeded? ? 'fail' : 'heading')) + end + + def attempter + @attempter ||= Idv::Attempter.new(idv_session.current_user) + end end end diff --git a/app/decorators/user_decorator.rb b/app/decorators/user_decorator.rb index 305ba88dfd0..685e0d4fb0b 100644 --- a/app/decorators/user_decorator.rb +++ b/app/decorators/user_decorator.rb @@ -74,6 +74,11 @@ def identity_verified? user.active_profile.present? end + def usps_mail_bounced? + return unless pending_profile + pending_profile&.usps_confirmation_codes&.order(created_at: :desc)&.first&.bounced_at + end + def active_profile_newer_than_pending_profile? user.active_profile.activated_at >= pending_profile.created_at end diff --git a/app/presenters/idv/usps_presenter.rb b/app/presenters/idv/usps_presenter.rb index dbbab9a725d..4b4b36074b7 100644 --- a/app/presenters/idv/usps_presenter.rb +++ b/app/presenters/idv/usps_presenter.rb @@ -12,6 +12,14 @@ def title letter_already_sent? ? I18n.t('idv.titles.mail.resend') : I18n.t('idv.titles.mail.verify') end + def byline + if current_user.decorate.usps_mail_bounced? + I18n.t('idv.messages.usps.new_address') + else + I18n.t('idv.messages.usps.address_on_file') + end + end + def button letter_already_sent? ? I18n.t('idv.buttons.mail.resend') : I18n.t('idv.buttons.mail.send') end @@ -22,6 +30,10 @@ def cancel_path idv_cancel_path end + def usps_mail_bounced? + current_user.decorate.usps_mail_bounced? + end + private def usps_mail_service diff --git a/app/services/analytics.rb b/app/services/analytics.rb index 666793d3af6..9c7f2db1cc3 100644 --- a/app/services/analytics.rb +++ b/app/services/analytics.rb @@ -92,6 +92,7 @@ def browser IDV_PHONE_RECORD_VISIT = 'IdV: phone of record visited'.freeze IDV_REVIEW_COMPLETE = 'IdV: review complete'.freeze IDV_REVIEW_VISIT = 'IdV: review info visited'.freeze + IDV_USPS_ADDRESS_SUBMITTED = 'IdV: USPS address submitted'.freeze IDV_VERIFICATION_ATTEMPT_CANCELLED = 'IdV: verification attempt cancelled'.freeze INVALID_AUTHENTICITY_TOKEN = 'Invalid Authenticity Token'.freeze LOGOUT_INITIATED = 'Logout Initiated'.freeze diff --git a/app/services/flow/base_flow.rb b/app/services/flow/base_flow.rb index c10da8cf645..e559055b4e6 100644 --- a/app/services/flow/base_flow.rb +++ b/app/services/flow/base_flow.rb @@ -8,16 +8,22 @@ def initialize(steps, actions, session, current_user) @steps = steps.with_indifferent_access @actions = actions.with_indifferent_access @params = nil + @redirect = nil @flow_session = session end def next_step + return @redirect if @redirect step, _klass = steps.detect do |_step, klass| !@flow_session[klass.to_s] end step end + def redirect_to(url) + @redirect = url + end + def handle(step, request, params) @flow_session[:error_message] = nil handler = steps[step] || actions[step] diff --git a/app/services/flow/base_step.rb b/app/services/flow/base_step.rb index 0ebed1a09e3..f157f944fae 100644 --- a/app/services/flow/base_step.rb +++ b/app/services/flow/base_step.rb @@ -2,8 +2,8 @@ module Flow class BaseStep include Rails.application.routes.url_helpers - def initialize(context, name) - @context = context + def initialize(flow, name) + @flow = flow @form_response = nil @name = name end @@ -43,10 +43,14 @@ def permit(*args) params.require(@name).permit(*args) end + def redirect_to(url) + @flow.redirect_to(url) + end + def reset - @context.flow_session = {} + @flow.flow_session = {} end - delegate :flow_session, :current_user, :params, :steps, :request, to: :@context + delegate :flow_session, :current_user, :params, :steps, :request, to: :@flow end end diff --git a/app/services/flow/flow_state_machine.rb b/app/services/flow/flow_state_machine.rb index 859cecabd17..20f9faf8c1f 100644 --- a/app/services/flow/flow_state_machine.rb +++ b/app/services/flow/flow_state_machine.rb @@ -10,7 +10,7 @@ module FlowStateMachine attr_accessor :flow def index - redirect_to_step(flow.next_step) + redirect_to_step(next_step) end def show @@ -23,6 +23,7 @@ def update step = params[:step] result = flow.handle(step, request, params) analytics.track_event(analytics_submitted, result.to_h.merge(step: step)) if @analytics_id + flow_finish and return unless next_step render_update(step, result) end @@ -37,8 +38,12 @@ def fsm_initialize end def render_update(step, result) - flow_finish and return unless flow.next_step + redirect_to next_step and return if next_step_is_url move_to_next_step and return if result.success? + set_error_and_render(step, result) + end + + def set_error_and_render(step, result) flow_session = flow.flow_session flow_session[:error_message] = result.errors.values.join(' ') render_step(step, flow_session) @@ -46,7 +51,7 @@ def render_update(step, result) def move_to_next_step user_session[@name] = flow.flow_session - redirect_to_step(flow.next_step) + redirect_to_step(next_step) end def render_step(step, flow_session) @@ -56,7 +61,6 @@ def render_step(step, flow_session) end def ensure_correct_step - next_step = flow.next_step redirect_to_step(next_step) if next_step.to_s != params[:step] end @@ -75,6 +79,14 @@ def analytics_submitted def analytics_visited @analytics_id + ' visited' end + + def next_step + flow.next_step + end + + def next_step_is_url + next_step.to_s.index(':') + end end end diff --git a/app/services/idv/flows/doc_auth_flow.rb b/app/services/idv/flows/doc_auth_flow.rb index 9ebba5ab638..493b7561800 100644 --- a/app/services/idv/flows/doc_auth_flow.rb +++ b/app/services/idv/flows/doc_auth_flow.rb @@ -13,7 +13,6 @@ class DocAuthFlow < Flow::BaseFlow mobile_back_image: Idv::Steps::MobileBackImageStep, ssn: Idv::Steps::SsnStep, verify: Idv::Steps::VerifyStep, - doc_failed: Idv::Steps::DocFailedStep, doc_success: Idv::Steps::DocSuccessStep, }.freeze diff --git a/app/services/idv/steps/doc_auth_base_step.rb b/app/services/idv/steps/doc_auth_base_step.rb index 960483c69a2..66d4f09c9a0 100644 --- a/app/services/idv/steps/doc_auth_base_step.rb +++ b/app/services/idv/steps/doc_auth_base_step.rb @@ -1,9 +1,9 @@ module Idv module Steps class DocAuthBaseStep < Flow::BaseStep - def initialize(context) + def initialize(flow) @assure_id = nil - super(context, :doc_auth) + super(flow, :doc_auth) end private @@ -27,7 +27,18 @@ def simulate? Figaro.env.acuant_simulator == 'true' end - delegate :idv_session, to: :@context + def attempter + @attempter ||= Idv::Attempter.new(current_user) + end + + def idv_failure(result) + attempter.increment + type = attempter.exceeded? ? :fail : :warning + redirect_to idv_session_failure_url(reason: type) + result + end + + delegate :idv_session, to: :@flow end end end diff --git a/app/services/idv/steps/doc_failed_step.rb b/app/services/idv/steps/doc_failed_step.rb deleted file mode 100644 index 4cf2bb75261..00000000000 --- a/app/services/idv/steps/doc_failed_step.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Idv - module Steps - class DocFailedStep < DocAuthBaseStep - def call - reset - end - end - end -end diff --git a/app/services/idv/steps/verify_step.rb b/app/services/idv/steps/verify_step.rb index 41446d32e19..ceda58128e8 100644 --- a/app/services/idv/steps/verify_step.rb +++ b/app/services/idv/steps/verify_step.rb @@ -3,28 +3,22 @@ module Steps class VerifyStep < DocAuthBaseStep def call pii_from_doc = flow_session[:pii_from_doc] - result = Idv::SsnForm.new(current_user).submit(ssn: pii_from_doc[:ssn]) - if result.success? - success(pii_from_doc) - else - flow_session[:matcher_pii_from_doc] = pii_from_doc - end + # do resolution first to prevent ssn time/discovery. resolution time order > than db call + result = perform_resolution(pii_from_doc) + result = check_ssn(pii_from_doc) if result.success? + summarize_result_and_throttle_failures(result) end private - def success(pii_from_doc) - result = perform_resolution(pii_from_doc) - if result.success? - step_successful(pii_from_doc) - else - flow_session[:matcher_pii_from_doc] = pii_from_doc - end + def summarize_result_and_throttle_failures(summary_result) + summary_result.success? ? summary_result : idv_failure(summary_result) end - def step_successful(pii_from_doc) - mark_step_complete(:doc_failed) # skip doc failed - save_legacy_state(pii_from_doc) + def check_ssn(pii_from_doc) + result = Idv::SsnForm.new(current_user).submit(ssn: pii_from_doc[:ssn]) + save_legacy_state(pii_from_doc) if result.success? + result end def save_legacy_state(pii_from_doc) diff --git a/app/view_models/account_show.rb b/app/view_models/account_show.rb index 7236d00ab9c..a893ee3afa5 100644 --- a/app/view_models/account_show.rb +++ b/app/view_models/account_show.rb @@ -34,7 +34,11 @@ def password_reset_partial def pending_profile_partial if decorated_user.pending_profile_requires_verification? - 'accounts/pending_profile_usps' + if decorated_user.usps_mail_bounced? + 'accounts/pending_profile_bounced_usps' + else + 'accounts/pending_profile_usps' + end else 'shared/null' end diff --git a/app/views/accounts/_pending_profile_bounced_usps.html.slim b/app/views/accounts/_pending_profile_bounced_usps.html.slim new file mode 100644 index 00000000000..20ca46deb8c --- /dev/null +++ b/app/views/accounts/_pending_profile_bounced_usps.html.slim @@ -0,0 +1,3 @@ +.mb4.alert.alert-warning + p = t('account.index.verification.bounced') + p.mb0 = link_to t('account.index.verification.update_address'), idv_usps_path diff --git a/app/views/idv/doc_auth/doc_failed.html.slim b/app/views/idv/doc_auth/doc_failed.html.slim deleted file mode 100644 index c6e21eb4f4f..00000000000 --- a/app/views/idv/doc_auth/doc_failed.html.slim +++ /dev/null @@ -1,23 +0,0 @@ --title t('doc_auth.titles.doc_auth') - -h1.h3.red = t('doc_auth.errors.state_id_fail') - -= form_for('', url: idv_doc_auth_step_path(step: :doc_failed), method: 'PUT', - html: { autocomplete: 'off', role: 'form', class: 'mt2' }) do |f| - - - f.simple_fields_for :doc_auth do - - ul - li = "#{t('doc_auth.forms.first_name')}: #{flow_session[:matcher_pii_from_doc][:first_name]}" - li = "#{t('doc_auth.forms.last_name')}: #{flow_session[:matcher_pii_from_doc][:last_name]}" - li = "#{t('doc_auth.forms.dob')}: #{flow_session[:matcher_pii_from_doc][:dob]}" - li = "#{t('doc_auth.forms.address1')}: #{flow_session[:matcher_pii_from_doc][:address1]}" - li = "#{t('doc_auth.forms.city')}: #{flow_session[:matcher_pii_from_doc][:city]}" - li = "#{t('doc_auth.forms.state')}: #{flow_session[:matcher_pii_from_doc][:state]}" - li = "#{t('doc_auth.forms.zip_code')}: #{flow_session[:matcher_pii_from_doc][:zipcode]}" - li = "#{t('doc_auth.forms.ssn')}: #{flow_session[:matcher_pii_from_doc][:ssn]}" - .mt4 - button type='submit' class='btn btn-primary btn-wide sm-col-6 col-12' - = t('forms.buttons.continue') - -= render 'start_over_or_cancel' diff --git a/app/views/idv/usps/_address_on_file.html.slim b/app/views/idv/usps/_address_on_file.html.slim new file mode 100644 index 00000000000..adc5ce5a2d3 --- /dev/null +++ b/app/views/idv/usps/_address_on_file.html.slim @@ -0,0 +1,2 @@ += button_to @presenter.button, idv_usps_path, method: 'put', + class: 'btn btn-primary btn-wide', form_class: 'inline-block mr2' diff --git a/app/views/idv/usps/_new_address.html.slim b/app/views/idv/usps/_new_address.html.slim new file mode 100644 index 00000000000..8f10b6946b7 --- /dev/null +++ b/app/views/idv/usps/_new_address.html.slim @@ -0,0 +1,23 @@ += simple_form_for(:idv_form, url: idv_usps_path, method: 'POST', + html: { autocomplete: 'off', role: 'form', class: 'mt2' }) do |f| + + = f.input :address1, label: t('idv.form.address1'), wrapper_html: { class: 'mb1' }, + required: true, maxlength: 255 + = f.input :address2, label: t('idv.form.address2'), required: false, maxlength: 255 + = f.input :city, label: t('idv.form.city'), required: true, maxlength: 255 + + .clearfix.mxn1 + .sm-col.sm-col-8.px1 + = f.input :state, collection: us_states_territories, + label: t('idv.form.state'), required: true, + prompt: '- Select -' + + .sm-col.sm-col-4.px1 + / using :tel for mobile numeric keypad + = f.input :zipcode, as: :tel, + label: t('idv.form.zipcode'), required: true, + pattern: '(\d{5}([\-]\d{4})?)', + input_html: { class: 'zipcode' } + .mt0 + button type='submit' class='btn btn-primary btn-wide sm-col-6 col-12' + = t('idv.buttons.mail.resend') diff --git a/app/views/idv/usps/index.html.slim b/app/views/idv/usps/index.html.slim index 785a53b29f3..b3566798ac8 100644 --- a/app/views/idv/usps/index.html.slim +++ b/app/views/idv/usps/index.html.slim @@ -4,15 +4,18 @@ h1.h2 = @presenter.title p - = t('idv.messages.usps.byline') + = @presenter.byline p.my0 strong = t('idv.messages.usps.success') -= button_to @presenter.button, idv_usps_path, method: 'put', - class: 'btn btn-primary btn-wide', form_class: 'inline-block mr2' - +- if @presenter.usps_mail_bounced? + = render 'idv/usps/new_address', presenter: @presenter +- else + = render 'idv/usps/address_on_file', presenter: @presenter +br +br = button_to t('idv.messages.clear_and_start_over'), idv_session_path, method: :delete, class: 'btn btn-link' diff --git a/app/views/user_mailer/undeliverable_address.slim b/app/views/user_mailer/undeliverable_address.slim index e82e87c01d5..51214e8f9ca 100644 --- a/app/views/user_mailer/undeliverable_address.slim +++ b/app/views/user_mailer/undeliverable_address.slim @@ -1,6 +1,6 @@ p.lead == t('.intro', app: link_to(APP_NAME, Figaro.env.mailer_domain_name, class: 'gray')) -p.lead == t('.call_to_action') +p.lead == t('.call_to_action', link: link_to('login.gov', root_url)) table.spacer tbody diff --git a/config/locales/account/en.yml b/config/locales/account/en.yml index 2bec7983f65..fc180aaa28b 100644 --- a/config/locales/account/en.yml +++ b/config/locales/account/en.yml @@ -26,9 +26,11 @@ en: ssn: Social Security Number unknown_location: unknown location verification: - instructions: Your account requires a secret code to be verified. + bounced: The postal service could not deliver the letter to your address. + instructions: Your account requires a confirmation code to be verified. reactivate_button: Enter the code you received via US mail success: Your account has been verified. + update_address: Please update your address to be verified. webauthn: Security key webauthn_add: "+ Add security key" webauthn_confirm_delete: Yes, remove key diff --git a/config/locales/account/es.yml b/config/locales/account/es.yml index e50c2852e4a..680f8d03c54 100644 --- a/config/locales/account/es.yml +++ b/config/locales/account/es.yml @@ -26,9 +26,11 @@ es: ssn: Número de Seguro Social unknown_location: ubicación desconocida verification: - instructions: Su cuenta requiere que un código secreto sea verificado. + bounced: El servicio postal no pudo entregar la carta a su dirección. + instructions: Su cuenta requiere un código de confirmación para ser verificado. reactivate_button: Ingrese el código que recibió por correo postal. success: Su cuenta ha sido verificada. + update_address: Please update your address to be verified. webauthn: Clave de seguridad webauthn_add: "+ Añadir clave de seguridad" webauthn_confirm_delete: Si quitar la llave diff --git a/config/locales/account/fr.yml b/config/locales/account/fr.yml index a0a594b5155..50c46466d26 100644 --- a/config/locales/account/fr.yml +++ b/config/locales/account/fr.yml @@ -28,9 +28,11 @@ fr: ssn: Numéro d'assurance sociale unknown_location: lieu inconnu verification: - instructions: Votre compte requiert la vérification d'un code secret. + bounced: Le service postal n'a pas pu envoyer la lettre à votre adresse. + instructions: Votre compte nécessite un code de confirmation pour être vérifié. reactivate_button: Entrez le code que vous avez reçu par la poste success: Votre compte a été vérifié. + update_address: Please update your address to be verified. webauthn: Clé de sécurité webauthn_add: "+ Ajouter une clé de sécurité" webauthn_confirm_delete: Oui, supprimer la clé diff --git a/config/locales/doc_auth/en.yml b/config/locales/doc_auth/en.yml index a761e6ba325..5f745bc2cbb 100644 --- a/config/locales/doc_auth/en.yml +++ b/config/locales/doc_auth/en.yml @@ -8,8 +8,6 @@ en: start_over: Start over use_computer: Use your computer use_phone: Use your phone - errors: - state_id_fail: Sorry. We could not verify your information. forms: address1: Address city: City diff --git a/config/locales/doc_auth/es.yml b/config/locales/doc_auth/es.yml index 3218f02ffcc..af9b2f0dffc 100644 --- a/config/locales/doc_auth/es.yml +++ b/config/locales/doc_auth/es.yml @@ -8,9 +8,6 @@ es: start_over: Comenzar de nuevo use_computer: Use your computer use_phone: Use your phone - errors: - state_id_fail: Lo siento. La información de su ID emitida por el estado no coincide - con la información de su número de seguro social. forms: address1: Address city: City diff --git a/config/locales/doc_auth/fr.yml b/config/locales/doc_auth/fr.yml index f6741d36766..cbc23989634 100644 --- a/config/locales/doc_auth/fr.yml +++ b/config/locales/doc_auth/fr.yml @@ -8,10 +8,6 @@ fr: start_over: Recommencer use_computer: Use your computer use_phone: Use your phone - errors: - state_id_fail: Pardon. Les informations de votre identifiant émis par l'état - téléchargé ne correspondent pas aux informations de votre numéro de sécurité - sociale. forms: address1: Address city: City diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml index 5fd824481b4..89a7dc5469b 100644 --- a/config/locales/idv/en.yml +++ b/config/locales/idv/en.yml @@ -160,8 +160,10 @@ en: data to make sure no one else can access it. success: Next, we'll need a phone number. usps: - byline: We will mail a letter with a confirmation code to your verified address - on file. + address_on_file: We will mail a letter with a confirmation code to your verified + address on file. + new_address: We will mail a letter with a confirmation code to the address + you specify. resend: Send me another letter success: It should arrive in 5 to 10 business days. review: diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml index d8ef03e3383..cdd69195e35 100644 --- a/config/locales/idv/es.yml +++ b/config/locales/idv/es.yml @@ -167,8 +167,10 @@ es: sus datos para asegurarse de que nadie más pueda acceder a ellos. success: A continuación, necesitaremos un número de teléfono. usps: - byline: Le enviaremos una carta con un código de confirmación a su dirección - verificada en el archivo. + address_on_file: Le enviaremos una carta con un código de confirmación a su + dirección verificada en el archivo. + new_address: We will mail a letter with a confirmation code to the address + you specify. resend: Envíeme otra carta success: Debe llegar en 5 a 10 días laborales. review: diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml index ed8c535c563..8ec8b9f34a9 100644 --- a/config/locales/idv/fr.yml +++ b/config/locales/idv/fr.yml @@ -180,8 +180,10 @@ fr: crypte vos données pour vous assurer que personne ne peut y accéder. success: Ensuite, nous aurons besoin d'un numéro de téléphone. usps: - byline: Nous posterons une lettre à l'adresse vérifiée dans nos dossiers. + address_on_file: Nous posterons une lettre à l'adresse vérifiée dans nos dossiers. Celle-ci contient un code de confirmation. + new_address: We will mail a letter with a confirmation code to the address + you specify. resend: Envoyez-moi une autre lettre success: Elle devrait arriver dans 5 à 10 jours ouvrables. review: diff --git a/config/locales/user_mailer/en.yml b/config/locales/user_mailer/en.yml index 7877492cb45..d9a509469d3 100644 --- a/config/locales/user_mailer/en.yml +++ b/config/locales/user_mailer/en.yml @@ -108,7 +108,7 @@ en: link_text: Go to %{app} reset_password: If you can't remember your password, go to %{app} to reset it. undeliverable_address: - call_to_action: Please sign in to login.gov and follow instructions. + call_to_action: Please sign into %{link} and follow instructions. help: '' - intro: We were unable to send mail to the address you provided. - subject: Mail sent to your address was undeliverable + intro: We were unable to mail your letter to the address you provided. + subject: The letter sent to your address was undeliverable diff --git a/config/locales/user_mailer/es.yml b/config/locales/user_mailer/es.yml index ef0377b2ed9..1643e019a8e 100644 --- a/config/locales/user_mailer/es.yml +++ b/config/locales/user_mailer/es.yml @@ -111,7 +111,7 @@ es: link_text: Ir a %{app} reset_password: Si no recuerda su contraseña, vaya a %{app} para restablecerla. undeliverable_address: - call_to_action: Por favor, inicie sesión en login.gov y siga las instrucciones. + call_to_action: Por favor inicie sesión en %{link} y siga las instrucciones. help: '' - intro: No hemos podido enviar el correo a la dirección que proporcionó. - subject: El correo enviado a su dirección no se pudo entregar + intro: No pudimos enviar su carta por correo a la dirección que proporcionó. + subject: La carta enviada a su dirección no se pudo entregar diff --git a/config/locales/user_mailer/fr.yml b/config/locales/user_mailer/fr.yml index 296d99f17c5..bc89efaca29 100644 --- a/config/locales/user_mailer/fr.yml +++ b/config/locales/user_mailer/fr.yml @@ -116,7 +116,7 @@ fr: reset_password: Si vous ne vous souvenez plus de votre mot de passe, allez à %{app} pour le réinitialiser. undeliverable_address: - call_to_action: Veuillez vous connecter à login.gov et suivre les instructions. + call_to_action: Veuillez vous connecter à %{link} et suivre les instructions. help: '' - intro: Nous n'avons pas pu envoyer de courrier à l'adresse que vous avez fournie. - subject: Le courrier envoyé à votre adresse était non distribuable. + intro: Nous n'avons pas pu envoyer votre lettre à l'adresse que vous avez indiquée. + subject: La lettre envoyée à votre adresse était non livrable diff --git a/config/routes.rb b/config/routes.rb index 824bf1d5fce..68f4d0204f3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -205,8 +205,12 @@ put '/phone_confirmation' => 'otp_verification#update', as: :nil get '/review' => 'review#new' put '/review' => 'review#create' - get '/session' => 'sessions#new' - put '/session' => 'sessions#create' + if FeatureManagement.doc_auth_exclusive? + get '/session', to: redirect('/verify') + else + get '/session' => 'sessions#new' + put '/session' => 'sessions#create' + end get '/session/success' => 'sessions#success' get '/session/failure/:reason' => 'sessions#failure', as: :session_failure delete '/session' => 'sessions#destroy' @@ -230,6 +234,7 @@ scope '/verify', module: 'idv', as: 'idv' do get '/usps' => 'usps#index' put '/usps' => 'usps#create' + post '/usps' => 'usps#update' end end diff --git a/spec/features/idv/doc_auth/doc_failed_step_spec.rb b/spec/features/idv/doc_auth/doc_failed_step_spec.rb deleted file mode 100644 index cc412df6aaf..00000000000 --- a/spec/features/idv/doc_auth/doc_failed_step_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'rails_helper' - -feature 'doc auth fail step' do - include IdvStepHelper - include DocAuthHelper - - before do - enable_doc_auth - complete_doc_auth_steps_before_doc_failed_step - end - - it 'is on the correct page' do - expect(page).to have_current_path(idv_doc_auth_doc_failed_step) - expect(page).to have_content(t('doc_auth.errors.state_id_fail')) - end - - it 'starts over if the user selects continue' do - click_idv_continue - - expect(page).to have_current_path(idv_doc_auth_welcome_step) - end -end diff --git a/spec/features/idv/doc_auth/verify_step_spec.rb b/spec/features/idv/doc_auth/verify_step_spec.rb index 48a8195e0c2..edab402fe19 100644 --- a/spec/features/idv/doc_auth/verify_step_spec.rb +++ b/spec/features/idv/doc_auth/verify_step_spec.rb @@ -4,6 +4,7 @@ include IdvStepHelper include DocAuthHelper + let(:max_attempts) { Idv::Attempter.idv_max_attempts } before do enable_doc_auth complete_doc_auth_steps_before_verify_step @@ -34,11 +35,11 @@ it 'does not proceed to the next page if resolution fails' do complete_doc_auth_steps_before_ssn_step - fill_out_ssn_form_with_known_bad_ssn + fill_out_ssn_form_with_ssn_that_fails_resolution click_idv_continue click_idv_continue - expect(page).to have_current_path(idv_doc_auth_doc_failed_step) + expect(page).to have_current_path(idv_session_failure_path(reason: :warning)) end it 'does not proceed to the next page if ssn is a duplicate' do @@ -47,6 +48,32 @@ click_idv_continue click_idv_continue - expect(page).to have_current_path(idv_doc_auth_doc_failed_step) + expect(page).to have_current_path(idv_session_failure_path(reason: :warning)) + end + + it 'throttles resolution' do + complete_doc_auth_steps_before_ssn_step + fill_out_ssn_form_with_ssn_that_fails_resolution + click_idv_continue + (max_attempts - 1).times do + click_idv_continue + expect(page).to have_current_path(idv_session_failure_path(reason: :warning)) + visit idv_doc_auth_verify_step + end + click_idv_continue + expect(page).to have_current_path(idv_session_failure_path(reason: :fail)) + end + + it 'throttles dup ssn' do + complete_doc_auth_steps_before_ssn_step + fill_out_ssn_form_with_duplicate_ssn + click_idv_continue + (max_attempts - 1).times do + click_idv_continue + expect(page).to have_current_path(idv_session_failure_path(reason: :warning)) + visit idv_doc_auth_verify_step + end + click_idv_continue + expect(page).to have_current_path(idv_session_failure_path(reason: :fail)) end end diff --git a/spec/features/saml/loa3_sso_spec.rb b/spec/features/saml/loa3_sso_spec.rb index 8032e0c07a0..b3a3db2bb02 100644 --- a/spec/features/saml/loa3_sso_spec.rb +++ b/spec/features/saml/loa3_sso_spec.rb @@ -3,6 +3,7 @@ feature 'LOA3 Single Sign On' do include SamlAuthHelper include IdvHelper + include DocAuthHelper def perform_id_verification_with_usps_without_confirming_code(user) allow(FeatureManagement).to receive(:prefill_otp_codes?).and_return(true) @@ -24,6 +25,18 @@ def perform_id_verification_with_usps_without_confirming_code(user) click_link t('idv.buttons.continue_plain') end + def mock_usps_mail_bounced + allow_any_instance_of(UserDecorator).to receive(:usps_mail_bounced?).and_return(true) + end + + def update_mailing_address + click_on t('idv.buttons.mail.resend') + fill_in :user_password, with: user.password + click_continue + click_acknowledge_personal_key + click_link t('idv.buttons.continue_plain') + end + def sign_out_user first(:link, t('links.sign_out')).click click_submit_default @@ -70,7 +83,6 @@ def sign_out_user create( :profile, deactivation_reason: :verification_pending, - phone_confirmed: phone_confirmed, pii: { ssn: '6666', dob: '1920-01-01' }, ) end @@ -121,6 +133,51 @@ def sign_out_user expect(current_path).to eq(idv_come_back_later_path) end end + + context 'provides an option to update address if undeliverable' do + it 'allows the user to update the address' do + user = create(:user, :signed_up) + + perform_id_verification_with_usps_without_confirming_code(user) + + expect(current_path).to eq account_path + + mock_usps_mail_bounced + visit account_path + click_link(t('account.index.verification.update_address')) + + expect(current_path).to eq idv_usps_path + + fill_out_address_form_fail + click_on t('idv.buttons.mail.resend') + + fill_out_address_form_ok + update_mailing_address + end + + it 'throttles resolution' do + user = create(:user, :signed_up) + + perform_id_verification_with_usps_without_confirming_code(user) + + expect(current_path).to eq account_path + + mock_usps_mail_bounced + visit account_path + click_link(t('account.index.verification.update_address')) + + expect(current_path).to eq idv_usps_path + fill_out_address_form_resolution_fail + click_on t('idv.buttons.mail.resend') + expect(current_path).to eq idv_usps_path + expect(page).to have_content(t('idv.failure.sessions.heading')) + + fill_out_address_form_resolution_fail + click_on t('idv.buttons.mail.resend') + expect(current_path).to eq idv_usps_path + expect(page).to have_content(strip_tags(t('idv.failure.sessions.fail'))) + end + end end context 'returning to verify after canceling during the same session' do diff --git a/spec/support/controller_helper.rb b/spec/support/controller_helper.rb index 40a5f1b18ff..d557bbe3577 100644 --- a/spec/support/controller_helper.rb +++ b/spec/support/controller_helper.rb @@ -54,6 +54,10 @@ def stub_decorated_user_with_pending_profile(user) decorated_user end + def stub_usps_mail_bounced(decorated_user) + allow(decorated_user).to receive(:usps_mail_bounced?).and_return(true) + end + def stub_identity(user, params) Identity.new(params.merge(user: user)).save end diff --git a/spec/support/features/doc_auth_helper.rb b/spec/support/features/doc_auth_helper.rb index 4c9d7366a5f..9f3e4374d1d 100644 --- a/spec/support/features/doc_auth_helper.rb +++ b/spec/support/features/doc_auth_helper.rb @@ -43,7 +43,7 @@ def fill_out_ssn_form_with_duplicate_ssn fill_in 'doc_auth_ssn', with: '123-45-6666' end - def fill_out_ssn_form_with_known_bad_ssn + def fill_out_ssn_form_with_ssn_that_fails_resolution fill_in 'doc_auth_ssn', with: '123-45-6666' end @@ -91,10 +91,6 @@ def idv_doc_auth_verify_step idv_doc_auth_step_path(step: :verify) end - def idv_doc_auth_doc_failed_step - idv_doc_auth_step_path(step: :doc_failed) - end - def idv_doc_auth_self_image_step idv_doc_auth_step_path(step: :self_image) end @@ -169,14 +165,6 @@ def complete_doc_auth_steps_before_verify_step(user = user_with_2fa) click_idv_continue end - def complete_doc_auth_steps_before_doc_failed_step(user = user_with_2fa) - complete_doc_auth_steps_before_verify_step(user) - - allow_any_instance_of(Idv::Agent).to receive(:proof). - and_return(success: false, errors: {}) - click_idv_continue - end - def complete_doc_auth_steps_before_self_image_step(user = user_with_2fa) complete_doc_auth_steps_before_doc_success_step(user) click_idv_continue @@ -216,6 +204,7 @@ def mock_assure_id_fail def enable_doc_auth allow(FeatureManagement).to receive(:doc_auth_enabled?).and_return(true) + allow(FeatureManagement).to receive(:doc_auth_exclusive?).and_return(true) end def attach_image @@ -236,6 +225,13 @@ def fill_out_address_form_ok fill_in 'idv_form_zipcode', with: '66044' end + def fill_out_address_form_resolution_fail + fill_in 'idv_form_address1', with: '123 Main St' + fill_in 'idv_form_city', with: 'Nowhere' + select 'Virginia', from: 'idv_form_state' + fill_in 'idv_form_zipcode', with: '00000' + end + def fill_out_address_form_fail fill_in 'idv_form_address1', with: '123 Main St' fill_in 'idv_form_city', with: 'Nowhere' diff --git a/spec/views/idv/usps/index.html.slim_spec.rb b/spec/views/idv/usps/index.html.slim_spec.rb index 630f9641e1d..a7811ec8c41 100644 --- a/spec/views/idv/usps/index.html.slim_spec.rb +++ b/spec/views/idv/usps/index.html.slim_spec.rb @@ -13,6 +13,8 @@ expect(usps_presenter).to receive(:title) expect(usps_presenter).to receive(:button) expect(usps_presenter).to receive(:cancel_path) + expect(usps_presenter).to receive(:byline) + expect(usps_presenter).to receive(:usps_mail_bounced?) render end