Skip to content
6 changes: 6 additions & 0 deletions app/controllers/idv/doc_auth_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class DocAuthController < ApplicationController
before_action :confirm_two_factor_authenticated
before_action :redirect_if_mail_bounced
before_action :redirect_if_pending_profile
before_action :redirect_if_pending_in_person_enrollment
before_action :extend_timeout_using_meta_refresh_for_select_paths

include IdvSession # remove if we retire the non docauth LOA3 flow
Expand Down Expand Up @@ -36,6 +37,11 @@ def redirect_if_pending_profile
redirect_to idv_gpo_verify_url if current_user.decorate.pending_profile_requires_verification?
end

def redirect_if_pending_in_person_enrollment
return if !IdentityConfig.store.in_person_proofing_enabled
redirect_to idv_in_person_ready_to_verify_url if current_user.pending_in_person_enrollment
end

def update_if_skipping_upload
return if params[:step] != 'upload' || !flow_session || !flow_session[:skip_upload_step]
track_step_visited
Expand Down
8 changes: 6 additions & 2 deletions app/controllers/idv/gpo_verify_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ def create
sp_name: decorated_session.sp_name,
disavowal_token: event.disavowal_token,
)
flash[:success] = t('account.index.verification.success')
redirect_to sign_up_completed_url
if result.extra[:pending_in_person_enrollment]
redirect_to idv_in_person_ready_to_verify_url
else
flash[:success] = t('account.index.verification.success')
redirect_to sign_up_completed_url
end
else
flash[:error] = @gpo_verify_form.errors.first.message
redirect_to idv_gpo_verify_url
Expand Down
8 changes: 7 additions & 1 deletion app/controllers/idv/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class SessionsController < ApplicationController

def destroy
cancel_verification_attempt_if_pending_profile
cancel_in_person_enrollment_if_exists
analytics.idv_start_over(
step: location_params[:step],
location: location_params[:location],
Expand All @@ -19,10 +20,15 @@ def destroy
private

def cancel_verification_attempt_if_pending_profile
return if current_user.profiles.verification_pending.blank?
return if current_user.profiles.gpo_verification_pending.blank?
Idv::CancelVerificationAttempt.new(user: current_user).call
end

def cancel_in_person_enrollment_if_exists
return if !IdentityConfig.store.in_person_proofing_enabled
current_user.pending_in_person_enrollment&.update(status: :cancelled)
end

def location_params
params.permit(:step, :location).to_h.symbolize_keys
end
Expand Down
21 changes: 17 additions & 4 deletions app/forms/api/profile_creation_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,29 @@ def cache_encrypted_pii
end

def complete_session
complete_profile if phone_confirmed?
create_gpo_entry if user_bundle.gpo_address_verification?
if user_bundle.gpo_address_verification?
profile.deactivate(:gpo_verification_pending)
create_gpo_entry
elsif phone_confirmed?
if pending_in_person_enrollment?
profile.deactivate(:in_person_verification_pending)
else
complete_profile
end
end
end

def pending_in_person_enrollment?
return false unless IdentityConfig.store.in_person_proofing_enabled
ProofingComponent.find_by(user: user)&.document_check == Idp::Constants::Vendors::USPS
end

def phone_confirmed?
user_bundle.vendor_phone_confirmation? && user_bundle.user_phone_confirmation?
end

def complete_profile
user.pending_profile&.activate
profile.activate
move_pii_to_user_session
end

Expand Down Expand Up @@ -127,7 +140,7 @@ def form_valid?
def extra_attributes
if user.present?
@extra_attributes ||= {
profile_pending: user.pending_profile?,
profile_pending: user_bundle.gpo_address_verification?,
user_uuid: user.uuid,
}
else
Expand Down
12 changes: 11 additions & 1 deletion app/forms/gpo_verify_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ def initialize(user:, otp: nil)
def submit
result = valid?
if result
activate_profile
if pending_in_person_enrollment?
user.pending_profile&.deactivate(:in_person_verification_pending)
else
activate_profile
end
else
reset_sensitive_fields
end
Expand All @@ -26,6 +30,7 @@ def submit
errors: errors,
extra: {
pii_like_keypaths: [[:errors, :otp], [:error_details, :otp]],
pending_in_person_enrollment: pending_in_person_enrollment?,
},
)
end
Expand Down Expand Up @@ -65,6 +70,11 @@ def reset_sensitive_fields
self.otp = nil
end

