Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LG-882 Invalid physical mail address #2775

Merged
merged 17 commits into from
Feb 25, 2019
5 changes: 5 additions & 0 deletions app/controllers/concerns/verify_profile_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@ def account_or_verify_profile_url

def account_or_verify_profile_route
return 'account' unless profile_needs_verification?
return 'idv_usps' if usps_mail_bounced?
'verify_account'
end

def profile_needs_verification?
return false if current_user.blank?
current_user.decorate.pending_profile_requires_verification?
end

def usps_mail_bounced?
current_user.decorate.usps_mail_bounced?
end
end
10 changes: 10 additions & 0 deletions app/controllers/idv/doc_auth_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module Idv
class DocAuthController < ApplicationController
before_action :confirm_two_factor_authenticated
before_action :redirect_if_mail_bounced
before_action :redirect_if_pending_profile

include IdvSession # remove if we retire the non docauth LOA3 flow
include Flow::FlowStateMachine
Expand All @@ -11,5 +13,13 @@ class DocAuthController < ApplicationController
flow: Idv::Flows::DocAuthFlow,
analytics_id: Analytics::DOC_AUTH,
}.freeze

def redirect_if_mail_bounced
redirect_to idv_usps_url if current_user.decorate.usps_mail_bounced?
end

def redirect_if_pending_profile
redirect_to verify_account_url if current_user.decorate.pending_profile_requires_verification?
end
end
end
103 changes: 102 additions & 1 deletion app/controllers/idv/usps_controller.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# :reek:TooManyMethods
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This controller has gotten to almost 160 lines, which to me looks like a signal that some of this should be moved into a service

module Idv
class UspsController < ApplicationController
class UspsController < ApplicationController # rubocop:disable Metrics/ClassLength
include IdvSession

before_action :confirm_two_factor_authenticated
before_action :confirm_idv_needed
before_action :confirm_user_completed_idv_profile_step
before_action :confirm_mail_not_spammed
before_action :max_attempts_reached, only: [:update]

def index
@presenter = UspsPresenter.new(current_user)
Expand All @@ -23,12 +25,67 @@ def create
end
end

def update
result = submit_form_and_perform_resolution
analytics.track_event(Analytics::IDV_USPS_ADDRESS_SUBMITTED, result.to_h)
result.success? ? resolution_success(pii) : failure
end

def usps_mail_service
@_usps_mail_service ||= Idv::UspsMail.new(current_user)
end

private

def submit_form_and_perform_resolution
result = idv_form.submit(profile_params)
result = perform_resolution(pii) if result.success?
result
end

def failure
redirect_to idv_usps_url unless performed?
end

def pii
hash = {}
update_hash_with_address(hash)
update_hash_with_non_address_pii(hash)
hash
end

def update_hash_with_address(hash)
profile_params.each { |key, value| hash[key] = value }
end

def update_hash_with_non_address_pii(hash)
pii_h = pii_to_h
%w[first_name middle_name last_name dob phone ssn].each do |key|
hash[key] = pii_h[key]
end
end

def pii_to_h
JSON.parse(user_session[:decrypted_pii])
end

def resolution_success(hash)
idv_session_settings(hash).each { |key, value| user_session['idv'][key] = value }
resend_letter
redirect_to idv_review_url
end

def idv_session_settings(hash)
{ 'vendor_phone_confirmation': false,
'user_phone_confirmation': false,
'resolution_successful': 'phone',
'address_verification_mechanism': 'usps',
'profile_confirmation': true,
'params': hash,
'applicant': hash,
'uuid': current_user.uuid }
end

def confirm_mail_not_spammed
redirect_to idv_review_url if idv_session.address_mechanism_chosen? &&
usps_mail_service.mail_spammed?
Expand All @@ -54,5 +111,49 @@ def resend_letter
return unless FeatureManagement.reveal_usps_code?
session[:last_usps_confirmation_code] = confirmation_maker.otp
end

def idv_form
Idv::AddressForm.new(
user: current_user,
previous_params: idv_session.previous_profile_step_params,
)
end

def profile_params
params.require(:idv_form).permit(Idv::AddressForm::ATTRIBUTES)
end

def perform_resolution(pii_from_doc)
idv_result = Idv::Agent.new(pii_from_doc).proof(:resolution)
success = idv_result[:success]
throttle_failure unless success
form_response(idv_result, success)
end

def form_response(result, success)
FormResponse.new(success: success, errors: result[:errors])
end

def throttle_failure
attempter.increment
flash_error
end

def flash_error
flash[:error] = error_message
redirect_to idv_usps_url
end

def max_attempts_reached
flash_error if attempter.exceeded?
end

def error_message
I18n.t('idv.failure.sessions.' + (attempter.exceeded? ? 'fail' : 'heading'))
end

def attempter
@attempter ||= Idv::Attempter.new(idv_session.current_user)
end
end
end
5 changes: 5 additions & 0 deletions app/decorators/user_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ def identity_verified?
user.active_profile.present?
end

def usps_mail_bounced?
return unless pending_profile
pending_profile&.usps_confirmation_codes&.order(created_at: :desc)&.first&.bounced_at
end

def active_profile_newer_than_pending_profile?
user.active_profile.activated_at >= pending_profile.created_at
end
Expand Down
12 changes: 12 additions & 0 deletions app/presenters/idv/usps_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ def title
letter_already_sent? ? I18n.t('idv.titles.mail.resend') : I18n.t('idv.titles.mail.verify')
end

