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-15216 Handle password reset for in-person enrollments waiting for fraud review #11855

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

shanechesnutt-ft
Copy link
Contributor

@shanechesnutt-ft shanechesnutt-ft commented Feb 7, 2025

🎫 Ticket

Link to the relevant ticket:
LG-15216

🛠 Summary of changes

Enable user's to not lose in-person enrollment progress after a password reset when the user in fraud review.

Changes Include:

  • New InPersonEnrollment status in_fraud_review
  • New metric for GetUspsProofingResultsJob enrollments_in_fraud_review
  • Update to the action account scripts to update relevant in-person enrollments base on result

📜 Testing Plan

In-Person Password Reset enabled

  • Set feature_pending_in_person_password_reset_enabled: true in application.yml.default

Scenario: Fraud review in-person enrollment and user password resets with personal key

  • Login through the oidc sinatra application selecting the Identity Verified level of service.
  • Create a new account
  • Complete the ID-IPP flow reaching the ready ssn page.
  • Mark the threat metrics as review on the ready ssn page.
  • Submit the ssn form.
  • Continue the ID-IPP flow reaching the ready to verify page.
  • Run the GetUspsProofingResultsJob
# To manually run the job in the rails console run the following:
job = GetUspsProofingResultsJob.new
job.perform(Time.zone.now)
  • Ensure the enrollment is updated to have in_fraud_review status.
  • Ensure user is navigated to the LG-99 screen.
  • Logout
  • Reset the password of the user.
  • Login through the oidc sinatra application selecting the Identity Verified level of service.
  • Ensure user is prompted for personal key entry
  • Enter in user's personal key
  • Ensure user is navigated to the LG-99 screen

Scenario: Fraud review in-person enrollment and user password resets without personal key

  • Login through the oidc sinatra application selecting the Identity Verified level of service.
  • Create a new account
  • Complete the ID-IPP flow reaching the ready ssn page.
  • Mark the threat metrics as review on the ready ssn page.
  • Submit the ssn form.
  • Continue the ID-IPP flow reaching the ready to verify page.
  • Run the GetUspsProofingResultsJob
# To manually run the job in the rails console run the following:
job = GetUspsProofingResultsJob.new
job.perform(Time.zone.now)
  • Ensure the enrollment is updated to have in_fraud_review status.
  • Ensure user is navigated to the LG-99 screen.
  • Logout
  • Reset the password of the user.
  • Login through the oidc sinatra application selecting the Identity Verified level of service.
  • Ensure user is prompted for personal key entry
  • Continue without a personal key
  • Ensure user is navigated to the welcome page
  • Click Continue on welcome page
  • Ensure enrollment is cancelled

Scenario: Fraud review in-person enrollment and user password resets before deployment

  • Checkout main and start the idp application
  • Login through the oidc sinatra application selecting the Identity Verified level of service.
  • Create a new account
  • Complete the ID-IPP flow reaching the ready ssn page.
  • Mark the threat metrics as review on the ready ssn page.
  • Submit the ssn form.
  • Continue the ID-IPP flow reaching the ready to verify page.
  • Run the GetUspsProofingResultsJob
# To manually run the job in the rails console run the following:
job = GetUspsProofingResultsJob.new
job.perform(Time.zone.now)
  • Ensure the enrollment is updated to have passed status.
  • Ensure user is navigated to the LG-99 screen.
  • Logout
  • Checkout LG-15216 and restart the idp application
  • Login through the oidc sinatra application selecting the Identity Verified level of service.
  • Ensure user is navigated to the LG-99 screen.
  • Logout
  • Reset the password of the user.
  • Login through the oidc sinatra application selecting the Identity Verified level of service.
  • Ensure user is navigated to the welcome page

Scenario: Fraud review in-person enrollment and user has passed review

  • Login through the oidc sinatra application selecting the Identity Verified level of service.
  • Create a new account
  • Complete the ID-IPP flow reaching the ready ssn page.
  • Mark the threat metrics as review on the ready ssn page.
  • Submit the ssn form.
  • Continue the ID-IPP flow reaching the ready to verify page.
  • Run the GetUspsProofingResultsJob
# To manually run the job in the rails console run the following:
job = GetUspsProofingResultsJob.new
job.perform(Time.zone.now)
  • Ensure the enrollment is updated to have in_fraud_review status.
  • Ensure user is navigated to the LG-99 screen.
  • Run the review-passed script bin/action-account review-passed <user_uuid>
  • Ensure the enrollment is updated to have passed status.
  • Ensure the user is prompted to activate their account.

