diff --git a/app/controllers/idv/in_person_controller.rb b/app/controllers/idv/in_person_controller.rb index 3ee3e59b932..b1091df590b 100644 --- a/app/controllers/idv/in_person_controller.rb +++ b/app/controllers/idv/in_person_controller.rb @@ -9,21 +9,16 @@ class InPersonController < ApplicationController before_action :confirm_two_factor_authenticated before_action :redirect_unless_enrollment - - include IdvSessionConcern - include Flow::FlowStateMachine - include ThreatMetrixConcern - - before_action :redirect_if_flow_completed - + before_action :initialize_in_person_session before_action :set_usps_form_presenter - FLOW_STATE_MACHINE_SETTINGS = { - step_url: :idv_in_person_step_url, - final_url: :idv_in_person_state_id_url, - flow: Idv::Flows::InPersonFlow, - analytics_id: 'In Person Proofing', - }.freeze + def index + redirect_to idv_in_person_state_id_url + end + + def update + redirect_to idv_in_person_state_id_url + end private @@ -31,8 +26,8 @@ def redirect_unless_enrollment redirect_to idv_url unless current_user.establishing_in_person_enrollment end - def redirect_if_flow_completed - flow_finish if idv_session.applicant + def initialize_in_person_session + user_session['idv/in_person'] ||= { pii_from_user: { uuid: current_user.uuid } } end def set_usps_form_presenter diff --git a/app/services/flow/base_flow.rb b/app/services/flow/base_flow.rb deleted file mode 100644 index 18e201ed721..00000000000 --- a/app/services/flow/base_flow.rb +++ /dev/null @@ -1,85 +0,0 @@ -# frozen_string_literal: true - -module Flow - class BaseFlow - include Failure - - attr_accessor :flow_session - attr_reader :current_user, :current_sp, :params, :request, :json, - :http_status, :controller - - def initialize(controller, session) - @controller = controller - @redirect = nil - @json = nil - @flow_session = session - end - - def next_step - return @redirect if @redirect - step, _klass = steps.find do |_step, klass| - !@flow_session[klass.to_s] - end - step - end - - def redirect_to(url) - @redirect = url - end - - def render_json(json, status: nil) - @json = json - @http_status = status || :ok - end - - def step_handler(step) - steps[step] || actions[step] - end - - def step_handler_instance(step) - @step_handler_instances ||= {} - @step_handler_instances[step] ||= step_handler(step)&.new(self) - end - - def handle(step) - @flow_session[:error_message] = nil - handler = step_handler_instance(step) - return failure("Unhandled step #{step}") unless handler - wrap_send(handler) - end - - def extra_view_variables(step) - handler = step_handler_instance(step) - return failure("Unhandled step #{step}") unless handler - handler.extra_view_variables - end - - def extra_analytics_properties - {} - end - - def flow_path - 'standard' - end - - private - - def wrap_send(handler) - value = handler.base_call - form_response(handler, value) - end - - def form_response(obj, value) - response = BaseStep.acceptable_response_object?(value) ? value : successful_response - obj.mark_step_complete if response.success? - response - end - - def successful_response - FormResponse.new(success: true) - end - - delegate :flash, :session, :current_user, :current_sp, :params, :request, - :poll_with_meta_refresh, :analytics, to: :@controller - end -end diff --git a/app/services/flow/base_step.rb b/app/services/flow/base_step.rb deleted file mode 100644 index b273da8fd12..00000000000 --- a/app/services/flow/base_step.rb +++ /dev/null @@ -1,86 +0,0 @@ -# frozen_string_literal: true - -module Flow - class BaseStep - include Rails.application.routes.url_helpers - include Failure - - def initialize(flow, name) - @flow = flow - @name = name - end - - def base_call - form_response = form_submit - unless form_response.success? - flow_session[:error_message] = form_response.first_error_message - return form_response - end - create_response(form_response, call) - end - - def mark_step_complete(step = nil) - klass = step.nil? ? self.class : steps[step] - flow_session[klass.to_s] = true - end - - def mark_step_incomplete(step = nil) - klass = step.nil? ? self.class : steps[step] - flow_session.delete(klass.to_s) - nil - end - - def self.acceptable_response_object?(obj) - obj.is_a?(FormResponse) || obj.is_a?(DocAuth::Response) - end - - # Return a hash of local variables required for step view template - def extra_view_variables - {} - end - - def url_options - @flow.controller.url_options - end - - delegate :analytics_visited_event, :analytics_submitted_event, to: :class - - private - - def create_response(form_submit_response, call_response) - return form_submit_response unless BaseStep.acceptable_response_object?(call_response) - form_submit_response.merge(call_response) - end - - def form_submit - FormResponse.new(success: true) - end - - def flow_params - params[@name] - end - - def permit(*args) - params.require(:doc_auth).permit(*args) - end - - def redirect_to(url) - @flow.redirect_to(url) - end - - def render_json(json, status: nil) - @flow.render_json(json, status: status) - end - - def reset - @flow.flow_session = {} - end - - def amzn_trace_id - request.headers['X-Amzn-Trace-Id'] - end - - delegate :flash, :session, :flow_session, :current_user, :current_sp, :params, :steps, :request, - :poll_with_meta_refresh, to: :@flow - end -end diff --git a/app/services/flow/failure.rb b/app/services/flow/failure.rb deleted file mode 100644 index 23ac1d45e92..00000000000 --- a/app/services/flow/failure.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module Flow - module Failure - private - - def failure(message, extra = nil) - flow_session[:error_message] = message - form_response_params = { success: false, errors: { message: message } } - form_response_params[:extra] = extra unless extra.nil? - FormResponse.new(**form_response_params) - end - end -end diff --git a/app/services/flow/flow_state_machine.rb b/app/services/flow/flow_state_machine.rb deleted file mode 100644 index 0eab05f6c43..00000000000 --- a/app/services/flow/flow_state_machine.rb +++ /dev/null @@ -1,219 +0,0 @@ -# frozen_string_literal: true - -module Flow - module FlowStateMachine - extend ActiveSupport::Concern - include OptInHelper - - included do - before_action :initialize_flow_state_machine - before_action :ensure_correct_step, only: :show - end - - attr_accessor :flow - - def index - redirect_to idv_in_person_state_id_url - end - - def show - track_step_visited - render_step(current_step, flow.flow_session) - end - - def update - step = current_step - - return render_not_found unless flow.step_handler_instance(step).present? - - result = flow.handle(step) - - analytics.public_send( - flow.step_handler_instance(step).analytics_submitted_event, - **result.to_h.merge(analytics_properties), - ) - - register_update_step(step, result) - if flow.json - render json: flow.json, status: flow.http_status - return - end - flow_finish and return unless next_step - render_update(step, result) - end - - def poll_with_meta_refresh(seconds) - @meta_refresh = seconds - end - - private - - def current_step - params[:step]&.underscore - end - - def track_step_visited - analytics.public_send( - flow.step_handler(current_step).analytics_visited_event, - **analytics_properties, - ) - - Funnel::DocAuth::RegisterStep.new(user_id, issuer).call(current_step, :view, true) - end - - def user_id - current_user ? current_user.id : user_id_from_token - end - - def user_id_from_token - current_session[:doc_capture_user_id] - end - - def register_update_step(step, result) - Funnel::DocAuth::RegisterStep.new(user_id, issuer).call(step, :update, result.success?) - end - - def issuer - sp_session[:issuer] - end - - def initialize_flow_state_machine - klass = self.class - flow = klass::FLOW_STATE_MACHINE_SETTINGS[:flow] - @name = klass.name.underscore.gsub('_controller', '') - @namespace = flow.name.split('::').first.underscore - @step_url = klass::FLOW_STATE_MACHINE_SETTINGS[:step_url] - @final_url = klass::FLOW_STATE_MACHINE_SETTINGS[:final_url] - @analytics_id = klass::FLOW_STATE_MACHINE_SETTINGS[:analytics_id] - @view = klass::FLOW_STATE_MACHINE_SETTINGS[:view] - - current_session[@name] ||= {} - @flow = flow.new(self, current_session, @name) - end - - def render_update(step, result) - redirect_to next_step and return if next_step_is_url - move_to_next_step and return if result.success? - ensure_correct_step and return - set_error_and_render(step, result) - end - - def set_error_and_render(step, result) - flow_session = flow.flow_session - flow_session[:error_message] = result.first_error_message - render_step(step, flow_session) - end - - def move_to_next_step - current_session[@name] = flow.flow_session - redirect_to_step(next_step) - end - - def render_step(step, flow_session) - @params = params - @request = request - return if call_optional_show_step(step) - step_params = flow.extra_view_variables(step) - local_params = step_params.merge( - flow_namespace: @namespace, - flow_session: flow_session, - step_indicator: step_indicator_params, - step_template: "#{@view || @name}/#{step}", - ) - render template: 'layouts/flow_step', locals: local_params - end - - def call_optional_show_step(optional_step) - return unless @flow.class.const_defined?(:OPTIONAL_SHOW_STEPS) - optional_show_step = @flow.class::OPTIONAL_SHOW_STEPS.with_indifferent_access[optional_step] - return unless optional_show_step - result = optional_show_step.new(@flow).base_call - - optional_show_step_name = optional_show_step.to_s.demodulize.underscore - optional_properties = result.to_h.merge( - step: optional_show_step_name, - analytics_id: @analytics_id, - ) - - analytics.public_send( - optional_show_step.analytics_optional_step_event, - **optional_properties, - ) - - if next_step.to_s != optional_step - if next_step_is_url - redirect_to next_step - else - redirect_to_step(next_step) - end - return true - end - false - end - - def step_indicator_params - return if !flow.class.const_defined?(:STEP_INDICATOR_STEPS) - handler = flow.step_handler(current_step) - return if !handler || !handler.const_defined?(:STEP_INDICATOR_STEP) - { - steps: flow.class::STEP_INDICATOR_STEPS, - current_step: handler::STEP_INDICATOR_STEP, - } - end - - def ensure_correct_step - redirect_to_step(next_step) if next_step.to_s != current_step - end - - def flow_finish - redirect_to send(@final_url) - end - - def redirect_to_step(_step) - flow_finish and return unless next_step - redirect_url - end - - def redirect_url - redirect_to idv_in_person_state_id_url - end - - def analytics_properties - { - flow_path: flow.flow_path, - step: current_step, - analytics_id: @analytics_id, - }.merge(flow.extra_analytics_properties) - .merge(**opt_in_analytics_properties) - end - - def current_step_name - "#{current_step}_#{action_name}" - end - - def next_step - flow.next_step - end - - def next_step_is_url - next_step.to_s.index(':') - end - - def current_session - user_session || session - end - end -end - -# sample usage: -# -# class FooController -# include Flow::FlowStateMachine -# -# FLOW_STATE_MACHINE_SETTINGS = { -# step_url: :foo_step_url, -# final_url: :after_foo_url, -# flow: FooFlow, -# analytics_id: Analytics::FOO, -# }.freeze -# end diff --git a/app/services/idv/flows/in_person_flow.rb b/app/services/idv/flows/in_person_flow.rb deleted file mode 100644 index 3343481770d..00000000000 --- a/app/services/idv/flows/in_person_flow.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -module Idv - module Flows - class InPersonFlow < Flow::BaseFlow - attr_reader :idv_session # this is used by DocAuthBaseStep - - STEP_INDICATOR_STEPS = [ - { name: :find_a_post_office }, - { name: :verify_info }, - { name: :verify_phone }, - { name: :re_enter_password }, - { name: :go_to_the_post_office }, - ].freeze - - STEP_INDICATOR_STEPS_GPO = [ - { name: :find_a_post_office }, - { name: :verify_info }, - { name: :verify_address }, - { name: :secure_account }, - { name: :go_to_the_post_office }, - ].freeze - - def initialize(controller, session, name) - @idv_session = self.class.session_idv(session) - super(controller, session[name]) - @flow_session ||= {} - @flow_session[:pii_from_user] ||= { uuid: current_user.uuid } - # there may be data in @idv_session to copy to @flow_session - applicant = @idv_session['applicant'] || {} - @flow_session[:pii_from_user] = @flow_session[:pii_from_user].to_h.merge(applicant) - end - - def self.session_idv(session) - session[:idv] ||= {} - end - - def extra_analytics_properties - { - pii_like_keypaths: [ - [:proofing_results, :context, :stages, :state_id, :state_id_jurisdiction], - ], - } - end - end - end -end diff --git a/app/services/idv/steps/doc_auth_base_step.rb b/app/services/idv/steps/doc_auth_base_step.rb deleted file mode 100644 index 09434827548..00000000000 --- a/app/services/idv/steps/doc_auth_base_step.rb +++ /dev/null @@ -1,70 +0,0 @@ -# frozen_string_literal: true - -module Idv - module Steps - class DocAuthBaseStep < Flow::BaseStep - def initialize(flow) - super(flow, :doc_auth) - end - - private - - def user_id_from_token - flow_session[:doc_capture_user_id] - end - - def hybrid_flow_mobile? - user_id_from_token.present? - end - - def rate_limited_response - @flow.analytics.rate_limit_reached( - limiter_type: :idv_doc_auth, - ) - redirect_to rate_limited_url - DocAuth::Response.new( - success: false, - errors: { limit: I18n.t('doc_auth.errors.rate_limited_heading') }, - ) - end - - def rate_limited_url - idv_session_errors_rate_limited_url - end - - def user_id - current_user ? current_user.id : user_id_from_token - end - - def sp_session - session.fetch(:sp, {}) - end - - def create_document_capture_session(key) - document_capture_session = DocumentCaptureSession.create( - user_id: user_id, - issuer: sp_session[:issuer], - ) - flow_session[key] = document_capture_session.uuid - - document_capture_session - end - - def document_capture_session - @document_capture_session ||= DocumentCaptureSession.find_by( - uuid: flow_session[document_capture_session_uuid_key], - ) - end - - def document_capture_session_uuid_key - :document_capture_session_uuid - end - - def verify_step_document_capture_session_uuid_key - :idv_verify_step_document_capture_session_uuid - end - - delegate :idv_session, :session, :flow_path, to: :@flow - end - end -end diff --git a/app/views/idv/shared/_back.html.erb b/app/views/idv/shared/_back.html.erb index 88f45338b35..336e7ddbf97 100644 --- a/app/views/idv/shared/_back.html.erb +++ b/app/views/idv/shared/_back.html.erb @@ -5,15 +5,12 @@ path can be passed in case the HTTP header is not specified or is invalid. If no yield a useable URL, nothing will be rendered. locals: -* step_url: (Optional) Base target for flow step URL calls, falls back to @step_url. -* action: (Optional) Flow action to call to return to the previous step. * step: (Optional) Name of step to which user should be returned. * fallback_path: (Optional) Path to redirect absent action, step, and HTTP referer. %> <% text = '‹ ' + t('forms.buttons.back') - step_url = local_assigns[:step_url] || @step_url step = local_assigns[:action] || local_assigns[:step] - path = (step_url && step) ? send(step_url, step: step) : go_back_path + path = go_back_path path ||= local_assigns[:fallback_path] classes = [] classes << local_assigns[:class] if local_assigns[:class] %> diff --git a/config/routes.rb b/config/routes.rb index cfd32ce457d..324f53196b8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -417,6 +417,7 @@ # sometimes underscores get messed up when linked to via SMS as: :capture_doc_dashes get '/in_person' => 'in_person#index' + put '/in_person' => 'in_person#update' get '/in_person/ready_to_verify' => 'in_person/ready_to_verify#show', as: :in_person_ready_to_verify post '/in_person/usps_locations' => 'in_person/usps_locations#index' @@ -429,8 +430,6 @@ put '/in_person/ssn' => 'in_person/ssn#update' get '/in_person/verify_info' => 'in_person/verify_info#show' put '/in_person/verify_info' => 'in_person/verify_info#update' - get '/in_person/:step' => 'in_person#show', as: :in_person_step - put '/in_person/:step' => 'in_person#update' get '/by_mail/enter_code' => 'by_mail/enter_code#index', as: :verify_by_mail_enter_code post '/by_mail/enter_code' => 'by_mail/enter_code#create' diff --git a/spec/controllers/idv/in_person_controller_spec.rb b/spec/controllers/idv/in_person_controller_spec.rb index 462a79c83eb..477d2fe99dc 100644 --- a/spec/controllers/idv/in_person_controller_spec.rb +++ b/spec/controllers/idv/in_person_controller_spec.rb @@ -13,13 +13,13 @@ end describe 'before_actions' do - it 'includes corrects before_actions' do + it 'includes correct before_actions' do expect(subject).to have_actions( :before, :confirm_two_factor_authenticated, - :initialize_flow_state_machine, - :ensure_correct_step, :set_usps_form_presenter, + :redirect_unless_enrollment, + :initialize_in_person_session, ) end end @@ -53,6 +53,13 @@ create(:in_person_enrollment, :establishing, user: user, profile: nil) end + it 'initializes the in-person session' do + get :index + expect(controller.user_session['idv/in_person']).to include( + pii_from_user: { uuid: user.uuid }, + ) + end + it 'redirects to the first step' do get :index @@ -99,7 +106,7 @@ end it 'finishes the flow' do - put :update, params: { step: 'state_id' } + put :update expect(response).to redirect_to idv_in_person_state_id_path end diff --git a/spec/features/idv/in_person_threatmetrix_spec.rb b/spec/features/idv/in_person_threatmetrix_spec.rb index af9cb2644c2..2e3ec70ce93 100644 --- a/spec/features/idv/in_person_threatmetrix_spec.rb +++ b/spec/features/idv/in_person_threatmetrix_spec.rb @@ -211,9 +211,9 @@ def deactivate_profile_update_enrollment(status:) visit_idp_from_sp_with_ial2(sp) expect(page).to have_current_path(idv_please_call_path) - page.visit('/verify/welcome') + page.visit(idv_welcome_path) expect(page).to have_current_path(idv_please_call_path) - page.visit('/verify/in_person/document_capture') + page.visit(idv_in_person_state_id_path) expect(page).to have_current_path(idv_please_call_path) end @@ -226,7 +226,7 @@ def deactivate_profile_update_enrollment(status:) expect do review_pass.run(args: [user.uuid], config:) end.to(change { ActionMailer::Base.deliveries.count }.by(1)) - page.visit('/verify/welcome') + page.visit(idv_welcome_path) expect(page).to have_current_path(idv_activated_path) end end @@ -310,9 +310,9 @@ def deactivate_profile_update_enrollment(status:) visit_idp_from_sp_with_ial2(sp) expect(page).to have_current_path(idv_not_verified_path) - page.visit('/verify/welcome') + page.visit(idv_welcome_path) expect(page).to have_current_path(idv_not_verified_path) - page.visit('/verify/in_person/document_capture') + page.visit(idv_in_person_state_id_path) expect(page).to have_current_path(idv_not_verified_path) end end diff --git a/spec/views/idv/shared/_back.html.erb_spec.rb b/spec/views/idv/shared/_back.html.erb_spec.rb index 2741c0da966..bd7e24cab3e 100644 --- a/spec/views/idv/shared/_back.html.erb_spec.rb +++ b/spec/views/idv/shared/_back.html.erb_spec.rb @@ -1,7 +1,6 @@ require 'rails_helper' RSpec.describe 'idv/doc_auth/_back.html.erb' do - let(:step_url) { nil } let(:action) { nil } let(:step) { nil } let(:classes) { nil } @@ -9,7 +8,6 @@ subject do render 'idv/shared/back', { - step_url: step_url, action: action, step: step, class: classes, @@ -25,78 +23,6 @@ end end - context 'with step URL in locals' do - let(:step_url) { :idv_in_person_step_url } - - context 'with action' do - let(:action) { 'redo_ssn' } - - it 'renders' do - expect(subject).to have_selector( - "form[action='#{send(:idv_in_person_step_url, step: 'redo_ssn')}']", - ) - expect(subject).to have_selector('input[name="_method"][value="put"]', visible: false) - expect(subject).to have_selector("[type='submit']") - expect(subject).to have_selector('button', text: '‹ ' + t('forms.buttons.back')) - end - - it_behaves_like 'back link with class' - end - - context 'with step' do - let(:step) { 'verify' } - - it 'renders' do - expect(subject).to have_selector( - "a[href='#{send( - :idv_in_person_step_url, - step: 'verify', - )}']", - ) - expect(subject).to have_content('‹ ' + t('forms.buttons.back')) - end - - it_behaves_like 'back link with class' - end - end - - context 'with step URL in instance variable' do - before do - assign(:step_url, :idv_in_person_step_url) - end - - context 'with action' do - let(:action) { 'redo_ssn' } - - it 'renders' do - expect(subject).to have_selector( - "form[action='#{send(:idv_in_person_step_url, step: 'redo_ssn')}']", - ) - expect(subject).to have_selector('input[name="_method"][value="put"]', visible: false) - expect(subject).to have_selector("[type='submit']") - expect(subject).to have_selector('button', text: '‹ ' + t('forms.buttons.back')) - end - - it_behaves_like 'back link with class' - end - - context 'with step' do - let(:step) { 'verify' } - - it 'renders' do - expect(subject).to have_selector( - "a[href='#{send( - :idv_in_person_step_url, - step: 'verify', - )}']", - ) - expect(subject).to have_content('‹ ' + t('forms.buttons.back')) - end - - it_behaves_like 'back link with class' - end - end - context 'with back path' do before do allow(view).to receive(:go_back_path).and_return('/example')