def byline
if current_user.decorate.usps_mail_bounced?
I18n.t('idv.messages.usps.new_address')
else
I18n.t('idv.messages.usps.address_on_file')
end
end

def button
letter_already_sent? ? I18n.t('idv.buttons.mail.resend') : I18n.t('idv.buttons.mail.send')
end
Expand All @@ -22,6 +30,10 @@ def cancel_path
idv_cancel_path
end

def usps_mail_bounced?
current_user.decorate.usps_mail_bounced?
end

private

def usps_mail_service
Expand Down
1 change: 1 addition & 0 deletions app/services/analytics.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def browser
IDV_PHONE_RECORD_VISIT = 'IdV: phone of record visited'.freeze
IDV_REVIEW_COMPLETE = 'IdV: review complete'.freeze
IDV_REVIEW_VISIT = 'IdV: review info visited'.freeze
IDV_USPS_ADDRESS_SUBMITTED = 'IdV: USPS address submitted'.freeze
IDV_VERIFICATION_ATTEMPT_CANCELLED = 'IdV: verification attempt cancelled'.freeze
INVALID_AUTHENTICITY_TOKEN = 'Invalid Authenticity Token'.freeze
LOGOUT_INITIATED = 'Logout Initiated'.freeze
Expand Down
6 changes: 6 additions & 0 deletions app/services/flow/base_flow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@ def initialize(steps, actions, session, current_user)
@steps = steps.with_indifferent_access
@actions = actions.with_indifferent_access
@params = nil
@redirect = nil
@flow_session = session
end

def next_step
return @redirect if @redirect
step, _klass = steps.detect do |_step, klass|
!@flow_session[klass.to_s]
end
step
end

def redirect_to(url)
@redirect = url
end

def handle(step, request, params)
@flow_session[:error_message] = nil
handler = steps[step] || actions[step]
Expand Down
12 changes: 8 additions & 4 deletions app/services/flow/base_step.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ module Flow
class BaseStep
include Rails.application.routes.url_helpers

def initialize(context, name)
@context = context
def initialize(flow, name)
@flow = flow
@form_response = nil
@name = name
end
Expand Down Expand Up @@ -43,10 +43,14 @@ def permit(*args)
params.require(@name).permit(*args)
end

def redirect_to(url)
@flow.redirect_to(url)
end

def reset
@context.flow_session = {}
@flow.flow_session = {}
end

delegate :flow_session, :current_user, :params, :steps, :request, to: :@context
delegate :flow_session, :current_user, :params, :steps, :request, to: :@flow
end
end
20 changes: 16 additions & 4 deletions app/services/flow/flow_state_machine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module FlowStateMachine
attr_accessor :flow

def index
redirect_to_step(flow.next_step)
redirect_to_step(next_step)
end

def show
Expand All @@ -23,6 +23,7 @@ def update
step = params[:step]
result = flow.handle(step, request, params)
analytics.track_event(analytics_submitted, result.to_h.merge(step: step)) if @analytics_id
flow_finish and return unless next_step
render_update(step, result)
end

Expand All @@ -37,16 +38,20 @@ def fsm_initialize
end

def render_update(step, result)
flow_finish and return unless flow.next_step
redirect_to next_step and return if next_step_is_url
move_to_next_step and return if result.success?
set_error_and_render(step, result)
end

def set_error_and_render(step, result)
flow_session = flow.flow_session
flow_session[:error_message] = result.errors.values.join(' ')
render_step(step, flow_session)
end

def move_to_next_step
user_session[@name] = flow.flow_session
redirect_to_step(flow.next_step)
redirect_to_step(next_step)
end

def render_step(step, flow_session)
Expand All @@ -56,7 +61,6 @@ def render_step(step, flow_session)
end

def ensure_correct_step
next_step = flow.next_step
redirect_to_step(next_step) if next_step.to_s != params[:step]
end

Expand All @@ -75,6 +79,14 @@ def analytics_submitted
def analytics_visited
@analytics_id + ' visited'
end

def next_step
flow.next_step
end

def next_step_is_url
next_step.to_s.index(':')
end
end
end

Expand Down
1 change: 0 additions & 1 deletion app/services/idv/flows/doc_auth_flow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ class DocAuthFlow < Flow::BaseFlow
mobile_back_image: Idv::Steps::MobileBackImageStep,
ssn: Idv::Steps::SsnStep,
verify: Idv::Steps::VerifyStep,
doc_failed: Idv::Steps::DocFailedStep,
doc_success: Idv::Steps::DocSuccessStep,
}.freeze

Expand Down
17 changes: 14 additions & 3 deletions app/services/idv/steps/doc_auth_base_step.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module Idv
module Steps
class DocAuthBaseStep < Flow::BaseStep
def initialize(context)
def initialize(flow)
@assure_id = nil
super(context, :doc_auth)
super(flow, :doc_auth)
end

private
Expand All @@ -27,7 +27,18 @@ def simulate?
Figaro.env.acuant_simulator == 'true'
end

delegate :idv_session, to: :@context
def attempter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This diff is pretty heavy. I think including changes like this, which aren't totally necessary for the change on the table in a separate PR would help slim things down and make this easier to review in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You said you would not approve it without the throttling.

@attempter ||= Idv::Attempter.new(current_user)
end

def idv_failure(result)
attempter.increment
type = attempter.exceeded? ? :fail : :warning
redirect_to idv_session_failure_url(reason: type)
result
end

delegate :idv_session, to: :@flow
end
end
end
9 changes: 0 additions & 9 deletions app/services/idv/steps/doc_failed_step.rb

This file was deleted.

Loading