def pending_in_person_enrollment?
return false unless IdentityConfig.store.in_person_proofing_enabled
user.pending_in_person_enrollment.present?
end

def activate_profile
user.pending_profile&.activate
end
Expand Down
2 changes: 1 addition & 1 deletion app/models/in_person_enrollment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class InPersonEnrollment < ApplicationRecord
passed: 2,
failed: 3,
expired: 4,
canceled: 5,
cancelled: 5,
}

validate :profile_belongs_to_user
Expand Down
3 changes: 2 additions & 1 deletion app/models/profile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ class Profile < ApplicationRecord
enum deactivation_reason: {
password_reset: 1,
encryption_error: 2,
verification_pending: 3,
gpo_verification_pending: 3,
verification_cancelled: 4,
in_person_verification_pending: 5,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Oh nice this makes sense

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since the enums are plain ints in the Db, what about renaming 3 to gpo_verification_pending?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since the enums are plain ints in the Db, what about renaming 3 to gpo_verification_pending?

Yeah, I think that makes sense. Along the same lines, in an earlier iteration I had also renamed the UserDecorator#pending_profile_requires_verification? method to pending_profile_requires_gpo_verification?. I have a feeling there's a lot of different references to this "pending" state, so I may want to be a bit conservative with those updates for now. I think renaming the deactivation reason may be a good start for now at least.

Copy link
Copy Markdown
Contributor Author

@aduth aduth Jul 27, 2022

Choose a reason for hiding this comment

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

Also, it may be worth noting that, in practice, "verification pending" is used exclusively to mean GPO verification, the process of creating and activating a profile does seem to treat it as a bit more generic, i.e. any new profile is pending by default until activated (see Idv::ProfileMaker#save_profile).

I wonder if we'd even need the deactivation_reason for the initialization of the profile? Or if we could just assume active: false would suffice on its own, and the GPO proofing path would be responsible for assigning that deactivation reason.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I didn't realize that profiles start as pending like that. That makes me think maybe we would want to keep a single pending enum and maybe add a separate enum column for what it's waiting on (gpo, ipp, etc), unsure if deactivation reason is a super good fit for that? But it is already there.

Copy link
Copy Markdown
Contributor Author

@aduth aduth Jul 27, 2022

Choose a reason for hiding this comment

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

That's what I was getting at with the second paragraph of my previous comment. It seems like we already have the two-field "pending" + "what it's waiting on" via active: false + deactivation_reason:. Currently, we initialize an active: false profile with deactivation_reason: :verification_pending, but I'm not sure there's a reason we have to?

In other words:

# New profile
Profile.new(active: false, deactivation_reason: nil)

# GPO verification profile
Profile.new(active: false, deactivation_reason: :gpo_verification_pending)

# IPP verification profile
Profile.new(active: false, deactivation_reason: :in_person_verification_pending)

# Active profile
Profile.new(active: true, deactivation_reason: nil)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I pushed what I had in mind in 3dec5e2 . Also worth noting that with the logic flow for initializing a profile, the profile would immediately be either activated or assigned deactivation_reason: :gpo_verification_pending or deactivation_reason: :in_person_verification_pending depending on the route the user had selected during the proofing attempt.

idv_session.create_profile_from_applicant_with_password(password)
idv_session.cache_encrypted_pii(password)
idv_session.complete_session

Because of this, we could even consider changing save_profile to accept deactivation_reason as an optional argument and assign it at creation, which could have the added advantage of avoiding a second database update.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah that seems the clearest overall. And yes also updating save_profile to save a round trip seems worthwhile as well, maybe for a follow up PR if this one is getting bogged down

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm going to merge this and follow-on with a few other improvements (more exhaustive specs, etc). I may do it there or in a follow-on-follow-on.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm going to merge this and follow-on with a few other improvements (more exhaustive specs, etc). I may do it there or in a follow-on-follow-on.

For posterity: #6680, #6681

}

attr_reader :personal_key
Expand Down
2 changes: 1 addition & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def pending_profile?
end

def pending_profile
profiles.verification_pending.order(created_at: :desc).first
profiles.gpo_verification_pending.order(created_at: :desc).first
end

def default_phone_configuration
Expand Down
2 changes: 1 addition & 1 deletion app/services/idv/cancel_verification_attempt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def initialize(user:)
end

def call
user.profiles.verification_pending.each do |profile|
user.profiles.gpo_verification_pending.each do |profile|
profile.update!(
active: false,
deactivation_reason: :verification_cancelled,
Expand Down
5 changes: 1 addition & 4 deletions app/services/idv/profile_maker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ def initialize(applicant:, user:, user_password:)
end

def save_profile
profile = Profile.new(
deactivation_reason: :verification_pending,
user: user,
)
profile = Profile.new(user: user, active: false)
profile.encrypt_pii(pii_attributes, user_password)
profile.proofing_components = current_proofing_components
profile.save!
Expand Down
19 changes: 16 additions & 3 deletions app/services/idv/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,30 @@ def clear
user_session.delete(:idv)
end

def pending_in_person_enrollment?
return false unless IdentityConfig.store.in_person_proofing_enabled
ProofingComponent.find_by(user: current_user)&.document_check == Idp::Constants::Vendors::USPS
end

def phone_confirmed?
vendor_phone_confirmation == true && user_phone_confirmation == true
end

def complete_session
complete_profile if phone_confirmed?
create_gpo_entry if address_verification_mechanism == 'gpo'
if address_verification_mechanism == 'gpo'
profile.deactivate(:gpo_verification_pending)
create_gpo_entry
elsif phone_confirmed?
if pending_in_person_enrollment?
profile.deactivate(:in_person_verification_pending)
else
complete_profile
end
end
end

def complete_profile
current_user.pending_profile&.activate
profile.activate
move_pii_to_user_session
end

Expand Down
2 changes: 1 addition & 1 deletion spec/controllers/accounts_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
user = create(
:user,
:signed_up,
profiles: [build(:profile, deactivation_reason: :verification_pending)],
profiles: [build(:profile, deactivation_reason: :gpo_verification_pending)],
)

sign_in user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,13 @@ def stub_idv_session
end

context 'with pending profile' do
let(:jwt_metadata) { { vendor_phone_confirmation: false, user_phone_confirmation: false } }
let(:jwt_metadata) do
{
vendor_phone_confirmation: false,
user_phone_confirmation: false,
address_verification_mechanism: 'gpo',
}
end

it 'creates a profile and returns completion url' do
post :create, params: { password: password, user_bundle_token: jwt }
Expand Down
25 changes: 25 additions & 0 deletions spec/controllers/idv/gpo_verify_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
'IdV: GPO verification submitted',
success: true,
errors: {},
pending_in_person_enrollment: false,
pii_like_keypaths: [[:errors, :otp], [:error_details, :otp]],
)

Expand All @@ -106,6 +107,28 @@
expect(disavowal_event_count).to eq 1
expect(response).to redirect_to(sign_up_completed_url)
end

context 'with pending in person enrollment' do
let(:user) { create(:user, :with_pending_in_person_enrollment) }

before do
allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true)
end