Scenario: Fraud review in-person enrollment and user has failed review

  • Login through the oidc sinatra application selecting the Identity Verified level of service.
  • Create a new account
  • Complete the ID-IPP flow reaching the ready ssn page.
  • Mark the threat metrics as review on the ready ssn page.
  • Submit the ssn form.
  • Continue the ID-IPP flow reaching the ready to verify page.
  • Run the GetUspsProofingResultsJob
# To manually run the job in the rails console run the following:
job = GetUspsProofingResultsJob.new
job.perform(Time.zone.now)
  • Ensure the enrollment is updated to have in_fraud_review status.
  • Ensure user is navigated to the LG-99 screen.
  • Run the review-reject script bin/action-account review-reject <user_uuid>
  • Ensure the enrollment is updated to have failed status.
  • Ensure the user is redirected to the unverified page.

In-Person Password Reset disabled

  • Set feature_pending_in_person_password_reset_enabled: false in application.yml.default

Scenario: Fraud review in-person enrollment and user password resets

  • Login through the oidc sinatra application selecting the Identity Verified level of service.
  • Create a new account
  • Complete the ID-IPP flow reaching the ready ssn page.
  • Mark the threat metrics as review on the ready ssn page.
  • Submit the ssn form.
  • Continue the ID-IPP flow reaching the ready to verify page.
  • Run the GetUspsProofingResultsJob
# To manually run the job in the rails console run the following:
job = GetUspsProofingResultsJob.new
job.perform(Time.zone.now)
  • Ensure the enrollment is updated to have in_fraud_review status.
  • Ensure user is navigated to the LG-99 screen.
  • Logout
  • Login through the oidc sinatra application selecting the Identity Verified level of service.
  • Ensure user is navigated to the LG-99 screen.
  • Logout
  • Reset the password of the user.
  • Login through the oidc sinatra application selecting the Identity Verified level of service.
  • Ensure user is navigated to the welcome page

@shanechesnutt-ft shanechesnutt-ft requested review from a team and WilliamBirdsall February 7, 2025 19:33
@shanechesnutt-ft shanechesnutt-ft force-pushed the sc/LG-15216 branch 2 times, most recently from d8e42d5 to 4ccd4e7 Compare February 10, 2025 15:58
@@ -223,9 +223,9 @@ def in_person_enrollment_status
pending_profile&.in_person_enrollment&.status
end

def ipp_enrollment_status_not_passed?
def ipp_enrollment_status_not_passed_or_in_fraud_review?
Copy link
Contributor

Choose a reason for hiding this comment

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

This method could benefit from a comment / usage doc since it's public.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed in 2edd2c2

#
# @param user [User] The user model
def cancel_establishing_and_pending_enrollments(user)
def cancel_in_progress_enrollments(user)
Copy link
Contributor Author

@shanechesnutt-ft shanechesnutt-ft Feb 11, 2025

Choose a reason for hiding this comment

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

I am going to update this method to cancel_establishing_and_in_progress_enrollments to remove confusion as to what an in_progress enrollment means. See user model current_in_progress_in_person_enrollment_profile method for consistency. Which leads me to think I should add IN_PROGRESS_ENROLLMENT_STATUSES to the InPersonEnrollment model to help establish this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated in 2edd2c2

@@ -62,8 +62,9 @@ def create_document_capture_session

def cancel_previous_in_person_enrollments
return unless IdentityConfig.store.in_person_proofing_enabled
UspsInPersonProofing::EnrollmentHelper
Copy link
Contributor

Choose a reason for hiding this comment

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

Is check IdentityConfig.store.in_person_proofing_enabled needed? (I know this code predates you and you did not change it.)

If we disable in_person_proofing_enabled because of an outage and the user begins again, wouldn't we want a clean slate even if they are not able to move through ipp? Can you think of any disadvantages to deleting the check?

Copy link
Contributor

Choose a reason for hiding this comment

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

This is a good question. I'm not (yet) sure where I fall on this topic, but I am curious to hear more about it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Cc @matthinz because I feel like this is the type of thing we discussed recently. This method is called from the welcome controller's update method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that makes sense, but I feel that this change would be better in a different PR/User Story.

cc: @eileen-nava

Copy link
Contributor

Choose a reason for hiding this comment

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

@shanechesnutt-ft Would you be up for writing a ticket to address this feedback in a separate PR?

@WilliamBirdsall
Copy link
Contributor

Ran through all test steps successfully! Great job with this :)

pending_in_person_enrollment&.profile ||
profiles.where.not(activated_at: nil).order(activated_at: :desc).first
def find_in_person_in_progress_or_active_profile
current_in_progress_in_person_enrollment_profile || find_active_profile
Copy link
Contributor

