diff --git a/app/controllers/concerns/idv/document_capture_concern.rb b/app/controllers/concerns/idv/document_capture_concern.rb index fc8078c443f..f114c76f2fd 100644 --- a/app/controllers/concerns/idv/document_capture_concern.rb +++ b/app/controllers/concerns/idv/document_capture_concern.rb @@ -43,7 +43,6 @@ def extract_pii_from_doc(user, response, store_in_session: false) if store_in_session idv_session.pii_from_doc ||= {} idv_session.pii_from_doc.merge!(pii_from_doc) - idv_session.clear_applicant! end end diff --git a/app/controllers/concerns/idv/verify_info_concern.rb b/app/controllers/concerns/idv/verify_info_concern.rb index fb2e23a0efa..fa84a187410 100644 --- a/app/controllers/concerns/idv/verify_info_concern.rb +++ b/app/controllers/concerns/idv/verify_info_concern.rb @@ -21,8 +21,6 @@ def shared_update document_capture_session.requested_at = Time.zone.now idv_session.verify_info_step_document_capture_session_uuid = document_capture_session.uuid - idv_session.vendor_phone_confirmation = false - idv_session.user_phone_confirmation = false # proof_resolution job expects these values pii[:uuid_prefix] = ServiceProvider.find_by(issuer: sp_session[:issuer])&.app_id @@ -164,7 +162,6 @@ def process_async_state(current_async_state) return if confirm_not_rate_limited_after_doc_auth if current_async_state.none? - idv_session.invalidate_verify_info_step! render :show elsif current_async_state.missing? analytics.idv_proofing_resolution_result_missing @@ -172,7 +169,6 @@ def process_async_state(current_async_state) render :show delete_async - idv_session.invalidate_verify_info_step! log_idv_verification_submitted_event( success: false, @@ -217,12 +213,9 @@ def async_state_done(current_async_state) save_threatmetrix_status(form_response) move_applicant_to_idv_session idv_session.mark_verify_info_step_complete! - idv_session.invalidate_steps_after_verify_info! flash[:success] = t('doc_auth.forms.doc_success') redirect_to next_step_url - else - idv_session.invalidate_verify_info_step! end analytics.idv_doc_auth_verify_proofing_results(**analytics_arguments, **form_response.to_h) end diff --git a/app/controllers/concerns/idv_step_concern.rb b/app/controllers/concerns/idv_step_concern.rb index 8cb8f48d6d1..537b8cb33be 100644 --- a/app/controllers/concerns/idv_step_concern.rb +++ b/app/controllers/concerns/idv_step_concern.rb @@ -54,7 +54,7 @@ def redirect_for_mail_only end def pii_from_user - flow_session['pii_from_user'] + user_session.dig('idv/in_person', 'pii_from_user') end def flow_path @@ -78,27 +78,6 @@ def confirm_hybrid_handoff_needed private - def confirm_verify_info_step_complete - return if idv_session.verify_info_step_complete? - - if current_user.has_in_person_enrollment? - redirect_to idv_in_person_verify_info_url - else - redirect_to idv_verify_info_url - end - end - - def confirm_verify_info_step_needed - return unless idv_session.verify_info_step_complete? - redirect_to idv_enter_password_url - end - - def confirm_address_step_complete - return if idv_session.phone_or_address_step_complete? - - redirect_to idv_otp_verification_url - end - def extra_analytics_properties extra = { pii_like_keypaths: [ @@ -136,7 +115,6 @@ def confirm_step_allowed def url_for_latest_step step_info = flow_policy.info_for_latest_step - url_for(controller: step_info.controller, action: step_info.action) end diff --git a/app/controllers/idv/address_controller.rb b/app/controllers/idv/address_controller.rb index 0bcfcbf079e..b32b614c573 100644 --- a/app/controllers/idv/address_controller.rb +++ b/app/controllers/idv/address_controller.rb @@ -5,7 +5,6 @@ class AddressController < ApplicationController before_action :confirm_not_rate_limited_after_doc_auth before_action :confirm_step_allowed - before_action :confirm_verify_info_step_needed def new analytics.idv_address_visit @@ -28,9 +27,10 @@ def update def self.step_info Idv::StepInfo.new( key: :address, - controller: controller_name, + controller: self, + action: :new, next_steps: [:verify_info], - preconditions: ->(idv_session:, user:) { idv_session.document_capture_complete? }, + preconditions: ->(idv_session:, user:) { idv_session.remote_document_capture_complete? }, undo_step: ->(idv_session:, user:) {}, ) end diff --git a/app/controllers/idv/agreement_controller.rb b/app/controllers/idv/agreement_controller.rb index a052c2f5395..2ae35f34da2 100644 --- a/app/controllers/idv/agreement_controller.rb +++ b/app/controllers/idv/agreement_controller.rb @@ -6,7 +6,6 @@ class AgreementController < ApplicationController before_action :confirm_not_rate_limited before_action :confirm_step_allowed - before_action :confirm_verify_info_step_needed def show analytics.idv_doc_auth_agreement_visited(**analytics_arguments) @@ -47,7 +46,7 @@ def update def self.step_info Idv::StepInfo.new( key: :agreement, - controller: controller_name, + controller: self, next_steps: [:hybrid_handoff, :document_capture, :phone_question, :how_to_verify], preconditions: ->(idv_session:, user:) { idv_session.welcome_visited }, undo_step: ->(idv_session:, user:) do diff --git a/app/controllers/idv/by_mail/request_letter_controller.rb b/app/controllers/idv/by_mail/request_letter_controller.rb index 739e33b5a13..8a0b12a7a50 100644 --- a/app/controllers/idv/by_mail/request_letter_controller.rb +++ b/app/controllers/idv/by_mail/request_letter_controller.rb @@ -6,8 +6,8 @@ class RequestLetterController < ApplicationController skip_before_action :confirm_no_pending_gpo_profile include Idv::StepIndicatorConcern - before_action :confirm_user_completed_idv_profile_step before_action :confirm_mail_not_rate_limited + before_action :confirm_step_allowed before_action :confirm_profile_not_too_old def index @@ -23,6 +23,7 @@ def index end def create + clear_future_steps! update_tracking idv_session.address_verification_mechanism = :gpo @@ -41,6 +42,19 @@ def gpo_mail_service @gpo_mail_service ||= Idv::GpoMail.new(current_user) end + def self.step_info + Idv::StepInfo.new( + key: :request_letter, + controller: self, + action: :index, + next_steps: [:enter_password], + preconditions: ->(idv_session:, user:) do + idv_session.verify_info_step_complete? || user.gpo_verification_pending_profile? + end, + undo_step: ->(idv_session:, user:) { idv_session.address_verification_mechanism = nil }, + ) + end + private def confirm_profile_not_too_old @@ -85,15 +99,6 @@ def confirm_mail_not_rate_limited redirect_to idv_enter_password_url if gpo_mail_service.rate_limited? end - def confirm_user_completed_idv_profile_step - # If the user has a pending profile, they may have completed idv in a - # different session and need a letter resent now - return if current_user.gpo_verification_pending_profile? - return if idv_session.verify_info_step_complete? - - redirect_to idv_verify_info_url - end - def resend_letter analytics.idv_gpo_address_letter_enqueued( enqueued_at: Time.zone.now, diff --git a/app/controllers/idv/document_capture_controller.rb b/app/controllers/idv/document_capture_controller.rb index c57d4f55329..32c349780cc 100644 --- a/app/controllers/idv/document_capture_controller.rb +++ b/app/controllers/idv/document_capture_controller.rb @@ -9,7 +9,6 @@ class DocumentCaptureController < ApplicationController before_action :confirm_not_rate_limited, except: [:update] before_action :confirm_step_allowed - before_action :confirm_verify_info_step_needed before_action :override_csp_to_allow_acuant def show @@ -59,8 +58,8 @@ def extra_view_variables def self.step_info Idv::StepInfo.new( key: :document_capture, - controller: controller_name, - next_steps: [:ssn], # :ipp_state_id + controller: self, + next_steps: [:ssn, :ipp_ssn], # :ipp_state_id preconditions: ->(idv_session:, user:) { idv_session.flow_path == 'standard' }, undo_step: ->(idv_session:, user:) do idv_session.pii_from_doc = nil diff --git a/app/controllers/idv/enter_password_controller.rb b/app/controllers/idv/enter_password_controller.rb index 3f3abe05733..48fd51fc886 100644 --- a/app/controllers/idv/enter_password_controller.rb +++ b/app/controllers/idv/enter_password_controller.rb @@ -4,8 +4,7 @@ class EnterPasswordController < ApplicationController include IdvStepConcern include StepIndicatorConcern - before_action :confirm_verify_info_step_complete - before_action :confirm_address_step_complete + before_action :confirm_step_allowed before_action :confirm_no_profile_yet before_action :confirm_current_password, only: [:create] @@ -29,6 +28,7 @@ def new end def create + clear_future_steps! irs_attempts_api_tracker.idv_password_entered(success: true) init_profile @@ -72,6 +72,19 @@ def step_indicator_step :get_a_letter end + def self.step_info + Idv::StepInfo.new( + key: :enter_password, + controller: self, + action: :new, + next_steps: [FlowPolicy::FINAL], + preconditions: ->(idv_session:, user:) do + idv_session.phone_or_address_step_complete? + end, + undo_step: ->(idv_session:, user:) {}, + ) + end + private def title diff --git a/app/controllers/idv/how_to_verify_controller.rb b/app/controllers/idv/how_to_verify_controller.rb index cab3caa2a62..919a5a405af 100644 --- a/app/controllers/idv/how_to_verify_controller.rb +++ b/app/controllers/idv/how_to_verify_controller.rb @@ -5,7 +5,6 @@ class HowToVerifyController < ApplicationController include RenderConditionConcern before_action :confirm_step_allowed - before_action :confirm_verify_info_step_needed check_or_render_not_found -> { self.class.enabled? } @@ -47,7 +46,7 @@ def update def self.step_info Idv::StepInfo.new( key: :how_to_verify, - controller: controller_name, + controller: self, next_steps: [:hybrid_handoff, :document_capture], preconditions: ->(idv_session:, user:) do self.enabled? && idv_session.idv_consent_given diff --git a/app/controllers/idv/hybrid_handoff_controller.rb b/app/controllers/idv/hybrid_handoff_controller.rb index dc55e73043f..b47e9c92924 100644 --- a/app/controllers/idv/hybrid_handoff_controller.rb +++ b/app/controllers/idv/hybrid_handoff_controller.rb @@ -7,7 +7,6 @@ class HybridHandoffController < ApplicationController include PhoneQuestionAbTestConcern before_action :confirm_not_rate_limited - before_action :confirm_verify_info_step_needed before_action :confirm_step_allowed before_action :confirm_hybrid_handoff_needed, only: :show before_action :maybe_redirect_for_phone_question_ab_test, only: :show @@ -40,7 +39,7 @@ def update def self.step_info Idv::StepInfo.new( key: :hybrid_handoff, - controller: controller_name, + controller: self, next_steps: [:link_sent, :document_capture], preconditions: ->(idv_session:, user:) { idv_session.idv_consent_given }, undo_step: ->(idv_session:, user:) do diff --git a/app/controllers/idv/in_person/ssn_controller.rb b/app/controllers/idv/in_person/ssn_controller.rb index e8e0c0ca684..a4a1e079f55 100644 --- a/app/controllers/idv/in_person/ssn_controller.rb +++ b/app/controllers/idv/in_person/ssn_controller.rb @@ -8,7 +8,6 @@ class SsnController < ApplicationController include ThreatMetrixConcern before_action :confirm_not_rate_limited_after_doc_auth - before_action :confirm_verify_info_step_needed before_action :confirm_in_person_address_step_complete before_action :confirm_repeat_ssn, only: :show before_action :override_csp_for_threat_metrix @@ -36,6 +35,7 @@ def show end def update + clear_future_steps! ssn_form = Idv::SsnFormatForm.new(idv_session.ssn) form_response = ssn_form.submit(params.require(:doc_auth).permit(:ssn)) @ssn_presenter = Idv::SsnPresenter.new( @@ -53,7 +53,6 @@ def update if form_response.success? idv_session.ssn = params[:doc_auth][:ssn] - idv_session.invalidate_steps_after_ssn! redirect_to next_url else flash[:error] = form_response.first_error_message @@ -61,6 +60,19 @@ def update end end + def self.step_info + Idv::StepInfo.new( + key: :ipp_ssn, + controller: self, + next_steps: [:ipp_verify_info], + preconditions: ->(idv_session:, user:) { idv_session.ipp_document_capture_complete? }, + undo_step: ->(idv_session:, user:) do + idv_session.ssn = nil + idv_session.threatmetrix_session_id = nil + end, + ) + end + private def flow_session @@ -88,7 +100,7 @@ def analytics_arguments end def confirm_in_person_address_step_complete - return if pii_from_user && pii_from_user[:address1].present? + return if flow_session[:pii_from_user] && flow_session[:pii_from_user][:address1].present? if IdentityConfig.store.in_person_residential_address_controller_enabled redirect_to idv_in_person_proofing_address_url else diff --git a/app/controllers/idv/in_person/verify_info_controller.rb b/app/controllers/idv/in_person/verify_info_controller.rb index 5ace3f505fe..7e8d05fc4f1 100644 --- a/app/controllers/idv/in_person/verify_info_controller.rb +++ b/app/controllers/idv/in_person/verify_info_controller.rb @@ -9,11 +9,11 @@ class VerifyInfoController < ApplicationController before_action :confirm_not_rate_limited_after_doc_auth, except: [:show] before_action :confirm_ssn_step_complete - before_action :confirm_verify_info_step_needed def show @step_indicator_steps = step_indicator_steps @ssn = idv_session.ssn + @pii = pii analytics.idv_doc_auth_verify_visited(**analytics_arguments) Funnel::DocAuth::RegisterStep.new(current_user.id, sp_session[:issuer]). @@ -23,6 +23,8 @@ def show end def update + clear_future_steps! + idv_session.invalidate_verify_info_step! success = shared_update if success @@ -30,6 +32,23 @@ def update end end + def self.step_info + Idv::StepInfo.new( + key: :ipp_verify_info, + controller: self, + next_steps: [:phone], + preconditions: ->(idv_session:, user:) do + idv_session.ssn && idv_session.ipp_document_capture_complete? + end, + undo_step: ->(idv_session:, user:) do + idv_session.resolution_successful = nil + idv_session.verify_info_step_document_capture_session_uuid = nil + idv_session.threatmetrix_review_status = nil + idv_session.applicant = nil + end, + ) + end + private def flow_param @@ -41,11 +60,11 @@ def flow_param # between various ID types and driver's license is the most common one that will # be supported. See also LG-3852 and related findings document. def set_state_id_type - pii[:state_id_type] = 'drivers_license' unless invalid_state? + pii_from_user[:state_id_type] = 'drivers_license' unless invalid_state? end def invalid_state? - pii.blank? + pii_from_user.blank? end def prev_url @@ -53,7 +72,7 @@ def prev_url end def pii - @pii = flow_session[:pii_from_user] + user_session.dig('idv/in_person', :pii_from_user) end # override IdvSession concern diff --git a/app/controllers/idv/link_sent_controller.rb b/app/controllers/idv/link_sent_controller.rb index f32b78ff4ae..6a07d2550ae 100644 --- a/app/controllers/idv/link_sent_controller.rb +++ b/app/controllers/idv/link_sent_controller.rb @@ -8,7 +8,6 @@ class LinkSentController < ApplicationController before_action :confirm_not_rate_limited before_action :confirm_step_allowed - before_action :confirm_verify_info_step_needed def show analytics.idv_doc_auth_link_sent_visited(**analytics_arguments) @@ -44,7 +43,7 @@ def extra_view_variables def self.step_info Idv::StepInfo.new( key: :link_sent, - controller: controller_name, + controller: self, next_steps: [:ssn], preconditions: ->(idv_session:, user:) { idv_session.flow_path == 'hybrid' }, undo_step: ->(idv_session:, user:) do diff --git a/app/controllers/idv/otp_verification_controller.rb b/app/controllers/idv/otp_verification_controller.rb index 9c2f238e048..792734c0f2a 100644 --- a/app/controllers/idv/otp_verification_controller.rb +++ b/app/controllers/idv/otp_verification_controller.rb @@ -1,13 +1,12 @@ module Idv class OtpVerificationController < ApplicationController include Idv::AvailabilityConcern - include IdvSession + include IdvStepConcern include StepIndicatorConcern include PhoneOtpRateLimitable before_action :confirm_two_factor_authenticated - before_action :confirm_step_needed - before_action :confirm_otp_sent + before_action :confirm_step_allowed before_action :set_code before_action :set_otp_verification_presenter @@ -18,6 +17,7 @@ def show end def update + clear_future_steps! result = phone_confirmation_otp_verification_form.submit(code: params[:code]) analytics.idv_phone_confirmation_otp_submitted(**result.to_h) @@ -36,18 +36,17 @@ def update end end - private - - def confirm_step_needed - return unless idv_session.user_phone_confirmation - redirect_to idv_enter_password_url + def self.step_info + Idv::StepInfo.new( + key: :otp_verification, + controller: self, + next_steps: [:enter_password], + preconditions: ->(idv_session:, user:) { idv_session.phone_otp_sent? }, + undo_step: ->(idv_session:, user:) { idv_session.user_phone_confirmation = nil }, + ) end - def confirm_otp_sent - return if idv_session.user_phone_confirmation_session.present? - - redirect_to idv_phone_url - end + private def set_code return unless FeatureManagement.prefill_otp_codes? diff --git a/app/controllers/idv/phone_controller.rb b/app/controllers/idv/phone_controller.rb index e43c4f54ba1..57ffbee41eb 100644 --- a/app/controllers/idv/phone_controller.rb +++ b/app/controllers/idv/phone_controller.rb @@ -9,8 +9,7 @@ class PhoneController < ApplicationController attr_reader :idv_form before_action :confirm_not_rate_limited_for_phone_address_verification, except: [:new] - before_action :confirm_verify_info_step_complete - before_action :confirm_step_needed + before_action :confirm_step_allowed before_action :set_idv_form def new @@ -41,6 +40,7 @@ def new end def create + clear_future_steps! result = idv_form.submit(step_params) Funnel::DocAuth::RegisterStep.new(current_user.id, current_sp&.issuer). call(:verify_phone, :update, result.success?) @@ -59,6 +59,23 @@ def create end end + def self.step_info + Idv::StepInfo.new( + key: :phone, + controller: self, + action: :new, + next_steps: [:otp_verification], + preconditions: ->(idv_session:, user:) { idv_session.verify_info_step_complete? }, + undo_step: ->(idv_session:, user:) do + idv_session.vendor_phone_confirmation = nil + idv_session.address_verification_mechanism = nil + idv_session.idv_phone_step_document_capture_session_uuid = nil + idv_session.user_phone_confirmation_session = nil + idv_session.previous_phone_step_params = nil + end, + ) + end + private def rate_limiter @@ -133,10 +150,6 @@ def step_params params.require(:idv_phone_form).permit(:phone, :international_code, :otp_delivery_preference) end - def confirm_step_needed - redirect_to_next_step if idv_session.user_phone_confirmation == true - end - def set_idv_form @idv_form = Idv::PhoneForm.new( user: current_user, diff --git a/app/controllers/idv/phone_question_controller.rb b/app/controllers/idv/phone_question_controller.rb index e1c711b77f5..06431500e10 100644 --- a/app/controllers/idv/phone_question_controller.rb +++ b/app/controllers/idv/phone_question_controller.rb @@ -6,7 +6,6 @@ class PhoneQuestionController < ApplicationController include StepIndicatorConcern before_action :confirm_not_rate_limited - before_action :confirm_verify_info_step_needed before_action :confirm_step_allowed before_action :confirm_hybrid_handoff_needed, only: :show @@ -36,7 +35,7 @@ def phone_without_camera def self.step_info Idv::StepInfo.new( key: :phone_question, - controller: controller_name, + controller: self, next_steps: [:hybrid_handoff, :document_capture], preconditions: ->(idv_session:, user:) do AbTests::IDV_PHONE_QUESTION.bucket(user.uuid) == :show_phone_question && diff --git a/app/controllers/idv/ssn_controller.rb b/app/controllers/idv/ssn_controller.rb index 891dd7a35a5..5306a836d96 100644 --- a/app/controllers/idv/ssn_controller.rb +++ b/app/controllers/idv/ssn_controller.rb @@ -8,7 +8,6 @@ class SsnController < ApplicationController before_action :confirm_not_rate_limited_after_doc_auth before_action :confirm_step_allowed - before_action :confirm_verify_info_step_needed before_action :override_csp_for_threat_metrix attr_reader :ssn_presenter @@ -51,7 +50,6 @@ def update if form_response.success? idv_session.ssn = params[:doc_auth][:ssn] - idv_session.invalidate_steps_after_ssn! redirect_to next_url else flash[:error] = form_response.first_error_message @@ -62,9 +60,9 @@ def update def self.step_info Idv::StepInfo.new( key: :ssn, - controller: controller_name, + controller: self, next_steps: [:verify_info], - preconditions: ->(idv_session:, user:) { idv_session.document_capture_complete? }, + preconditions: ->(idv_session:, user:) { idv_session.remote_document_capture_complete? }, undo_step: ->(idv_session:, user:) do idv_session.ssn = nil idv_session.threatmetrix_session_id = nil diff --git a/app/controllers/idv/verify_info_controller.rb b/app/controllers/idv/verify_info_controller.rb index 5eb287561c8..59f43359b77 100644 --- a/app/controllers/idv/verify_info_controller.rb +++ b/app/controllers/idv/verify_info_controller.rb @@ -8,7 +8,6 @@ class VerifyInfoController < ApplicationController before_action :confirm_not_rate_limited_after_doc_auth, except: [:show] before_action :confirm_step_allowed - before_action :confirm_verify_info_step_needed def show @step_indicator_steps = step_indicator_steps @@ -25,6 +24,7 @@ def show def update clear_future_steps! + idv_session.invalidate_verify_info_step! success = shared_update if success @@ -41,14 +41,17 @@ def update def self.step_info Idv::StepInfo.new( key: :verify_info, - controller: controller_name, - next_steps: [:success], # [:phone], + controller: self, + next_steps: [:phone], preconditions: ->(idv_session:, user:) do - idv_session.ssn && idv_session.document_capture_complete? + idv_session.ssn && idv_session.remote_document_capture_complete? end, undo_step: ->(idv_session:, user:) do idv_session.resolution_successful = nil idv_session.address_edited = nil + idv_session.verify_info_step_document_capture_session_uuid = nil + idv_session.threatmetrix_review_status = nil + idv_session.applicant = nil end, ) end @@ -74,7 +77,7 @@ def analytics_arguments end def pii - @pii = idv_session.pii_from_doc + idv_session.pii_from_doc end end end diff --git a/app/controllers/idv/welcome_controller.rb b/app/controllers/idv/welcome_controller.rb index 1af9eec8632..c16ea600c45 100644 --- a/app/controllers/idv/welcome_controller.rb +++ b/app/controllers/idv/welcome_controller.rb @@ -5,7 +5,6 @@ class WelcomeController < ApplicationController include StepIndicatorConcern before_action :confirm_not_rate_limited - before_action :confirm_verify_info_step_needed def show analytics.idv_doc_auth_welcome_visited(**analytics_arguments) @@ -32,9 +31,9 @@ def update def self.step_info Idv::StepInfo.new( key: :welcome, - controller: controller_name, + controller: self, next_steps: [:agreement], - preconditions: ->(idv_session:, user:) { true }, + preconditions: ->(idv_session:, user:) { !user.gpo_verification_pending_profile? }, undo_step: ->(idv_session:, user:) do idv_session.welcome_visited = nil idv_session.document_capture_session_uuid = nil diff --git a/app/policies/idv/flow_policy.rb b/app/policies/idv/flow_policy.rb index 01936ee9f61..c32175ae3c2 100644 --- a/app/policies/idv/flow_policy.rb +++ b/app/policies/idv/flow_policy.rb @@ -2,6 +2,8 @@ module Idv class FlowPolicy attr_reader :idv_session, :user + FINAL = :final + def initialize(idv_session:, user:) @idv_session = idv_session @user = user @@ -9,7 +11,7 @@ def initialize(idv_session:, user:) def controller_allowed?(controller:) controller_name = controller < ApplicationController ? - controller.controller_name : controller + StepInfo.full_controller_name(controller) : controller key = controller_to_key(controller: controller_name) step_allowed?(key: key) end @@ -20,7 +22,7 @@ def info_for_latest_step def undo_future_steps_from_controller!(controller:) controller_name = controller < ApplicationController ? - controller.controller_name : controller + StepInfo.full_controller_name(controller) : controller key = controller_to_key(controller: controller_name) undo_future_steps!(key: key) end @@ -29,7 +31,7 @@ def undo_future_steps_from_controller!(controller:) def latest_step(current_step: :root) return nil if steps[current_step]&.next_steps.blank? - return current_step if steps[current_step].next_steps == [:success] + return current_step if steps[current_step].next_steps == [FINAL] steps[current_step].next_steps.each do |key| if step_allowed?(key: key) @@ -43,8 +45,8 @@ def steps { root: Idv::StepInfo.new( key: :root, - controller: AccountsController.controller_name, - next_steps: [:welcome], + controller: AccountsController, + next_steps: [:welcome, :request_letter], preconditions: ->(idv_session:, user:) { true }, undo_step: ->(idv_session:, user:) { true }, ), @@ -56,8 +58,14 @@ def steps link_sent: Idv::LinkSentController.step_info, document_capture: Idv::DocumentCaptureController.step_info, ssn: Idv::SsnController.step_info, + ipp_ssn: Idv::InPerson::SsnController.step_info, verify_info: Idv::VerifyInfoController.step_info, + ipp_verify_info: Idv::InPerson::VerifyInfoController.step_info, address: Idv::AddressController.step_info, + phone: Idv::PhoneController.step_info, + otp_verification: Idv::OtpVerificationController.step_info, + request_letter: Idv::ByMail::RequestLetterController.step_info, + enter_password: Idv::EnterPasswordController.step_info, } end @@ -66,13 +74,13 @@ def step_allowed?(key:) end def undo_steps_from!(key:) - return if key == :success - - steps[key].undo_step.call(idv_session: idv_session, user: user) + return if key == FINAL steps[key].next_steps.each do |next_step| undo_steps_from!(key: next_step) end + + steps[key].undo_step.call(idv_session: idv_session, user: user) end def undo_future_steps!(key:) diff --git a/app/policies/idv/step_info.rb b/app/policies/idv/step_info.rb index 598da807209..e5b06df542c 100644 --- a/app/policies/idv/step_info.rb +++ b/app/policies/idv/step_info.rb @@ -8,17 +8,29 @@ class StepInfo validates :action, presence: true validate :next_steps_validation, :preconditions_validation, :undo_step_validation - def initialize(key:, controller:, next_steps:, preconditions:, undo_step:, action: :show) + def initialize( + key:, + controller:, + next_steps:, + preconditions:, + undo_step:, + action: :show + ) @key = key - @controller = controller - @action = action + @controller = Idv::StepInfo.full_controller_name(controller) @next_steps = next_steps @preconditions = preconditions @undo_step = undo_step + @action = action raise ArgumentError unless valid? end + def self.full_controller_name(controller) + # Need an absolute path for url_for if controller is in a different module + "/#{controller.name.underscore.gsub('_controller', '')}" + end + def next_steps_validation unless next_steps.is_a?(Array) errors.add(:next_steps, type: :invalid_argument, message: 'next_steps must be an Array') diff --git a/app/services/idv/session.rb b/app/services/idv/session.rb index 880e319d58f..a018810327b 100644 --- a/app/services/idv/session.rb +++ b/app/services/idv/session.rb @@ -161,13 +161,21 @@ def has_pii_from_user_in_flow_session end def invalidate_in_person_pii_from_user! - if user_session.dig('idv/in_person', :pii_from_user) + if has_pii_from_user_in_flow_session user_session['idv/in_person'][:pii_from_user] = nil end end def document_capture_complete? - pii_from_doc || has_pii_from_user_in_flow_session || verify_info_step_complete? + pii_from_doc || has_pii_from_user_in_flow_session + end + + def remote_document_capture_complete? + pii_from_doc + end + + def ipp_document_capture_complete? + has_pii_from_user_in_flow_session end def verify_info_step_complete? @@ -194,18 +202,6 @@ def address_confirmed! session[:gpo_code_verified] = true end - def invalidate_steps_after_ssn! - # Guard against unvalidated attributes from in-person flow in review controller - clear_applicant! - - invalidate_verify_info_step! - invalidate_phone_step! - end - - def clear_applicant! - session[:applicant] = nil - end - def mark_verify_info_step_complete! session[:resolution_successful] = true end diff --git a/spec/controllers/concerns/idv_step_concern_spec.rb b/spec/controllers/concerns/idv_step_concern_spec.rb index 2bcd478adac..c10546ad65c 100644 --- a/spec/controllers/concerns/idv_step_concern_spec.rb +++ b/spec/controllers/concerns/idv_step_concern_spec.rb @@ -173,118 +173,6 @@ def show end end - describe '#confirm_address_step_complete' do - controller(idv_step_controller_class) do - before_action :confirm_address_step_complete - end - - before(:each) do - sign_in(user) - routes.draw do - get 'show' => 'anonymous#show' - end - end - - context 'the user has completed phone confirmation' do - it 'does not redirect' do - idv_session.vendor_phone_confirmation = true - idv_session.user_phone_confirmation = true - - get :show - - expect(response.body).to eq('Hello') - expect(response.status).to eq(200) - end - end - - context 'the user has not confirmed their phone OTP' do - it 'redirects to OTP confirmation' do - idv_session.vendor_phone_confirmation = true - idv_session.user_phone_confirmation = false - - get :show - - expect(response).to redirect_to(idv_otp_verification_url) - end - end - - context 'the user has not confirmed their phone with the vendor' do - it 'redirects to phone confirmation' do - idv_session.vendor_phone_confirmation = false - idv_session.user_phone_confirmation = false - - get :show - - expect(response).to redirect_to(idv_otp_verification_url) - end - end - - context 'the user has selected GPO for address confirmation' do - it 'does not redirect' do - idv_session.address_verification_mechanism = 'gpo' - - get :show - - expect(response.body).to eq('Hello') - expect(response.status).to eq(200) - end - end - end - - describe '#confirm_verify_info_step_complete' do - controller(idv_step_controller_class) do - before_action :confirm_verify_info_step_complete - end - - before(:each) do - sign_in(user) - routes.draw do - get 'show' => 'anonymous#show' - end - end - - context 'the user has completed the verify info step' do - it 'does not redirect and renders the view' do - idv_session.resolution_successful = true - - get :show - - expect(response.body).to eq('Hello') - expect(response.status).to eq(200) - end - end - - context 'the user has not completed the verify info step' do - it 'redirects to the remote verify info step' do - idv_session.resolution_successful = nil - - get :show - - expect(response).to redirect_to(idv_verify_info_url) - end - end - - context 'the user has not completed the verify info step with an in-person enrollment' do - let(:selected_location_details) do - JSON.parse(UspsInPersonProofing::Mock::Fixtures.enrollment_selected_location_details) - end - - it 'redirects to the in-person verify info step' do - idv_session.resolution_successful = nil - - InPersonEnrollment.find_or_create_by( - user: user, - ).update!( - selected_location_details: selected_location_details, - ) - - get :show - - expect(response).to redirect_to(idv_in_person_verify_info_url) - end - end - end - describe '#confirm_letter_recently_enqueued' do controller(idv_step_controller_class) do before_action :confirm_letter_recently_enqueued diff --git a/spec/controllers/idv/address_controller_spec.rb b/spec/controllers/idv/address_controller_spec.rb index 0b9f0a94e24..1f4f2025213 100644 --- a/spec/controllers/idv/address_controller_spec.rb +++ b/spec/controllers/idv/address_controller_spec.rb @@ -8,7 +8,6 @@ before do stub_sign_in(user) stub_analytics - stub_idv_steps_before_verify_step(user) subject.idv_session.welcome_visited = true subject.idv_session.idv_consent_given = true subject.idv_session.flow_path = 'standard' @@ -32,10 +31,10 @@ subject.idv_session.resolution_successful = true end - it 'redirects to enter_password' do + it 'renders the :new template' do get :new - expect(response).to redirect_to(idv_enter_password_url) + expect(response).to render_template(:new) end end end @@ -67,7 +66,9 @@ it 'updates pii_from_doc in idv_session' do expect do put :update, params: params - end.to change { subject.idv_session.pii_from_doc }.to eql( + end.to change { subject.idv_session.pii_from_doc } + + expect(subject.idv_session.pii_from_doc).to eql( pii_from_doc.merge( { 'address1' => '1234 Main St', diff --git a/spec/controllers/idv/agreement_controller_spec.rb b/spec/controllers/idv/agreement_controller_spec.rb index 36a95181335..16d51d7fd1b 100644 --- a/spec/controllers/idv/agreement_controller_spec.rb +++ b/spec/controllers/idv/agreement_controller_spec.rb @@ -94,9 +94,9 @@ subject.idv_session.resolution_successful = true end - it 'redirects to enter password step' do + it 'renders the show template' do get :show - expect(response).to redirect_to(idv_enter_password_url) + expect(response).to render_template(:show) end end end diff --git a/spec/controllers/idv/by_mail/request_letter_controller_spec.rb b/spec/controllers/idv/by_mail/request_letter_controller_spec.rb index 2e0a7c38980..84398cadfb6 100644 --- a/spec/controllers/idv/by_mail/request_letter_controller_spec.rb +++ b/spec/controllers/idv/by_mail/request_letter_controller_spec.rb @@ -13,6 +13,12 @@ allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end + describe '#step_info' do + it 'returns a valid StepInfo object' do + expect(Idv::ByMail::RequestLetterController.step_info).to be_valid + end + end + describe 'before_actions' do it 'includes authentication before_action' do expect(subject).to have_actions( @@ -138,6 +144,12 @@ stub_verify_steps_one_and_two(user) end + it 'invalidates future steps' do + expect(subject).to receive(:clear_future_steps!) + + put :create + end + it 'sets session to :gpo and redirects' do expect(subject.idv_session.address_verification_mechanism).to be_nil @@ -187,6 +199,11 @@ stub_sign_in(user) stub_user_with_pending_profile(user) allow(user).to receive(:gpo_verification_pending_profile?).and_return(true) + subject.idv_session.welcome_visited = true + subject.idv_session.idv_consent_given = true + subject.idv_session.flow_path = 'standard' + subject.idv_session.resolution_successful = true + subject.idv_session.applicant = Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN end it 'calls the GpoConfirmationMaker to send another letter and redirects' do diff --git a/spec/controllers/idv/document_capture_controller_spec.rb b/spec/controllers/idv/document_capture_controller_spec.rb index 56acad2f715..c25066bca35 100644 --- a/spec/controllers/idv/document_capture_controller_spec.rb +++ b/spec/controllers/idv/document_capture_controller_spec.rb @@ -113,7 +113,7 @@ end context 'verify info step is complete' do - it 'redirects to enter password step' do + it 'renders show' do subject.idv_session.welcome_visited = true subject.idv_session.idv_consent_given = true subject.idv_session.flow_path = 'standard' @@ -123,7 +123,7 @@ get :show - expect(response).to redirect_to(idv_enter_password_url) + expect(response).to render_template :show end end @@ -165,9 +165,11 @@ let(:result) { { success: true, errors: {} } } it 'invalidates future steps' do - expect(subject).to receive(:clear_future_steps!) + subject.idv_session.applicant = Idp::Constants::MOCK_IDV_APPLICANT + expect(subject).to receive(:clear_future_steps!).and_call_original put :update + expect(subject.idv_session.applicant).to be_nil end it 'sends analytics_submitted event' do diff --git a/spec/controllers/idv/enter_password_controller_spec.rb b/spec/controllers/idv/enter_password_controller_spec.rb index b3689158560..0ffd596b141 100644 --- a/spec/controllers/idv/enter_password_controller_spec.rb +++ b/spec/controllers/idv/enter_password_controller_spec.rb @@ -14,25 +14,7 @@ let(:applicant) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE } let(:use_gpo) { false } let(:idv_session) do - idv_session = Idv::Session.new( - user_session: subject.user_session, - current_user: user, - service_provider: nil, - ) - idv_session.resolution_successful = true - - if use_gpo - idv_session.address_verification_mechanism = 'gpo' - idv_session.vendor_phone_confirmation = false - idv_session.user_phone_confirmation = false - else - idv_session.address_verification_mechanism = 'phone' - idv_session.vendor_phone_confirmation = true - idv_session.user_phone_confirmation = true - end - - idv_session.applicant = applicant.with_indifferent_access - idv_session + subject.idv_session end let(:ab_test_args) do @@ -44,7 +26,34 @@ stub_sign_in(user) stub_attempts_tracker allow(@irs_attempts_api_tracker).to receive(:track_event) + allow(IdentityConfig.store).to receive(:usps_mock_fallback).and_return(false) allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) + subject.idv_session.welcome_visited = true + subject.idv_session.idv_consent_given = true + subject.idv_session.flow_path = 'standard' + subject.idv_session.pii_from_doc = Idp::Constants::MOCK_IDV_APPLICANT + subject.idv_session.ssn = Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE[:ssn] + subject.idv_session.resolution_successful = true + subject.idv_session.applicant = Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE + subject.idv_session.resolution_successful = true + + if use_gpo + subject.idv_session.address_verification_mechanism = 'gpo' + subject.idv_session.vendor_phone_confirmation = false + subject.idv_session.user_phone_confirmation = false + else + subject.idv_session.address_verification_mechanism = 'phone' + subject.idv_session.vendor_phone_confirmation = true + subject.idv_session.user_phone_confirmation = true + end + + subject.idv_session.applicant = applicant.with_indifferent_access + end + + describe '#step_info' do + it 'returns a valid StepInfo object' do + expect(Idv::EnterPasswordController.step_info).to be_valid + end end describe 'before_actions' do @@ -52,8 +61,7 @@ expect(subject).to have_actions( :before, :confirm_two_factor_authenticated, - :confirm_verify_info_step_complete, - :confirm_address_step_complete, + :confirm_step_allowed, ) end @@ -75,8 +83,6 @@ def show routes.draw do post 'show' => 'idv/enter_password#show' end - allow(subject).to receive(:idv_session).and_return(idv_session) - allow(@irs_attempts_api_tracker).to receive(:track_event) end context 'user does not provide password' do @@ -117,10 +123,6 @@ def show describe '#new' do context 'user has completed all steps' do - before do - idv_session - end - it 'shows completed session' do get :new @@ -142,8 +144,8 @@ def show context 'user is in gpo flow' do before do - idv_session.vendor_phone_confirmation = false - idv_session.address_verification_mechanism = 'gpo' + subject.idv_session.vendor_phone_confirmation = false + subject.idv_session.address_verification_mechanism = 'gpo' end render_views @@ -260,21 +262,17 @@ def show end end - it 'redirects to the verify info controller if the user has not completed it' do - controller.idv_session.resolution_successful = nil + it 'redirects to phone step if the user has not completed it' do + subject.idv_session.user_phone_confirmation = nil get :new - expect(response).to redirect_to(idv_verify_info_url) + expect(response).to redirect_to(idv_phone_url) end end describe '#create' do context 'user fails to supply correct password' do - before do - idv_session - end - it 'redirects to original path' do put :create, params: { user: { password: 'wrong' } } @@ -294,144 +292,231 @@ def show end end - context 'user has completed all steps' do - before do - idv_session - end + it 'redirects to personal key path' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + + expect(@analytics).to have_logged_event( + :idv_enter_password_submitted, + success: true, + fraud_review_pending: false, + fraud_rejection: false, + gpo_verification_pending: false, + in_person_verification_pending: false, + proofing_components: nil, + deactivation_reason: anything, + **ab_test_args, + ) + expect(@analytics).to have_logged_event( + 'IdV: final resolution', + hash_including(success: true), + ) + expect(response).to redirect_to idv_personal_key_path + end - it 'redirects to personal key path' do - put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + it 'redirects to confirmation path after user presses the back button' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + allow_any_instance_of(User).to receive(:active_profile).and_return(true) + get :new + expect(response).to redirect_to idv_personal_key_path + end - expect(@analytics).to have_logged_event( - :idv_enter_password_submitted, - success: true, - fraud_review_pending: false, - fraud_rejection: false, - gpo_verification_pending: false, - in_person_verification_pending: false, - proofing_components: nil, - deactivation_reason: anything, - **ab_test_args, - ) - expect(@analytics).to have_logged_event( - 'IdV: final resolution', - hash_including(success: true), + it 'tracks irs password entered event (idv_password_entered)' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + + expect(@irs_attempts_api_tracker).to have_received(:track_event).with( + :idv_password_entered, + success: true, + ) + end + + it 'creates Profile with applicant attributes' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + + profile = subject.idv_session.profile + pii = profile.decrypt_pii(ControllerHelper::VALID_PASSWORD) + + expect(pii.zipcode).to eq subject.idv_session.applicant[:zipcode] + + expect(pii.first_name).to eq subject.idv_session.applicant[:first_name] + end + + context 'user picked phone confirmation' do + before do + allow(Rails).to receive(:cache).and_return( + ActiveSupport::Cache::RedisCacheStore.new(url: IdentityConfig.store.redis_throttle_url), ) - expect(response).to redirect_to idv_personal_key_path + subject.idv_session.address_verification_mechanism = 'phone' + subject.idv_session.vendor_phone_confirmation = true + subject.idv_session.user_phone_confirmation = true end - it 'redirects to confirmation path after user presses the back button' do - put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + it 'invalidates future steps' do + expect(subject).to receive(:clear_future_steps!) - allow_any_instance_of(User).to receive(:active_profile).and_return(true) - get :new - expect(response).to redirect_to idv_personal_key_path + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } end - it 'tracks irs password entered event (idv_password_entered)' do + it 'activates profile' do put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } - expect(@irs_attempts_api_tracker).to have_received(:track_event).with( - :idv_password_entered, - success: true, - ) - end + profile = subject.idv_session.profile + profile.reload - it 'creates Profile with applicant attributes' do - put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + expect(profile).to be_active + end - profile = idv_session.profile - pii = profile.decrypt_pii(ControllerHelper::VALID_PASSWORD) + it 'dispatches account verified alert' do + expect(UserAlerts::AlertUserAboutAccountVerified).to receive(:call) - expect(pii.zipcode).to eq applicant[:zipcode] + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + end - expect(pii.first_name).to eq applicant[:first_name] + it 'creates an `account_verified` event once per confirmation' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + events_count = user.events.where(event_type: :account_verified, ip: '0.0.0.0'). + where(disavowal_token_fingerprint: nil).count + expect(events_count).to eq 1 end - context 'user picked phone confirmation' do + context 'with in person profile' do + let!(:enrollment) do + create(:in_person_enrollment, :establishing, user: user, profile: nil) + end + before do - allow(Rails).to receive(:cache).and_return( - ActiveSupport::Cache::RedisCacheStore.new(url: IdentityConfig.store.redis_throttle_url), - ) - idv_session.address_verification_mechanism = 'phone' - idv_session.vendor_phone_confirmation = true - idv_session.user_phone_confirmation = true + stub_request_token + stub_request_enroll + subject.idv_session.applicant = + Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID_WITH_PHONE + ProofingComponent.create(user: user, document_check: Idp::Constants::Vendors::USPS) + allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true) end - it 'activates profile' do + it 'redirects to personal key path' do put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } - profile = idv_session.profile - profile.reload + expect(response).to redirect_to idv_personal_key_path + end + + it 'creates a USPS enrollment' do + proofer = UspsInPersonProofing::Proofer.new + mock = double + + expect(UspsInPersonProofing::Proofer).to receive(:new).and_return(mock) + expect(mock).to receive(:request_enroll) do |applicant| + expect(applicant.first_name).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:first_name]) + expect(applicant.last_name).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:last_name]) + expect(applicant.address).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:address1]) + expect(applicant.city).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:city]) + expect(applicant.state).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:state]) + expect(applicant.zip_code).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:zipcode]) + expect(applicant.email).to eq('no-reply@login.gov') + expect(applicant.unique_id).to be_a(String) + + proofer.request_enroll(applicant) + end - expect(profile).to be_active + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } end - it 'dispatches account verified alert' do - expect(UserAlerts::AlertUserAboutAccountVerified).to receive(:call) + it 'does not dispatch account verified alert' do + expect(UserAlerts::AlertUserAboutAccountVerified).not_to receive(:call) put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } end - it 'creates an `account_verified` event once per confirmation' do + it 'creates an in-person enrollment record' do put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } - events_count = user.events.where(event_type: :account_verified, ip: '0.0.0.0'). - where(disavowal_token_fingerprint: nil).count - expect(events_count).to eq 1 + + enrollment.reload + + expect(enrollment.status).to eq(InPersonEnrollment::STATUS_PENDING) + expect(enrollment.user_id).to eq(user.id) + expect(enrollment.enrollment_code).to be_a(String) + expect(enrollment.profile).to eq(user.profiles.last) + expect(enrollment.profile.in_person_verification_pending?).to eq(true) end - context 'with in person profile' do - let!(:enrollment) do - create(:in_person_enrollment, :establishing, user: user, profile: nil) - end - let(:applicant) do - Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID_WITH_PHONE - end + it 'sends ready to verify email' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + + expect_delivered_email_count(1) + expect_delivered_email( + to: [user.email_addresses.first.email], + subject: t('user_mailer.in_person_ready_to_verify.subject', app_name: APP_NAME), + ) + end + context 'when there is a 4xx error' do before do - stub_request_token - stub_request_enroll - ProofingComponent.create(user: user, document_check: Idp::Constants::Vendors::USPS) - allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true) - allow(IdentityConfig.store).to receive(:usps_mock_fallback).and_return(false) + stub_request_enroll_bad_request_response end - it 'redirects to personal key path' do + it 'logs the response message' do put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } - expect(response).to redirect_to idv_personal_key_path + expect(@analytics).to have_logged_event( + 'USPS IPPaaS enrollment failed', + context: 'authentication', + enrollment_id: enrollment.id, + exception_class: 'UspsInPersonProofing::Exception::RequestEnrollException', + exception_message: 'Sponsor for sponsorID 5 not found', + original_exception_class: 'Faraday::BadRequestError', + reason: 'Request exception', + ) end - it 'creates a USPS enrollment' do - proofer = UspsInPersonProofing::Proofer.new - mock = double + it 'leaves the enrollment in establishing' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } - expect(UspsInPersonProofing::Proofer).to receive(:new).and_return(mock) - expect(mock).to receive(:request_enroll) do |applicant| - expect(applicant.first_name).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:first_name]) - expect(applicant.last_name).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:last_name]) - expect(applicant.address).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:address1]) - expect(applicant.city).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:city]) - expect(applicant.state).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:state]) - expect(applicant.zip_code).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:zipcode]) - expect(applicant.email).to eq('no-reply@login.gov') - expect(applicant.unique_id).to be_a(String) + expect(InPersonEnrollment.count).to be(1) + enrollment = InPersonEnrollment.where(user_id: user.id).first + expect(enrollment.status).to eq(InPersonEnrollment::STATUS_ESTABLISHING) + expect(enrollment.user_id).to eq(user.id) + expect(enrollment.enrollment_code).to be_nil + end + end - proofer.request_enroll(applicant) - end + context 'when there is 5xx error' do + before do + stub_request_enroll_internal_server_error_response + end + it 'logs the error message' do put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } - end - it 'does not dispatch account verified alert' do - expect(UserAlerts::AlertUserAboutAccountVerified).not_to receive(:call) + expect(@analytics).to have_logged_event( + 'USPS IPPaaS enrollment failed', + context: 'authentication', + enrollment_id: enrollment.id, + exception_class: 'UspsInPersonProofing::Exception::RequestEnrollException', + exception_message: 'the server responded with status 500', + original_exception_class: 'Faraday::ServerError', + reason: 'Request exception', + ) + end + it 'leaves the enrollment in establishing' do put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + + expect(InPersonEnrollment.count).to be(1) + enrollment = InPersonEnrollment.where(user_id: user.id).first + expect(enrollment.status).to eq(InPersonEnrollment::STATUS_ESTABLISHING) + expect(enrollment.user_id).to eq(user.id) + expect(enrollment.enrollment_code).to be_nil end - it 'creates an in-person enrollment record' do + it 'allows the user to retry the request' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + expect(flash[:error]).to eq t('idv.failure.exceptions.internal_error') + expect(response).to redirect_to idv_enter_password_path + + stub_request_enroll + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + expect(response).to redirect_to idv_personal_key_path + enrollment.reload expect(enrollment.status).to eq(InPersonEnrollment::STATUS_PENDING) @@ -440,14 +525,24 @@ def show expect(enrollment.profile).to eq(user.profiles.last) expect(enrollment.profile.in_person_verification_pending?).to eq(true) end + end + + context 'when the USPS response is not a hash' do + before do + stub_request_enroll_non_hash_response + end - it 'sends ready to verify email' do + it 'logs an error message' do put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } - expect_delivered_email_count(1) - expect_delivered_email( - to: [user.email_addresses.first.email], - subject: t('user_mailer.in_person_ready_to_verify.subject', app_name: APP_NAME), + expect(@analytics).to have_logged_event( + 'USPS IPPaaS enrollment failed', + context: 'authentication', + enrollment_id: enrollment.id, + exception_class: 'UspsInPersonProofing::Exception::RequestEnrollException', + exception_message: 'Expected a hash but got a NilClass', + original_exception_class: 'StandardError', + reason: 'Request exception', ) end @@ -606,141 +701,186 @@ def show end end - context 'threatmetrix review status is set in profile' do - %i[enabled disabled].each do |proofing_device_profiling_state| - context "when proofing_device_profiling is #{proofing_device_profiling_state}" do - [nil, 'pass', 'review'].each do |review_status| - context "when review status is #{review_status.nil? ? 'nil' : review_status}" do - let(:fraud_review_pending?) do - proofing_device_profiling_state == :enabled && - !review_status.nil? && review_status != 'pass' - end - let(:review_status) { review_status } - let(:proofing_device_profiling_state) { proofing_device_profiling_state } - let(:applicant) do - Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE - end - - before do - allow(IdentityConfig.store).to receive(:proofing_device_profiling). - and_return(proofing_device_profiling_state) - idv_session.threatmetrix_review_status = review_status - stub_request_token - end - - it 'creates a profile with fraud_review_pending defined' do - put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + context 'when the USPS response is missing an enrollment code' do + before do + stub_request_enroll_invalid_response + end + + it 'logs an error message' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + + expect(@analytics).to have_logged_event( + 'USPS IPPaaS enrollment failed', + context: 'authentication', + enrollment_id: enrollment.id, + exception_class: 'UspsInPersonProofing::Exception::RequestEnrollException', + exception_message: 'Expected to receive an enrollment code', + original_exception_class: 'StandardError', + reason: 'Request exception', + ) + end + + it 'leaves the enrollment in establishing' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + + expect(InPersonEnrollment.count).to be(1) + enrollment = InPersonEnrollment.where(user_id: user.id).first + expect(enrollment.status).to eq(InPersonEnrollment::STATUS_ESTABLISHING) + expect(enrollment.user_id).to eq(user.id) + expect(enrollment.enrollment_code).to be_nil + end + end - expect(user.profiles.last.fraud_review_pending?).to eq(fraud_review_pending?) - end + context 'when user enters an address2 value' do + it 'does not include address2' do + subject.idv_session.applicant = + Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID_WITH_PHONE. + merge(address2: '3b') + proofer = UspsInPersonProofing::Proofer.new + mock = double + expect(UspsInPersonProofing::Proofer).to receive(:new).and_return(mock) + expect(mock).to receive(:request_enroll) do |applicant| + expect(applicant.address). + to eq(Idp::Constants::MOCK_IDV_APPLICANT[:address1]) + proofer.request_enroll(applicant) + end - it 'logs events' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + end + end + end + + context 'threatmetrix review status is set in profile' do + %i[enabled disabled].each do |proofing_device_profiling_state| + context "when proofing_device_profiling is #{proofing_device_profiling_state}" do + [nil, 'pass', 'review'].each do |review_status| + context "when review status is #{review_status.nil? ? 'nil' : review_status}" do + let(:fraud_review_pending?) do + proofing_device_profiling_state == :enabled && + !review_status.nil? && review_status != 'pass' + end + let(:review_status) { review_status } + let(:proofing_device_profiling_state) { proofing_device_profiling_state } + + before do + allow(IdentityConfig.store).to receive(:proofing_device_profiling). + and_return(proofing_device_profiling_state) + subject.idv_session.threatmetrix_review_status = review_status + stub_request_token + end + + it 'creates a profile with fraud_review_pending defined' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + + expect(user.profiles.last.fraud_review_pending?).to eq(fraud_review_pending?) + end + + it 'logs events' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + expect(@analytics).to have_logged_event( + :idv_enter_password_submitted, + success: true, + fraud_review_pending: fraud_review_pending?, + fraud_rejection: false, + gpo_verification_pending: false, + in_person_verification_pending: false, + proofing_components: nil, + deactivation_reason: nil, + **ab_test_args, + ) + expect(@analytics).to have_logged_event( + 'IdV: final resolution', + success: true, + fraud_review_pending: fraud_review_pending?, + fraud_rejection: false, + gpo_verification_pending: false, + in_person_verification_pending: false, + proofing_components: nil, + deactivation_reason: nil, + **ab_test_args, + ) + end + + it 'updates the doc auth log for the user for the verified view event' do + unstub_analytics + doc_auth_log = DocAuthLog.create(user_id: user.id) + + expect do put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } - expect(@analytics).to have_logged_event( - :idv_enter_password_submitted, - success: true, - fraud_review_pending: fraud_review_pending?, - fraud_rejection: false, - gpo_verification_pending: false, - in_person_verification_pending: false, - proofing_components: nil, - deactivation_reason: nil, - **ab_test_args, - ) - expect(@analytics).to have_logged_event( - 'IdV: final resolution', - success: true, - fraud_review_pending: fraud_review_pending?, - fraud_rejection: false, - gpo_verification_pending: false, - in_person_verification_pending: false, - proofing_components: nil, - deactivation_reason: nil, - **ab_test_args, - ) - end - - it 'updates the doc auth log for the user for the verified view event' do - unstub_analytics - doc_auth_log = DocAuthLog.create(user_id: user.id) - - expect do - put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } - end.to( - change { doc_auth_log.reload.verified_view_count }.from(0).to(1), - ) - end + end.to( + change { doc_auth_log.reload.verified_view_count }.from(0).to(1), + ) end end end end end end + end - context 'user picked GPO confirmation' do - before do - idv_session.address_verification_mechanism = 'gpo' - end + context 'user picked GPO confirmation' do + before do + subject.idv_session.address_verification_mechanism = 'gpo' + end - it 'leaves profile deactivated' do - put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + it 'leaves profile deactivated' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } - profile = idv_session.profile - profile.reload + profile = subject.idv_session.profile + profile.reload - expect(profile).to_not be_active - end + expect(profile).to_not be_active + end - it 'sends an email about the gpo letter' do - expect do - put :create, - params: { - user: { password: ControllerHelper::VALID_PASSWORD }, - } - end.to(change { ActionMailer::Base.deliveries.count }.by(1)) - end + it 'sends an email about the gpo letter' do + expect do + put :create, + params: { + user: { password: ControllerHelper::VALID_PASSWORD }, + } + end.to(change { ActionMailer::Base.deliveries.count }.by(1)) + end + + it 'logs USPS address letter enqueued event with phone_step_attempts', :freeze_time do + RateLimiter.new(user: user, rate_limit_type: :proof_address).increment! + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + + expect(@analytics).to have_logged_event( + 'IdV: USPS address letter enqueued', + resend: false, + enqueued_at: Time.zone.now, + phone_step_attempts: 1, + first_letter_requested_at: subject.idv_session.profile.gpo_verification_pending_at, + hours_since_first_letter: 0, + proofing_components: nil, + **ab_test_args, + ) + end + context 'when user is rate limited' do it 'logs USPS address letter enqueued event with phone_step_attempts', :freeze_time do - RateLimiter.new(user: user, rate_limit_type: :proof_address).increment! + rate_limit_type = :proof_address + rate_limiter = RateLimiter.new(user: user, rate_limit_type: rate_limit_type) + rate_limiter.increment_to_limited! put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } expect(@analytics).to have_logged_event( 'IdV: USPS address letter enqueued', resend: false, enqueued_at: Time.zone.now, - phone_step_attempts: 1, - first_letter_requested_at: idv_session.profile.gpo_verification_pending_at, + phone_step_attempts: RateLimiter.max_attempts(rate_limit_type), + first_letter_requested_at: subject.idv_session.profile.gpo_verification_pending_at, hours_since_first_letter: 0, proofing_components: nil, **ab_test_args, ) end + end - context 'when user is rate limited' do - it 'logs USPS address letter enqueued event with phone_step_attempts', :freeze_time do - rate_limit_type = :proof_address - rate_limiter = RateLimiter.new(user: user, rate_limit_type: rate_limit_type) - rate_limiter.increment_to_limited! - put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } - - expect(@analytics).to have_logged_event( - 'IdV: USPS address letter enqueued', - resend: false, - enqueued_at: Time.zone.now, - phone_step_attempts: RateLimiter.max_attempts(rate_limit_type), - first_letter_requested_at: idv_session.profile.gpo_verification_pending_at, - hours_since_first_letter: 0, - proofing_components: nil, - **ab_test_args, - ) - end - end - - it 'redirects to come back later page' do - put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + it 'redirects to come back later page' do + put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } - expect(response).to redirect_to idv_letter_enqueued_url - end + expect(response).to redirect_to idv_letter_enqueued_url end end end diff --git a/spec/controllers/idv/hybrid_handoff_controller_spec.rb b/spec/controllers/idv/hybrid_handoff_controller_spec.rb index 83b28831bc3..450992c4abd 100644 --- a/spec/controllers/idv/hybrid_handoff_controller_spec.rb +++ b/spec/controllers/idv/hybrid_handoff_controller_spec.rb @@ -148,22 +148,22 @@ subject.idv_session.mark_verify_info_step_complete! end - it 'does not set redo_document_capture to true in idv_session' do + it 'does set redo_document_capture to true in idv_session' do get :show, params: { redo: true } - expect(subject.idv_session.redo_document_capture).not_to be_truthy + expect(subject.idv_session.redo_document_capture).to be_truthy end - it 'does not add redo_document_capture to analytics' do + it 'does add redo_document_capture to analytics' do get :show, params: { redo: true } - expect(@analytics).not_to have_logged_event(analytics_name) + expect(@analytics).to have_logged_event(analytics_name) end - it 'redirects to review' do + it 'renders show' do get :show, params: { redo: true } - expect(response).to redirect_to(idv_enter_password_url) + expect(response).to render_template :show end end end diff --git a/spec/controllers/idv/in_person/ssn_controller_spec.rb b/spec/controllers/idv/in_person/ssn_controller_spec.rb index 0e622b3a339..c5a9e49f06f 100644 --- a/spec/controllers/idv/in_person/ssn_controller_spec.rb +++ b/spec/controllers/idv/in_person/ssn_controller_spec.rb @@ -16,9 +16,8 @@ end before do - allow(subject).to receive(:pii_from_user).and_return(pii_from_user) - allow(subject).to receive(:flow_session).and_return(flow_session) stub_sign_in(user) + subject.user_session['idv/in_person'] = flow_session stub_analytics stub_attempts_tracker allow(@analytics).to receive(:track_event) @@ -26,20 +25,21 @@ subject.idv_session.flow_path = 'standard' end + describe '#step_info' do + it 'returns a valid StepInfo object' do + expect(Idv::InPerson::SsnController.step_info).to be_valid + end + end + describe 'before_actions' do - context('#confirm_in_person_address_step_complete') do + context '#confirm_in_person_address_step_complete' do context 'residential address controller flag not enabled' do before do allow(IdentityConfig.store).to receive(:in_person_residential_address_controller_enabled). and_return(false) end it 'redirects if the user hasn\'t completed the address page' do - # delete address attributes on session - flow_session[:pii_from_user].delete(:address1) - flow_session[:pii_from_user].delete(:address2) - flow_session[:pii_from_user].delete(:city) - flow_session[:pii_from_user].delete(:state) - flow_session[:pii_from_user].delete(:zipcode) + subject.user_session['idv/in_person'][:pii_from_user].delete(:address1) get :show expect(response).to redirect_to idv_in_person_step_url(step: :address) @@ -52,12 +52,7 @@ and_return(true) end it 'redirects if address page not completed' do - # delete address attributes on session - flow_session[:pii_from_user].delete(:address1) - flow_session[:pii_from_user].delete(:address2) - flow_session[:pii_from_user].delete(:city) - flow_session[:pii_from_user].delete(:state) - flow_session[:pii_from_user].delete(:zipcode) + subject.user_session['idv/in_person'][:pii_from_user].delete(:address1) get :show expect(response).to redirect_to idv_in_person_proofing_address_url @@ -217,6 +212,12 @@ expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) expect(response.body).to include('Enter a nine-digit Social Security number') end + + it 'invalidates future steps' do + expect(subject).to receive(:clear_future_steps!) + + put :update, params: params + end end end end diff --git a/spec/controllers/idv/in_person/verify_info_controller_spec.rb b/spec/controllers/idv/in_person/verify_info_controller_spec.rb index 2b9aae859ec..dd4331b0535 100644 --- a/spec/controllers/idv/in_person/verify_info_controller_spec.rb +++ b/spec/controllers/idv/in_person/verify_info_controller_spec.rb @@ -6,7 +6,7 @@ { pii_from_user: pii_from_user } end - let(:user) { build(:user, :with_phone, with: { phone: '+1 (415) 555-0130' }) } + let(:user) { create(:user, :with_phone, with: { phone: '+1 (415) 555-0130' }) } let(:service_provider) { create(:service_provider) } let(:ab_test_args) do @@ -14,13 +14,19 @@ end before do - allow(subject).to receive(:flow_session).and_return(flow_session) stub_sign_in(user) subject.idv_session.flow_path = 'standard' subject.idv_session.ssn = Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID[:ssn] + subject.user_session['idv/in_person'] = flow_session allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end + describe '#step_info' do + it 'returns a valid StepInfo object' do + expect(Idv::InPerson::VerifyInfoController.step_info).to be_valid + end + end + describe 'before_actions' do it 'includes authentication before_action' do expect(subject).to have_actions( @@ -42,13 +48,6 @@ :confirm_ssn_step_complete, ) end - - it 'confirms verify step needed' do - expect(subject).to have_actions( - :before, - :confirm_verify_info_step_needed, - ) - end end before do @@ -136,12 +135,13 @@ expect(Idv::Agent).to receive(:new). with(hash_including(uuid_prefix: service_provider.app_id)).and_call_original # our test data already has the expected value by default - flow_session[:pii_from_user].delete(:state_id_type) - + subject.user_session['idv/in_person'][:pii_from_user].delete(:state_id_type) put :update - expect(flow_session[:pii_from_user][:state_id_type]).to eq 'drivers_license' - expect(flow_session[:pii_from_user][:uuid_prefix]).to eq service_provider.app_id + expect(subject.user_session['idv/in_person'][:pii_from_user][:state_id_type]). + to eq 'drivers_license' + expect(subject.user_session['idv/in_person'][:pii_from_user][:uuid_prefix]). + to eq service_provider.app_id end context 'a user does not have an establishing in person enrollment associated with them' do @@ -244,5 +244,11 @@ expect(response).to redirect_to(idv_session_errors_ssn_failure_url) end end + + it 'invalidates future steps' do + expect(subject).to receive(:clear_future_steps!) + + put :update + end end end diff --git a/spec/controllers/idv/link_sent_controller_spec.rb b/spec/controllers/idv/link_sent_controller_spec.rb index 1ed9c71067f..87cf6c82a0c 100644 --- a/spec/controllers/idv/link_sent_controller_spec.rb +++ b/spec/controllers/idv/link_sent_controller_spec.rb @@ -122,9 +122,11 @@ end it 'invalidates future steps' do - expect(subject).to receive(:clear_future_steps!) + subject.idv_session.applicant = Idp::Constants::MOCK_IDV_APPLICANT + expect(subject).to receive(:clear_future_steps!).and_call_original put :update + expect(subject.idv_session.applicant).to be_nil end it 'sends analytics_submitted event' do diff --git a/spec/controllers/idv/otp_verification_controller_spec.rb b/spec/controllers/idv/otp_verification_controller_spec.rb index 3ca3199494d..f9ea33666d2 100644 --- a/spec/controllers/idv/otp_verification_controller_spec.rb +++ b/spec/controllers/idv/otp_verification_controller_spec.rb @@ -28,12 +28,24 @@ sign_in(user) stub_verify_steps_one_and_two(user) + subject.idv_session.welcome_visited = true + subject.idv_session.idv_consent_given = true + subject.idv_session.flow_path = 'standard' + subject.idv_session.pii_from_doc = Idp::Constants::MOCK_IDV_APPLICANT + subject.idv_session.ssn = Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE[:ssn] + subject.idv_session.resolution_successful = true subject.idv_session.applicant[:phone] = phone subject.idv_session.vendor_phone_confirmation = true subject.idv_session.user_phone_confirmation = user_phone_confirmation subject.idv_session.user_phone_confirmation_session = user_phone_confirmation_session end + describe '#step_info' do + it 'returns a valid StepInfo object' do + expect(Idv::OtpVerificationController.step_info).to be_valid + end + end + describe 'before_actions' do it 'includes before_actions from IdvSession' do expect(subject).to have_actions(:before, :redirect_unless_sp_requested_verification) @@ -53,9 +65,9 @@ context 'the user has already confirmed their phone' do let(:user_phone_confirmation) { true } - it 'redirects to the review step' do + it 'allows the back button and renders show' do get :show - expect(response).to redirect_to(idv_enter_password_path) + expect(response).to render_template :show end end @@ -80,6 +92,12 @@ end end + it 'invalidates future steps' do + expect(subject).to receive(:clear_future_steps!) + + put :update, params: otp_code_param + end + context 'the user has already confirmed their phone' do let(:user_phone_confirmation) { true } diff --git a/spec/controllers/idv/phone_controller_spec.rb b/spec/controllers/idv/phone_controller_spec.rb index cd65c39a93a..e29505e7a70 100644 --- a/spec/controllers/idv/phone_controller_spec.rb +++ b/spec/controllers/idv/phone_controller_spec.rb @@ -11,12 +11,17 @@ let(:international_phone) { '+81 54 354 3643' } let(:timeout_phone) { '7035555888' } + describe '#step_info' do + it 'returns a valid StepInfo object' do + expect(Idv::PhoneController.step_info).to be_valid + end + end + describe 'before_actions' do it 'includes authentication before_action' do expect(subject).to have_actions( :before, :confirm_two_factor_authenticated, - :confirm_verify_info_step_complete, ) end @@ -60,11 +65,11 @@ subject.idv_session.user_phone_confirmation = true end - it 'redirects to review when step is complete' do + it 'allows the back button and renders new' do subject.idv_session.vendor_phone_confirmation = true get :new - expect(response).to redirect_to idv_enter_password_path + expect(response).to render_template :new end end @@ -83,6 +88,11 @@ context 'when the user has not finished the verify step' do before do + subject.idv_session.welcome_visited = true + subject.idv_session.idv_consent_given = true + subject.idv_session.flow_path = 'standard' + subject.idv_session.pii_from_doc = Idp::Constants::MOCK_IDV_APPLICANT + subject.idv_session.ssn = '123-45-6789' subject.idv_session.applicant = nil subject.idv_session.resolution_successful = nil end @@ -316,6 +326,21 @@ allow(@analytics).to receive(:track_event) end + it 'invalidates future steps' do + user = build(:user, :with_phone, with: { phone: good_phone, confirmed_at: Time.zone.now }) + stub_verify_steps_one_and_two(user) + expect(subject).to receive(:clear_future_steps!) + + phone_params = { + idv_phone_form: { + phone: good_phone, + otp_delivery_preference: :sms, + }, + } + + put :create, params: phone_params + end + it 'tracks events with valid phone' do user = build(:user, :with_phone, with: { phone: good_phone, confirmed_at: Time.zone.now }) stub_verify_steps_one_and_two(user) diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb index a19c8b4c427..20aec02dce3 100644 --- a/spec/controllers/idv/verify_info_controller_spec.rb +++ b/spec/controllers/idv/verify_info_controller_spec.rb @@ -16,9 +16,9 @@ end before do + stub_sign_in(user) stub_analytics stub_attempts_tracker - stub_idv_steps_before_verify_step(user) subject.idv_session.welcome_visited = true subject.idv_session.idv_consent_given = true subject.idv_session.flow_path = 'standard' @@ -100,12 +100,15 @@ end context 'when the user has already verified their info' do - it 'redirects to the enter password controller' do + it 'renders show' do subject.idv_session.resolution_successful = true + subject.idv_session.pii_from_doc = Idp::Constants::MOCK_IDV_APPLICANT + subject.idv_session.ssn = Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN[:ssn] + subject.idv_session.applicant = Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN get :show - expect(response).to redirect_to(idv_enter_password_url) + expect(response).to render_template :show end end diff --git a/spec/controllers/idv/welcome_controller_spec.rb b/spec/controllers/idv/welcome_controller_spec.rb index d86dae82f0c..21472040a11 100644 --- a/spec/controllers/idv/welcome_controller_spec.rb +++ b/spec/controllers/idv/welcome_controller_spec.rb @@ -82,9 +82,9 @@ subject.idv_session.resolution_successful = true end - it 'redirects to enter password step' do + it 'renders show' do get :show - expect(response).to redirect_to(idv_enter_password_url) + expect(response).to render_template('idv/welcome/show') end end end diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb index 354b47d8bf9..9240a09471e 100644 --- a/spec/features/idv/analytics_spec.rb +++ b/spec/features/idv/analytics_spec.rb @@ -468,7 +468,7 @@ # rubocop:enable Layout/LineLength # rubocop:enable Layout/MultilineHashKeyLineBreaks - # Needed for enqueued_at in gpo_step + # Needed for enqueued_at in RequestLetter around do |ex| freeze_time { ex.run } end @@ -629,7 +629,7 @@ complete_ssn_step complete_verify_step enter_gpo_flow - gpo_step + complete_request_letter complete_enter_password_step(user) end diff --git a/spec/features/idv/doc_auth/document_capture_spec.rb b/spec/features/idv/doc_auth/document_capture_spec.rb index 1f58acd3191..d3c49dcbeff 100644 --- a/spec/features/idv/doc_auth/document_capture_spec.rb +++ b/spec/features/idv/doc_auth/document_capture_spec.rb @@ -187,8 +187,6 @@ click_idv_continue complete_verify_step expect(page).to have_current_path(idv_phone_url) - visit(idv_document_capture_url) - expect(page).to have_current_path(idv_phone_url) end end end diff --git a/spec/features/idv/doc_auth/redo_document_capture_spec.rb b/spec/features/idv/doc_auth/redo_document_capture_spec.rb index 83f14f5b618..852e10407ec 100644 --- a/spec/features/idv/doc_auth/redo_document_capture_spec.rb +++ b/spec/features/idv/doc_auth/redo_document_capture_spec.rb @@ -63,52 +63,6 @@ expect(page).to have_content(DocAuthHelper::GOOD_SSN) end - it 'document capture cannot be reached after submitting verify info step' do - warning_link_text = t('doc_auth.headings.capture_scan_warning_link') - - expect(page).to have_css( - '[role="status"]', - text: t( - 'doc_auth.headings.capture_scan_warning_html', - link_html: warning_link_text, - ).tr(' ', ' '), - ) - click_link warning_link_text - - expect(current_path).to eq(idv_hybrid_handoff_path) - complete_hybrid_handoff_step - - visit idv_verify_info_url - expect(page).to have_current_path(idv_document_capture_path) - DocAuth::Mock::DocAuthMockClient.reset! - attach_and_submit_images - complete_ssn_step - complete_verify_step - - expect(page).to have_current_path(idv_phone_path) - - fill_out_phone_form_fail - - click_idv_send_security_code - - expect(page).to have_content(t('idv.failure.phone.warning.heading')) - - visit idv_url - expect(current_path).to eq(idv_phone_path) - - visit idv_hybrid_handoff_url - expect(current_path).to eq(idv_phone_path) - - visit idv_document_capture_url - expect(current_path).to eq(idv_phone_path) - - visit idv_ssn_url - expect(current_path).to eq(idv_phone_path) - - visit idv_verify_info_url - expect(current_path).to eq(idv_phone_path) - end - context 'with a bad SSN' do let(:use_bad_ssn) { true } 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 7b1f9a706be..dbbeca74952 100644 --- a/spec/features/idv/doc_auth/verify_info_step_spec.rb +++ b/spec/features/idv/doc_auth/verify_info_step_spec.rb @@ -292,8 +292,7 @@ ). and_call_original - sign_in_and_2fa_user(user) - complete_doc_auth_steps_before_verify_step + complete_ssn_step complete_verify_step expect(DocAuthLog.find_by(user_id: user.id).aamva).not_to be_nil @@ -316,8 +315,7 @@ ). and_call_original - sign_in_and_2fa_user(user) - complete_doc_auth_steps_before_verify_step + complete_ssn_step complete_verify_step expect(DocAuthLog.find_by(user_id: user.id).aamva).to be_nil diff --git a/spec/features/idv/end_to_end_idv_spec.rb b/spec/features/idv/end_to_end_idv_spec.rb index 619b590320d..17f38e0b223 100644 --- a/spec/features/idv/end_to_end_idv_spec.rb +++ b/spec/features/idv/end_to_end_idv_spec.rb @@ -73,10 +73,10 @@ test_go_back_from_verify_info complete_verify_step - validate_phone_page - visit_by_mail_and_return + test_go_back_from_phone complete_otp_verification_page(user) + test_go_back_from_enter_password complete_enter_password_step(user) acknowledge_and_confirm_personal_key @@ -102,8 +102,10 @@ complete_all_in_person_proofing_steps(user) enter_gpo_flow - gpo_step + test_go_back_from_request_letter + complete_request_letter + test_go_back_in_person_flow complete_enter_password_step(user) try_to_go_back_from_letter_enqueued @@ -446,8 +448,52 @@ def test_go_back_from_verify_info go_back go_back expect(page).to have_current_path(idv_welcome_path) - visit(idv_verify_info_path) + 5.times { go_forward } + expect(page).to have_current_path(idv_verify_info_path) + end + + def test_go_back_from_phone + go_back + go_back + expect(page).to have_current_path(idv_ssn_path) + go_back + go_back + go_back + go_back + expect(page).to have_current_path(idv_welcome_path) + 6.times { go_forward } + expect(page).to have_current_path(idv_phone_path) + end + + def test_go_back_from_enter_password + go_back + expect(page).to have_current_path(idv_otp_verification_path) + go_back + expect(page).to have_current_path(idv_phone_path) + go_back expect(page).to have_current_path(idv_verify_info_path) + 3.times { go_forward } + + expect(page).to have_current_path(idv_enter_password_path) + end + + def test_go_back_from_request_letter + go_back + expect(page).to have_current_path(idv_phone_path) + go_back + expect(page).to have_current_path(idv_in_person_verify_info_path) + 2.times { go_forward } + expect(page).to have_current_path(idv_request_letter_path) + end + + def test_go_back_in_person_flow + go_back + go_back + go_back + expect(page).to have_current_path(idv_in_person_verify_info_path) + # can't go back further with in person controllers (yet) + + 3.times { go_forward } end def try_to_go_back_from_letter_enqueued diff --git a/spec/features/idv/steps/enter_password_step_spec.rb b/spec/features/idv/steps/enter_password_step_spec.rb index d0f269de629..4b73c10d855 100644 --- a/spec/features/idv/steps/enter_password_step_spec.rb +++ b/spec/features/idv/steps/enter_password_step_spec.rb @@ -94,6 +94,12 @@ def sends_letter_creates_unverified_profile_sends_email it 'allows the user to submit password and proceed to obtain a personal key' do visit(idv_hybrid_handoff_url(redo: true)) + expect(current_path).to eq idv_hybrid_handoff_path + complete_hybrid_handoff_step + complete_document_capture_step + complete_ssn_step + complete_verify_step + complete_phone_step(user) complete_enter_password_step(user) expect(current_path).to eq idv_personal_key_path end diff --git a/spec/features/idv/steps/phone_step_spec.rb b/spec/features/idv/steps/phone_step_spec.rb index 940fca00f2b..344474a3675 100644 --- a/spec/features/idv/steps/phone_step_spec.rb +++ b/spec/features/idv/steps/phone_step_spec.rb @@ -37,27 +37,6 @@ expect(page).to have_content(t('titles.idv.enter_one_time_code')) expect(page).to have_content('+1 703-789-7890') end - - it 'is not re-entrant after confirming OTP' do - start_idv_from_sp - complete_idv_steps_before_phone_step(user) - fill_out_phone_form_ok - click_idv_send_security_code - fill_in_code_with_last_phone_otp - click_submit_default - - visit idv_phone_path - expect(page).to have_content(t('idv.titles.session.enter_password', app_name: APP_NAME)) - expect(page).to have_current_path(idv_enter_password_path) - - fill_in 'Password', with: user_password - click_continue - - # Currently this byasses the confirmation step since that is only - # accessible once - visit idv_phone_path - expect(page).to_not have_current_path(idv_phone_path) - end end it 'allows resubmitting form' do @@ -99,6 +78,7 @@ click_on t('idv.failure.phone.warning.try_again_button') expect(page).to have_current_path(idv_phone_path) + phone_field = find_field(t('two_factor_authentication.phone_label')) expect(phone_field.value).to be_empty end diff --git a/spec/policies/idv/flow_policy_spec.rb b/spec/policies/idv/flow_policy_spec.rb index 346086edc43..600efef59d3 100644 --- a/spec/policies/idv/flow_policy_spec.rb +++ b/spec/policies/idv/flow_policy_spec.rb @@ -5,9 +5,11 @@ let(:user) { create(:user) } + let(:user_session) { { 'idv/in_person' => {} } } + let(:idv_session) do Idv::Session.new( - user_session: {}, + user_session: user_session, current_user: user, service_provider: nil, ) @@ -68,6 +70,79 @@ expect(idv_session.address_edited).to be_nil end end + + context 'user is on enter password step' do + before do + idv_session.welcome_visited = true + idv_session.document_capture_session_uuid = SecureRandom.uuid + + idv_session.idv_consent_given = true + idv_session.skip_hybrid_handoff = true + + idv_session.flow_path = 'standard' + idv_session.phone_for_mobile_flow = '201-555-1212' + + idv_session.pii_from_doc = nil + idv_session.had_barcode_read_failure = true + idv_session.had_barcode_attention_error = true + + idv_session.ssn = nil + idv_session.threatmetrix_session_id = SecureRandom.uuid + + idv_session.address_edited = true + + idv_session.verify_info_step_document_capture_session_uuid = SecureRandom.uuid + idv_session.threatmetrix_review_status = 'pass' + idv_session.resolution_successful = true + idv_session.applicant = Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.dup + + idv_session.vendor_phone_confirmation = true + idv_session.address_verification_mechanism = 'phone' + idv_session.idv_phone_step_document_capture_session_uuid = SecureRandom.uuid + idv_session.user_phone_confirmation_session = { phone: '201-555-1212' } + idv_session.previous_phone_step_params = '201-555-1111' + + idv_session.user_phone_confirmation = true + end + + it 'does clear steps after ssn when user submits ssn' do + subject.undo_future_steps_from_controller!(controller: Idv::SsnController) + + expect(idv_session.address_edited).to be_nil + + expect(idv_session.verify_info_step_document_capture_session_uuid).to be_nil + expect(idv_session.threatmetrix_review_status).to be_nil + expect(idv_session.resolution_successful).to be_nil + expect(idv_session.applicant).to be_nil + + expect(idv_session.vendor_phone_confirmation).to be_nil + expect(idv_session.address_verification_mechanism).to be_nil + expect(idv_session.idv_phone_step_document_capture_session_uuid).to be_nil + expect(idv_session.user_phone_confirmation_session).to be_nil + expect(idv_session.previous_phone_step_params).to be_nil + + expect(idv_session.user_phone_confirmation).to be_nil + end + + it 'does not clear earlier steps when user goes back and submits ssn' do + expect do + subject.undo_future_steps_from_controller!(controller: Idv::SsnController) + end.not_to change { + idv_session.welcome_visited + idv_session.document_capture_session_uuid + idv_session.idv_consent_given + idv_session.skip_hybrid_handoff + + idv_session.flow_path + idv_session.phone_for_mobile_flow + + idv_session.had_barcode_read_failure + idv_session.had_barcode_attention_error + + idv_session.threatmetrix_session_id + } + end + end end context 'each step in the flow' do @@ -145,6 +220,22 @@ end end + context 'preconditions for in_person ssn are present' do + before do + idv_session.welcome_visited = true + idv_session.idv_consent_given = true + idv_session.flow_path = 'standard' + idv_session.send(:user_session)['idv/in_person'][:pii_from_user] = { pii: 'value' } + end + + it 'returns ipp_ssn' do + expect(subject.info_for_latest_step.key).to eq(:ipp_ssn) + expect(subject.controller_allowed?(controller: Idv::InPerson::SsnController)).to be + expect(subject.controller_allowed?(controller: Idv::InPerson::VerifyInfoController)). + not_to be + end + end + context 'preconditions for verify_info are present' do it 'returns verify_info' do idv_session.welcome_visited = true @@ -155,7 +246,112 @@ expect(subject.info_for_latest_step.key).to eq(:verify_info) expect(subject.controller_allowed?(controller: Idv::VerifyInfoController)).to be - # expect(subject.controller_allowed?(controller: Idv::PhoneController)).not_to be + expect(subject.controller_allowed?(controller: Idv::PhoneController)).not_to be + end + end + + context 'preconditions for in_person verify_info are present' do + it 'returns ipp_verify_info' do + idv_session.welcome_visited = true + idv_session.idv_consent_given = true + idv_session.flow_path = 'standard' + idv_session.send(:user_session)['idv/in_person'][:pii_from_user] = { pii: 'value' } + idv_session.ssn = '666666666' + + expect(subject.info_for_latest_step.key).to eq(:ipp_verify_info) + expect(subject.controller_allowed?(controller: Idv::InPerson::VerifyInfoController)).to be + expect(subject.controller_allowed?(controller: Idv::PhoneController)).not_to be + end + end + + context 'preconditions for phone are present' do + it 'returns phone' do + idv_session.welcome_visited = true + idv_session.idv_consent_given = true + idv_session.flow_path = 'standard' + idv_session.pii_from_doc = { pii: 'value' } + idv_session.applicant = { pii: 'value' } + idv_session.ssn = '666666666' + idv_session.resolution_successful = true + + expect(subject.info_for_latest_step.key).to eq(:phone) + expect(subject.controller_allowed?(controller: Idv::PhoneController)).to be + expect(subject.controller_allowed?(controller: Idv::OtpVerificationController)).not_to be + end + end + + context 'preconditions for otp_verification are present' do + let(:user_phone_confirmation_session) { { code: 'abcde' } } + + it 'returns otp_verification' do + idv_session.welcome_visited = true + idv_session.idv_consent_given = true + idv_session.flow_path = 'standard' + idv_session.pii_from_doc = { pii: 'value' } + idv_session.applicant = { pii: 'value' } + idv_session.ssn = '666666666' + idv_session.resolution_successful = true + idv_session.user_phone_confirmation_session = user_phone_confirmation_session + + expect(subject.info_for_latest_step.key).to eq(:otp_verification) + expect(subject.controller_allowed?(controller: Idv::OtpVerificationController)).to be + expect(subject.controller_allowed?(controller: Idv::EnterPasswordController)).not_to be + end + end + + context 'preconditions for request_letter are present' do + it 'returns enter_password with gpo verification pending' do + idv_session.welcome_visited = true + idv_session.idv_consent_given = true + idv_session.flow_path = 'standard' + idv_session.pii_from_doc = { pii: 'value' } + idv_session.applicant = { pii: 'value' } + idv_session.ssn = '666666666' + idv_session.resolution_successful = true + + expect(subject.controller_allowed?(controller: Idv::ByMail::RequestLetterController)).to be + expect(subject.controller_allowed?(controller: Idv::EnterPasswordController)).not_to be + end + end + + context 'preconditions for enter_password are present' do + let(:user_phone_confirmation_session) { { code: 'abcde' } } + + context 'user has a gpo address_verification_mechanism' do + it 'returns enter_password with gpo verification pending' do + idv_session.welcome_visited = true + idv_session.idv_consent_given = true + idv_session.flow_path = 'standard' + idv_session.pii_from_doc = { pii: 'value' } + idv_session.applicant = { pii: 'value' } + idv_session.ssn = '666666666' + idv_session.resolution_successful = true + idv_session.user_phone_confirmation_session = user_phone_confirmation_session + idv_session.address_verification_mechanism = 'gpo' + + expect(subject.info_for_latest_step.key).to eq(:enter_password) + expect(subject.controller_allowed?(controller: Idv::EnterPasswordController)).to be + # expect(subject.controller_allowed?(controller: Idv::PersonalKeyController)).not_to be + end + end + + context 'user passed phone step' do + it 'returns enter_password' do + idv_session.welcome_visited = true + idv_session.idv_consent_given = true + idv_session.flow_path = 'standard' + idv_session.pii_from_doc = { pii: 'value' } + idv_session.applicant = { pii: 'value' } + idv_session.ssn = '666666666' + idv_session.resolution_successful = true + idv_session.user_phone_confirmation_session = user_phone_confirmation_session + idv_session.vendor_phone_confirmation = true + idv_session.user_phone_confirmation = true + + expect(subject.info_for_latest_step.key).to eq(:enter_password) + expect(subject.controller_allowed?(controller: Idv::EnterPasswordController)).to be + # expect(subject.controller_allowed?(controller: Idv::PersonalKeyController)).not_to be + end end end end diff --git a/spec/policies/idv/step_info_spec.rb b/spec/policies/idv/step_info_spec.rb index aac648e168f..e89f3867b41 100644 --- a/spec/policies/idv/step_info_spec.rb +++ b/spec/policies/idv/step_info_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe 'Idv::StepInfo' do - let(:controller) { ApplicationController.controller_name } + let(:controller) { ApplicationController.class } let(:next_steps) { [] } let(:preconditions) { ->(idv_session:, user:) { true } } let(:undo_step) { ->(idv_session:, user:) { true } } @@ -46,4 +46,19 @@ expect { subject }.to raise_error(ArgumentError) end end + + context '#full_controller_name' do + let(:idv_step_controller_class) do + Class.new(ApplicationController) do + def self.name + 'Idv::Lets::Go::Deeper::AnonymousController' + end + end + end + + it 'returns an absolute "path" for the controller name' do + expect(Idv::StepInfo.full_controller_name(idv_step_controller_class)). + to eq('/idv/lets/go/deeper/anonymous') + end + end end diff --git a/spec/support/controller_helper.rb b/spec/support/controller_helper.rb index 204ee8047a7..d8c33b1c171 100644 --- a/spec/support/controller_helper.rb +++ b/spec/support/controller_helper.rb @@ -34,22 +34,6 @@ def stub_sign_in_before_2fa(user = build(:user, password: VALID_PASSWORD)) controller.user_session[TwoFactorAuthenticatable::NEED_AUTHENTICATION] = true end - def stub_idv_steps_before_verify_step( - user, - applicant: Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE - ) - user_session = {} - stub_sign_in(user) - idv_session = Idv::Session.new( - user_session: user_session, current_user: user, - service_provider: nil - ) - idv_session.applicant = applicant - 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 - def stub_verify_steps_one_and_two( user, applicant: Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE diff --git a/spec/support/features/idv_step_helper.rb b/spec/support/features/idv_step_helper.rb index 23926d4f10b..4a1a429ba5a 100644 --- a/spec/support/features/idv_step_helper.rb +++ b/spec/support/features/idv_step_helper.rb @@ -40,7 +40,7 @@ def complete_idv_steps_before_gpo_step(user = user_with_2fa) enter_gpo_flow end - def gpo_step + def complete_request_letter click_on t('idv.buttons.mail.send') end @@ -101,7 +101,7 @@ def complete_idv_steps_with_phone_before_confirmation_step(user = user_with_2fa) def complete_idv_steps_with_gpo_before_enter_password_step(user = user_with_2fa) complete_idv_steps_before_gpo_step(user) - gpo_step + complete_request_letter end def complete_idv_steps_with_gpo_before_confirmation_step(user = user_with_2fa) diff --git a/spec/support/idv_examples/max_attempts.rb b/spec/support/idv_examples/max_attempts.rb index be2c90027c1..3c981b026f7 100644 --- a/spec/support/idv_examples/max_attempts.rb +++ b/spec/support/idv_examples/max_attempts.rb @@ -1,5 +1,6 @@ RSpec.shared_examples 'verification step max attempts' do |step, sp| include ActionView::Helpers::DateHelper + include DocAuthHelper let(:user) { user_with_2fa } @@ -8,51 +9,50 @@ complete_idv_steps_before_step(step, user) end - context 'after completing the max number of attempts' do - around do |ex| - freeze_time { ex.run } - end - - before do - perfom_maximum_allowed_idv_step_attempts(step) { fill_out_phone_form_fail } - end + context "#{sp ? "starting from sp" : "starting without an sp"} #{sp}" do + context 'after completing the max number of attempts' do + around do |ex| + freeze_time { ex.run } + end - scenario 'more than 3 attempts in 24 hours prevents further attempts' do - # Blocked if visiting verify directly - visit idv_url - expect_user_to_fail_at_phone_step + before do + perfom_maximum_allowed_idv_step_attempts(step) { fill_out_phone_form_fail } + end - # Blocked if visiting from an SP - visit_idp_from_sp_with_ial2(:oidc) - expect_user_to_fail_at_phone_step - end + scenario 'more than 3 attempts in 24 hours prevents further attempts' do + visit idv_url + complete_doc_auth_steps_before_verify_step + complete_verify_step + expect_user_to_fail_at_phone_step + end - scenario 'user sees the failure screen', js: true do - expect(page).to have_content(t('idv.failure.phone.rate_limited.heading')) - expect(page).to have_content( - strip_tags( - t( - 'idv.failure.phone.rate_limited.body', - time_left: distance_of_time_in_words( - IdentityConfig.store.idv_attempt_window_in_hours.hours, + scenario 'user sees the failure screen', js: true do + expect(page).to have_content(t('idv.failure.phone.rate_limited.heading')) + expect(page).to have_content( + strip_tags( + t( + 'idv.failure.phone.rate_limited.body', + time_left: distance_of_time_in_words( + IdentityConfig.store.idv_attempt_window_in_hours.hours, + ), ), ), - ), - ) + ) + end end - end - context 'after completing one less than the max attempts' do - it 'allows the user to continue if their last attempt is successful' do - (RateLimiter.max_attempts(:proof_address) - 1).times do - fill_out_phone_form_fail - click_idv_continue_for_step(step) - click_on t('idv.failure.phone.warning.try_again_button') - end + context 'after completing one less than the max attempts' do + it 'allows the user to continue if their last attempt is successful' do + (RateLimiter.max_attempts(:proof_address) - 1).times do + fill_out_phone_form_fail + click_idv_continue_for_step(step) + click_on t('idv.failure.phone.warning.try_again_button') + end - fill_out_phone_form_ok - verify_phone_otp - expect(page).to have_current_path(idv_enter_password_path, wait: 10) + fill_out_phone_form_ok + verify_phone_otp + expect(page).to have_current_path(idv_enter_password_path, wait: 10) + end end end