it 'redirects to ready to verify screen' do
expect(@analytics).to receive(:track_event).with(
'IdV: GPO verification submitted',
success: true,
errors: {},
pending_in_person_enrollment: true,
pii_like_keypaths: [[:errors, :otp], [:error_details, :otp]],
)

action

expect(response).to redirect_to(idv_in_person_ready_to_verify_url)
end
end
end

context 'with an invalid form' do
Expand All @@ -116,6 +139,7 @@
'IdV: GPO verification submitted',
success: false,
errors: { otp: [t('errors.messages.confirmation_code_incorrect')] },
pending_in_person_enrollment: false,
error_details: { otp: [:confirmation_code_incorrect] },
pii_like_keypaths: [[:errors, :otp], [:error_details, :otp]],
)
Expand All @@ -142,6 +166,7 @@
'IdV: GPO verification submitted',
success: false,
errors: { otp: [t('errors.messages.confirmation_code_incorrect')] },
pending_in_person_enrollment: false,
error_details: { otp: [:confirmation_code_incorrect] },
pii_like_keypaths: [[:errors, :otp], [:error_details, :otp]],
).exactly(max_attempts).times
Expand Down
2 changes: 1 addition & 1 deletion spec/controllers/idv/sessions_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
let(:user) do
create(
:user,
profiles: [create(:profile, deactivation_reason: :verification_pending)],
profiles: [create(:profile, deactivation_reason: :gpo_verification_pending)],
)
end