@gina-yamada gina-yamada Feb 12, 2025

Choose a reason for hiding this comment

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

Originally, the only behavior was to find an active profile. When we enabled the epic, the default behavior will be to find an in person in progress or active profile. What do you think about changing the order of this so the default is to first look for an active profile because that was the original behavior? I think it would have a smaller affect on pw reset. (If for some reason in person enrollments was not getting cleaned up properly at some point- it would have a small impact because it would only happen if there was not an active profile.) I did not consider this in your previous pr.

What are your thoughts?

find_active_profile || current_in_progress_in_person_enrollment_profile

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So in the last Password Reset PR I prioritized the pending profile first because when the personal key is generated for a profile it will replace the previous personal key. This can occur when the user has an active legacy profile and a pending non-legacy profile. My logic was that since the personal key is replaced, the previous active account is dead when the user performs a password reset. So, rather than having them lose both profiles I would prioritize the latest profile (the pending profile) which would allow the user to use their latest personal key.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jmhooper does this sound reasonable? I know you reviewed the last PR and this didn't come up. I just wanted to make sure the way this is implemented makes sense.

Copy link
Contributor

Choose a reason for hiding this comment

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

@gina-yamada Good question! I agree with Shane's explanation and logic. Please feel free to say if you have more questions about this.

Copy link
Contributor

@gina-yamada gina-yamada Feb 13, 2025

Choose a reason for hiding this comment

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

Shane and I walked through this together prior to his post yesterday. He posted for visibility. I am okay with this now that I have more information. Thank you

Copy link
Contributor

@eileen-nava eileen-nava left a comment

Choose a reason for hiding this comment

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

Approved, this looks good.

@@ -62,8 +62,9 @@ def create_document_capture_session

def cancel_previous_in_person_enrollments
return unless IdentityConfig.store.in_person_proofing_enabled
UspsInPersonProofing::EnrollmentHelper
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a good question. I'm not (yet) sure where I fall on this topic, but I am curious to hear more about it.

@@ -104,7 +104,9 @@ def extra_analytics_attributes

def pending_profile_invalidated?
if FeatureManagement.pending_in_person_password_reset_enabled?
pending_profile.present? && !pending_profile.in_person_verification_pending?
pending_profile.present? &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for updating this.

@@ -142,8 +143,7 @@ def check_enrollment(enrollment)

def cancel_enrollment(enrollment)
enrollment_outcomes[:enrollments_cancelled] += 1
enrollment.cancelled!
enrollment.profile.deactivate_due_to_in_person_verification_cancelled
enrollment.cancel
Copy link
Contributor

Choose a reason for hiding this comment

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

👍🏻

allow(IdentityConfig.store).to receive(:in_person_proofing_enforce_tmx).and_return(false)
end

it 'returns nil' do
Copy link
Contributor

Choose a reason for hiding this comment

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

Tests look clear and thorough.