Expand Down
4 changes: 2 additions & 2 deletions spec/controllers/users/sessions_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@
user = create(:user, :signed_up)
create(
:profile,
deactivation_reason: :verification_pending,
deactivation_reason: :gpo_verification_pending,
user: user, pii: { ssn: '1234' }
)

Expand Down Expand Up @@ -564,7 +564,7 @@
it 'redirects to the verify profile page' do
profile = create(
:profile,
deactivation_reason: :verification_pending,
deactivation_reason: :gpo_verification_pending,
pii: { ssn: '6666', dob: '1920-01-01' },
)
user = profile.user
Expand Down
32 changes: 31 additions & 1 deletion spec/features/idv/in_person_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,18 @@
expect(page).to have_content(t('in_person_proofing.headings.barcode'))
expect(page).to have_content(Idv::InPerson::EnrollmentCodeFormatter.format(enrollment_code))
expect(page).to have_content(t('in_person_proofing.body.barcode.deadline', deadline: deadline))

# signing in again before completing in-person proofing at a post office
sign_in_and_2fa_user(user)
complete_doc_auth_steps_before_welcome_step
expect(page).to have_current_path(idv_in_person_ready_to_verify_path)
end

context 'verify address by mail (GPO letter)' do
before do
allow(FeatureManagement).to receive(:reveal_gpo_code?).and_return(true)
end

it 'requires address verification before showing instructions', allow_browser_log: true do
begin_in_person_proofing
complete_all_in_person_proofing_steps
Expand All @@ -109,7 +118,28 @@
expect(page).to have_content(t('idv.titles.come_back_later'))
expect(page).to have_current_path(idv_come_back_later_path)

# WILLFIX: After LG-6897, assert that "Ready to Verify" content is shown after code entry.
click_idv_continue
expect(page).to have_current_path(account_path)
expect(page).not_to have_content(t('headings.account.verified_account'))
click_on t('account.index.verification.reactivate_button')
click_button t('forms.verify_profile.submit')

expect(page).to have_current_path(idv_in_person_ready_to_verify_path)
expect(page).not_to have_content(t('account.index.verification.success'))
end

it 'lets the user clear and start over from gpo confirmation', allow_browser_log: true do
Comment thread
sheldon-b marked this conversation as resolved.
begin_in_person_proofing
complete_all_in_person_proofing_steps
click_on t('idv.troubleshooting.options.verify_by_mail')
click_on t('idv.buttons.mail.send')
complete_review_step
acknowledge_and_confirm_personal_key
click_idv_continue
click_on t('account.index.verification.reactivate_button')
click_on t('idv.messages.clear_and_start_over')

expect(page).to have_current_path(idv_doc_auth_welcome_step)
end
end
end
2 changes: 1 addition & 1 deletion spec/features/idv/steps/gpo_step_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def expect_user_to_be_unverified(user)
profile = user.profiles.first

expect(profile.active?).to eq false
expect(profile.deactivation_reason).to eq 'verification_pending'
expect(profile.deactivation_reason).to eq 'gpo_verification_pending'
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
it 'does not prompt a pending user for a mailed code' do
user = create(
:profile,
deactivation_reason: :verification_pending,
deactivation_reason: :gpo_verification_pending,
pii: { first_name: 'John', ssn: '111223333' },
).user

Expand Down
2 changes: 1 addition & 1 deletion spec/features/saml/ial2_sso_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def sign_out_user
let(:profile) do
create(
:profile,
deactivation_reason: :verification_pending,
deactivation_reason: :gpo_verification_pending,
pii: { ssn: '6666', dob: '1920-01-01' },
)
end
Expand Down
Loading