it 'includes that the profile was not deactivated in the form response' do
expect(@result.extra).to include(
user_id: user.uuid,
profile_deactivated: false,
Copy link
Contributor

Choose a reason for hiding this comment

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

👍🏻 to testing this

let(:profile) { enrollment.profile }

before do
@result = form.submit(params)
Copy link
Contributor

Choose a reason for hiding this comment

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

I have a question about test syntax and will follow up in a synchronous 1:1.

let!(:profile) { enrollment.profile }

before do
subtask.run(args:, config:)
Copy link
Contributor

Choose a reason for hiding this comment

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

I have a question about test syntax and will follow up in a synchronous 1:1.

@@ -509,6 +518,7 @@
expect(expired_enrollment.eligible_for_notification?).to eq(false)
expect(passed_enrollment_without_notification.eligible_for_notification?).to eq(false)
expect(failed_enrollment_without_notification.eligible_for_notification?).to eq(false)
expect(in_fraud_review_enrollment.eligible_for_notification?).to eq(false)
Copy link
Contributor

Choose a reason for hiding this comment

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

👍🏻

subject.cancel_establishing_and_pending_enrollments(user)
end
before do
subject.cancel_establishing_and_in_progress_enrollments(user)
Copy link
Contributor

Choose a reason for hiding this comment

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

I have a question about test syntax and will follow up in a synchronous 1:1.

Copy link
Contributor

@eileen-nava eileen-nava left a comment

Choose a reason for hiding this comment

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

Changes look good. 👍🏻

@@ -5,6 +5,7 @@ class FraudRejectionDailyJob < ApplicationJob

def perform(_date)
profiles_eligible_for_fraud_rejection.find_each do |profile|
profile.in_person_enrollment&.failed!
Copy link
Contributor

Choose a reason for hiding this comment

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

💯

changelog: User-facing Improvement, In-person Proofing, Allow users to
perform a password reset on pending in-person profiles in fraud review
without losing progress if the user supplies their personal key.
- Update enrollment helper method based on changes
end

it 'passes the enrollment and activates the profile' do
expect(enrollment.status).to eq('passed')
Copy link
Contributor

Choose a reason for hiding this comment

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

👏 Thanks for this spec! I was checking to ensure we updated the InPersonEnrollment status so it was no longer in_fraud_review.

Copy link
Contributor

@n1zyy n1zyy left a comment

Choose a reason for hiding this comment

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

Sorry for not getting to this sooner. There's a lot to make sense of here but it looks good, and I'm grateful that this long-standing issue causing users pain is being fixed. 👏

I have a couple of questions inline, but I'm giving this an approval conditional on the answer not being "Oh no everything is broken." 😉

@@ -223,9 +223,14 @@ def in_person_enrollment_status
pending_profile&.in_person_enrollment&.status
end

def ipp_enrollment_status_not_passed?
# Whether the user's in person enrollment status is not passed or in_fraud_review. Enrollments use
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# Whether the user's in person enrollment status is not passed or in_fraud_review. Enrollments use
# Whether the user's in person enrollment status is not passed or in_fraud_review. Enrollments used

The most trivial nit ever, but my eyes noticed it.

@@ -91,13 +91,15 @@ def cancel_stale_establishing_enrollments_for_user(user)
.find_each(&:cancelled!)
end

# Cancel a user's associated establishing and pending in-person enrollments.
# Cancel a user's associated establishing, pending, and in_fraud_review in-person enrollments.
Copy link
Contributor

Choose a reason for hiding this comment

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

Oxford comma FTW!

@@ -641,7 +641,7 @@ def send_enrollment_status_sms_notification(enrollment:)
end

def notification_delivery_params(enrollment)
return {} unless enrollment.passed? || enrollment.failed?
return {} unless enrollment.passed? || enrollment.failed? || enrollment.in_fraud_review?
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like I've gotten rusty in this section of code. Is the intent here to start sending delayed notices of fraud-review status? It's just not obvious to me what the significance of this is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah this isn't very obvious, but in the code before when the user passed at the post office and had been flagged for fraud review we would update the in-person enrollment to have a status of passed and send the please call email. In this PR we are changing things up a little bit by adding the new in_fraud_review status. We wanted to keep the same behavior with sending the email in the same way (with a delay) so I had to add the status check here.

@@ -281,6 +282,7 @@ def run(args:, config:)
elsif FraudReviewChecker.new(user).fraud_review_eligible?
profile = user.fraud_review_pending_profile
profile_fraud_review_pending_at = profile.fraud_review_pending_at
profile.in_person_enrollment&.passed!
Copy link
Contributor

Choose a reason for hiding this comment

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

This is another "Apologies that I don't remember this so well..." one, but I'm trying to make sure I understand what happens here in non-obvious cases.

  1. If I have an IPP enrollment, but don't actually go to the post office because I'm a fraudster, this won't mark me as a pass because I'm not fraud_review_eligible?, right?
  2. If I get in one of the muddled states where I abandon IPP, and continue with doc auth and get myself into fraud review, this won't mark me as passed because I don't have an enrollment, right?

This is a case where I think the logic is good but I just want to make sure we're not accidentally creating a vulnerability.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. Yeah that seems to be correct because user has not been to post office for their enrollment/profile to be updated. IPP enrollments profiles are not updated with a fraud_review_pending_at timestamp until the user goes to the post office.

  2. Yeah think that is also the case because the profile associated with the enrollment would be different from the one associated with doc auth. I can do some checks around this today. What exactly to do you mean by "muddled states where I abandon IPP, and continue with doc auth"? I want to make sure I test this case correctly.

@gina-yamada
Copy link
Contributor

gina-yamada commented Feb 13, 2025

@shanechesnutt-ft
👌 For Scenario: Fraud review in-person enrollment and user password resets without personal key: An LG-99 email was delivered immediately after I reset my PW and just after the PW reset email "got delivered". I would not expect this email to be delivered again. Double check if you observe this. (I feel like it should have occurred in test 1 as well but I may have just missed it.) Maybe a question for product if you see this behavior too.

UPDATE: After looking at the code, this email is only kicked off from the USPS job and is on a delay. I think just funny timing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants