diff --git a/.stylelintrc.json b/.stylelintrc.json index b0496c335ba..d975da860ba 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,3 +1,4 @@ { - "extends": "@18f/identity-stylelint-config" + "extends": "@18f/identity-stylelint-config", + "ignoreFiles": "**/fixtures/**/*" } diff --git a/app/controllers/api/verify/base_controller.rb b/app/controllers/api/verify/base_controller.rb deleted file mode 100644 index 64d8f526df8..00000000000 --- a/app/controllers/api/verify/base_controller.rb +++ /dev/null @@ -1,25 +0,0 @@ -module Api - module Verify - class BaseController < ApplicationController - skip_before_action :verify_authenticity_token - - before_action :confirm_two_factor_authenticated_for_api - respond_to :json - - private - - def render_errors(errors, status: :bad_request) - render json: { errors: errors }, status: status - end - - def confirm_two_factor_authenticated_for_api - return if user_authenticated_for_api? - render_errors({ user: 'Unauthorized' }, status: :unauthorized) - end - - def user_authenticated_for_api? - user_fully_authenticated? - end - end - end -end diff --git a/app/controllers/api/verify/document_capture_controller.rb b/app/controllers/api/verify/document_capture_controller.rb deleted file mode 100644 index 1badfde7d0a..00000000000 --- a/app/controllers/api/verify/document_capture_controller.rb +++ /dev/null @@ -1,88 +0,0 @@ -module Api - module Verify - class DocumentCaptureController < BaseController - include ApplicationHelper - include EffectiveUser - - def create - result = Idv::ApiDocumentVerificationForm.new( - verify_params, - analytics: analytics, - irs_attempts_api_tracker: irs_attempts_api_tracker, - flow_path: params[:flow_path], - ).submit - - if result.success? - enqueue_job - - render json: { success: true, status: 'in_progress' }, status: :accepted - else - render_errors(result.errors) - end - end - - private - - def enqueue_job - verify_document_capture_session = DocumentCaptureSession. - find_by(uuid: params[:document_capture_session_uuid]) - verify_document_capture_session.requested_at = Time.zone.now - verify_document_capture_session.create_doc_auth_session - - applicant = { - user_uuid: effective_user.uuid, - uuid_prefix: current_sp&.app_id, - document_arguments: document_attributes, - } - Idv::Agent.new(applicant).proof_document( - verify_document_capture_session, - trace_id: amzn_trace_id, - image_metadata: image_metadata, - analytics_data: { - browser_attributes: analytics.browser_attributes, - }, - flow_path: params[:flow_path], - ) - nil - end - - def document_attributes - verify_params.slice( - :encryption_key, - :front_image_iv, - :back_image_iv, - :front_image_url, - :back_image_url, - ).to_h - end - - def verify_params - params.permit( - :encryption_key, - :front_image_iv, - :back_image_iv, - :front_image_url, - :back_image_url, - :document_capture_session_uuid, - :flow_path, - ) - end - - def image_metadata - params.permit(:front_image_metadata, :back_image_metadata). - to_h. - transform_values do |str| - JSON.parse(str, symbolize_names: true) - rescue JSON::ParserError - nil - end. - compact. - transform_keys { |key| key.gsub(/_image_metadata$/, '') } - end - - def user_authenticated_for_api? - !!effective_user - end - end - end -end diff --git a/app/controllers/api/verify/document_capture_errors_controller.rb b/app/controllers/api/verify/document_capture_errors_controller.rb deleted file mode 100644 index d454039ff66..00000000000 --- a/app/controllers/api/verify/document_capture_errors_controller.rb +++ /dev/null @@ -1,27 +0,0 @@ -module Api - module Verify - class DocumentCaptureErrorsController < BaseController - include EffectiveUser - - def delete - form = DocumentCaptureErrorsDeleteForm.new( - document_capture_session_uuid: params[:document_capture_session_uuid], - ) - result, document_capture_session = form.submit - - if result.success? - document_capture_session.update(ocr_confirmation_pending: false) - render json: {} - else - render json: { errors: result.errors }, status: :bad_request - end - end - - private - - def user_authenticated_for_api? - !!effective_user - end - end - end -end diff --git a/app/controllers/concerns/idv_step_concern.rb b/app/controllers/concerns/idv_step_concern.rb index 4f76b343709..5a0c5187d80 100644 --- a/app/controllers/concerns/idv_step_concern.rb +++ b/app/controllers/concerns/idv_step_concern.rb @@ -6,6 +6,17 @@ module IdvStepConcern included do before_action :confirm_two_factor_authenticated before_action :confirm_idv_needed + before_action :confirm_no_pending_gpo_profile + before_action :confirm_no_pending_in_person_enrollment + end + + def confirm_no_pending_gpo_profile + redirect_to idv_gpo_verify_url if current_user.pending_profile_requires_verification? + end + + def confirm_no_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 flow_session diff --git a/app/controllers/idv/document_capture_controller.rb b/app/controllers/idv/document_capture_controller.rb index 1da8c6a0642..50f62cda427 100644 --- a/app/controllers/idv/document_capture_controller.rb +++ b/app/controllers/idv/document_capture_controller.rb @@ -36,22 +36,11 @@ def update end def extra_view_variables - url_builder = ImageUploadPresignedUrlGenerator.new - { document_capture_session_uuid: flow_session[:document_capture_session_uuid], flow_path: 'standard', sp_name: decorated_session.sp_name, failure_to_proof_url: return_to_sp_failure_to_proof_url(step: 'document_capture'), - - front_image_upload_url: url_builder.presigned_image_upload_url( - image_type: 'front', - transaction_id: flow_session[:document_capture_session_uuid], - ), - back_image_upload_url: url_builder.presigned_image_upload_url( - image_type: 'back', - transaction_id: flow_session[:document_capture_session_uuid], - ), }.merge( acuant_sdk_upgrade_a_b_testing_variables, in_person_cta_variant_testing_variables, diff --git a/app/controllers/idv/gpo_controller.rb b/app/controllers/idv/gpo_controller.rb index 4d243172618..f8772cc7d40 100644 --- a/app/controllers/idv/gpo_controller.rb +++ b/app/controllers/idv/gpo_controller.rb @@ -7,6 +7,7 @@ class GpoController < ApplicationController before_action :confirm_idv_needed before_action :confirm_user_completed_idv_profile_step before_action :confirm_mail_not_spammed + before_action :confirm_profile_not_too_old def index @presenter = GpoPresenter.new(current_user, url_options) @@ -38,6 +39,10 @@ def gpo_mail_service private + def confirm_profile_not_too_old + redirect_to idv_path if gpo_mail_service.profile_too_old? + end + def step_indicator_current_step if resend_requested? :get_a_letter diff --git a/app/controllers/idv/gpo_verify_controller.rb b/app/controllers/idv/gpo_verify_controller.rb index 8d20615f032..33a1608db9d 100644 --- a/app/controllers/idv/gpo_verify_controller.rb +++ b/app/controllers/idv/gpo_verify_controller.rb @@ -10,10 +10,14 @@ class GpoVerifyController < ApplicationController def index analytics.idv_gpo_verification_visited gpo_mail = Idv::GpoMail.new(current_user) - @mail_spammed = gpo_mail.mail_spammed? @gpo_verify_form = GpoVerifyForm.new(user: current_user, pii: pii) @code = session[:last_gpo_confirmation_code] if FeatureManagement.reveal_gpo_code? + @user_can_request_another_gpo_code = + FeatureManagement.gpo_verification_enabled? && + !gpo_mail.mail_spammed? && + !gpo_mail.profile_too_old? + if throttle.throttled? render_throttled else diff --git a/app/controllers/idv/hybrid_mobile/document_capture_controller.rb b/app/controllers/idv/hybrid_mobile/document_capture_controller.rb index 80ffa3c2f1f..2ae28c72c13 100644 --- a/app/controllers/idv/hybrid_mobile/document_capture_controller.rb +++ b/app/controllers/idv/hybrid_mobile/document_capture_controller.rb @@ -36,20 +36,10 @@ def update end def extra_view_variables - url_builder = ImageUploadPresignedUrlGenerator.new - { flow_path: 'hybrid', document_capture_session_uuid: document_capture_session_uuid, failure_to_proof_url: return_to_sp_failure_to_proof_url(step: 'document_capture'), - front_image_upload_url: url_builder.presigned_image_upload_url( - image_type: 'front', - transaction_id: document_capture_session_uuid, - ), - back_image_upload_url: url_builder.presigned_image_upload_url( - image_type: 'back', - transaction_id: document_capture_session_uuid, - ), }.merge( native_camera_ab_testing_variables, acuant_sdk_upgrade_a_b_testing_variables, diff --git a/app/controllers/idv/link_sent_controller.rb b/app/controllers/idv/link_sent_controller.rb index a0b34d7f27c..dd5c5d4ccd5 100644 --- a/app/controllers/idv/link_sent_controller.rb +++ b/app/controllers/idv/link_sent_controller.rb @@ -5,7 +5,6 @@ class LinkSentController < ApplicationController include StepIndicatorConcern include StepUtilitiesConcern - before_action :render_404_if_link_sent_controller_disabled before_action :confirm_two_factor_authenticated before_action :confirm_upload_step_complete before_action :confirm_document_capture_needed @@ -43,10 +42,6 @@ def extra_view_variables private - def render_404_if_link_sent_controller_disabled - render_not_found unless IdentityConfig.store.doc_auth_link_sent_controller_enabled - end - def confirm_upload_step_complete return if flow_session['Idv::Steps::UploadStep'] diff --git a/app/controllers/idv/session_errors_controller.rb b/app/controllers/idv/session_errors_controller.rb index f3bb6d80e8e..8d6c19fc62e 100644 --- a/app/controllers/idv/session_errors_controller.rb +++ b/app/controllers/idv/session_errors_controller.rb @@ -28,6 +28,7 @@ def failure throttle_type: :idv_resolution, ) @expires_at = throttle.expires_at + @sp_name = decorated_session.sp_name log_event(based_on_throttle: throttle) end diff --git a/app/controllers/idv/sessions_controller.rb b/app/controllers/idv/sessions_controller.rb index 0021a4f9fa1..bdd5293c374 100644 --- a/app/controllers/idv/sessions_controller.rb +++ b/app/controllers/idv/sessions_controller.rb @@ -43,7 +43,7 @@ def cancel_processing end def cancel_verification_attempt_if_pending_profile - return if current_user.profiles.gpo_verification_pending.blank? + return if !current_user.pending_profile? Idv::CancelVerificationAttempt.new(user: current_user).call end diff --git a/app/controllers/users/phones_controller.rb b/app/controllers/users/phones_controller.rb index db35275226a..1efdd37f611 100644 --- a/app/controllers/users/phones_controller.rb +++ b/app/controllers/users/phones_controller.rb @@ -13,11 +13,13 @@ class PhonesController < ApplicationController def add user_session[:phone_id] = nil @new_phone_form = NewPhoneForm.new(user: current_user, analytics: analytics) + analytics.add_phone_setup_visit end def create @new_phone_form = NewPhoneForm.new(user: current_user, analytics: analytics) result = @new_phone_form.submit(user_params) + analytics.multi_factor_auth_phone_setup(**result.to_h) if result.success? confirm_phone elsif recoverable_recaptcha_error?(result) diff --git a/app/forms/api/verify/document_capture_errors_delete_form.rb b/app/forms/api/verify/document_capture_errors_delete_form.rb deleted file mode 100644 index 0d217ba7403..00000000000 --- a/app/forms/api/verify/document_capture_errors_delete_form.rb +++ /dev/null @@ -1,43 +0,0 @@ -module Api - module Verify - class DocumentCaptureErrorsDeleteForm - include ActiveModel::Model - - validates_presence_of :document_capture_session_uuid - validate :validate_document_capture_session - - attr_reader :document_capture_session_uuid - - def initialize(document_capture_session_uuid: nil) - @document_capture_session_uuid = document_capture_session_uuid - end - - def submit - result = FormResponse.new( - success: valid?, - errors: errors, - ) - - [result, document_capture_session] - end - - private - - def validate_document_capture_session - return if document_capture_session || !document_capture_session_uuid - errors.add( - :document_capture_session_uuid, - 'Invalid document capture session', - type: :invalid_document_capture_session, - ) - end - - def document_capture_session - return @document_capture_session if defined?(@document_capture_session) - @document_capture_session = DocumentCaptureSession.find_by( - uuid: document_capture_session_uuid, - ) - end - end - end -end diff --git a/app/forms/gpo_verify_form.rb b/app/forms/gpo_verify_form.rb index dbecf153599..18c8d5cd7a0 100644 --- a/app/forms/gpo_verify_form.rb +++ b/app/forms/gpo_verify_form.rb @@ -22,6 +22,7 @@ def submit UspsInPersonProofing::EnrollmentHelper.schedule_in_person_enrollment(user, pii) pending_profile&.deactivate(:in_person_verification_pending) elsif fraud_check_failed? && threatmetrix_enabled? + pending_profile&.remove_gpo_deactivation_reason deactivate_for_fraud_review else activate_profile @@ -55,7 +56,6 @@ def gpo_confirmation_code def deactivate_for_fraud_review pending_profile&.deactivate_for_fraud_review - pending_profile&.update!(deactivation_reason: nil, verified_at: Time.zone.now) end def validate_otp_not_expired @@ -94,6 +94,7 @@ def fraud_check_failed? end def activate_profile - user.pending_profile&.activate_after_gpo_verification + pending_profile&.remove_gpo_deactivation_reason + pending_profile&.activate end end diff --git a/app/forms/idv/api_document_verification_form.rb b/app/forms/idv/api_document_verification_form.rb deleted file mode 100644 index 83f6b9cc182..00000000000 --- a/app/forms/idv/api_document_verification_form.rb +++ /dev/null @@ -1,123 +0,0 @@ -module Idv - class ApiDocumentVerificationForm - include ActiveModel::Model - include ActionView::Helpers::TranslationHelper - - validates_presence_of :encryption_key - validate :validate_image_urls - validates_presence_of :document_capture_session - validates_presence_of :front_image_iv - validates_presence_of :back_image_iv - - validate :throttle_if_rate_limited - - def initialize( - params, - analytics:, - irs_attempts_api_tracker:, - flow_path: nil - ) - @params = params - @analytics = analytics - @irs_attempts_api_tracker = irs_attempts_api_tracker - @flow_path = flow_path - end - - def submit - increment_throttle! - - response = FormResponse.new( - success: valid?, - errors: errors, - extra: { - remaining_attempts: remaining_attempts, - flow_path: @flow_path, - }, - ) - - @analytics.idv_doc_auth_submitted_image_upload_form( - **response.to_h, - ) - - response - end - - def remaining_attempts - return unless document_capture_session - throttle.remaining_count - end - - def document_capture_session_uuid - params[:document_capture_session_uuid] - end - - def document_capture_session - @document_capture_session ||= DocumentCaptureSession.find_by( - uuid: document_capture_session_uuid, - ) - end - - private - - attr_reader :params - - def encryption_key - params[:encryption_key] - end - - def front_image_iv - params[:front_image_iv] - end - - def back_image_iv - params[:back_image_iv] - end - - def valid_url?(key) - uri = params[key] - parsed_uri = URI.parse(uri) - parsed_uri.scheme.present? && parsed_uri.host.present? - rescue URI::InvalidURIError - false - end - - def throttle_if_rate_limited - return unless @throttled - @analytics.throttler_rate_limit_triggered(throttle_type: :idv_doc_auth) - @irs_attempts_api_tracker.idv_document_upload_rate_limited - errors.add(:limit, t('errors.doc_auth.throttled_heading'), type: :throttled) - end - - def increment_throttle! - return unless document_capture_session - throttle.increment! - @throttled = throttle.throttled? - end - - def throttle - @throttle ||= Throttle.new( - user: document_capture_session.user, - throttle_type: :idv_doc_auth, - ) - end - - def validate_image_urls - unless valid_url?(:front_image_url) - errors.add( - :front_image_url, invalid_link, - type: :invalid_link - ) - end - unless valid_url?(:back_image_url) - errors.add( - :back_image_url, invalid_link, - type: :invalid_link - ) - end - end - - def invalid_link - t('doc_auth.errors.not_a_file') - end - end -end diff --git a/app/forms/idv/api_document_verification_status_form.rb b/app/forms/idv/api_document_verification_status_form.rb deleted file mode 100644 index 414fa7e673b..00000000000 --- a/app/forms/idv/api_document_verification_status_form.rb +++ /dev/null @@ -1,53 +0,0 @@ -module Idv - class ApiDocumentVerificationStatusForm - include ActiveModel::Model - include ActionView::Helpers::TranslationHelper - - validate :timeout_error - validate :failed_result - validates_presence_of :document_capture_session - - def initialize(async_state:, document_capture_session:) - @async_state = async_state - @document_capture_session = document_capture_session - end - - def submit - FormResponse.new( - success: valid?, - errors: errors, - extra: { - remaining_attempts: remaining_attempts, - doc_auth_result: @async_state&.result&.[](:doc_auth_result), - }, - ) - end - - def remaining_attempts - return unless @document_capture_session - Throttle.new( - user: @document_capture_session.user, - throttle_type: :idv_doc_auth, - ).remaining_count - end - - def timeout_error - return unless @async_state.missing? - errors.add( - :timeout, t('errors.doc_auth.document_verification_timeout'), - type: :document_verification_timeout - ) - end - - def failed_result - return if !@async_state.done? || @async_state.result[:success] - @async_state.result[:errors].each do |key, error| - errors.add(key, error, type: error) - end - end - - private - - attr_reader :document_capture_session - end -end diff --git a/app/helpers/aws_s3_helper.rb b/app/helpers/aws_s3_helper.rb deleted file mode 100644 index f8dd7958718..00000000000 --- a/app/helpers/aws_s3_helper.rb +++ /dev/null @@ -1,26 +0,0 @@ -module AwsS3Helper - def s3_presigned_url(...) - URI.parse(s3_object(...).presigned_url(:put, expires_in: presigned_url_expiration_in_seconds)) - end - - def s3_resource - Aws::S3::Resource.new(region: Identity::Hostdata.aws_region) - rescue Aws::Sigv4::Errors::MissingCredentialsError => aws_error - Rails.logger.info "Aws Missing CredentialsError!\n" + aws_error.message - nil - end - - def s3_object(bucket_prefix:, keyname:) - raise(ArgumentError, 'keyname is required') if keyname.blank? - raise(ArgumentError, 'bucket_prefix is required') if bucket_prefix.blank? - return if !s3_resource - - s3_resource.bucket( - Identity::Hostdata.bucket_name("#{bucket_prefix}-#{Identity::Hostdata.env}"), - ).object(keyname) - end - - def presigned_url_expiration_in_seconds - IdentityConfig.store.session_total_duration_timeout_in_minutes.minutes.seconds.to_i - end -end diff --git a/app/helpers/csp_helper.rb b/app/helpers/csp_helper.rb deleted file mode 100644 index 8c790226aad..00000000000 --- a/app/helpers/csp_helper.rb +++ /dev/null @@ -1,11 +0,0 @@ -module CspHelper - def add_document_capture_image_urls_to_csp(request, urls) - cleaned_urls = urls.compact.map do |url| - URI(url).tap { |uri| uri.query = nil }.to_s - end - - policy = request.content_security_policy.clone - policy.connect_src(*policy.connect_src, *cleaned_urls) - request.content_security_policy = policy - end -end diff --git a/app/javascript/packages/build-sass/CHANGELOG.md b/app/javascript/packages/build-sass/CHANGELOG.md index 4153de0f449..1779cd56b61 100644 --- a/app/javascript/packages/build-sass/CHANGELOG.md +++ b/app/javascript/packages/build-sass/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +### Improvements + +- Adds support for ".scss" file extension, as an alternative to the current ".css.scss" support. In both cases, the output files use the basename with a ".css" extension. + ## 1.2.0 ### Improvements diff --git a/app/javascript/packages/build-sass/README.md b/app/javascript/packages/build-sass/README.md index 31ec8fcc31a..09c3858c0ba 100644 --- a/app/javascript/packages/build-sass/README.md +++ b/app/javascript/packages/build-sass/README.md @@ -23,7 +23,7 @@ Default behavior includes: Invoke the included `build-sass` executable with the source files and any relevant command flags. ``` -npx build-sass path/to/sass/*.css.scss --out-dir=build +npx build-sass path/to/sass/*.scss --out-dir=build ``` Flags: diff --git a/app/javascript/packages/build-sass/fixtures/css-scss-extension/.gitignore b/app/javascript/packages/build-sass/fixtures/css-scss-extension/.gitignore new file mode 100644 index 00000000000..abdfa7226b6 --- /dev/null +++ b/app/javascript/packages/build-sass/fixtures/css-scss-extension/.gitignore @@ -0,0 +1 @@ +styles.css diff --git a/app/javascript/packages/build-sass/fixtures/css-scss-extension/styles.css.scss b/app/javascript/packages/build-sass/fixtures/css-scss-extension/styles.css.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/javascript/packages/build-sass/fixtures/scss-extension/.gitignore b/app/javascript/packages/build-sass/fixtures/scss-extension/.gitignore new file mode 100644 index 00000000000..abdfa7226b6 --- /dev/null +++ b/app/javascript/packages/build-sass/fixtures/scss-extension/.gitignore @@ -0,0 +1 @@ +styles.css diff --git a/app/javascript/packages/build-sass/fixtures/scss-extension/styles.scss b/app/javascript/packages/build-sass/fixtures/scss-extension/styles.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/javascript/packages/build-sass/index.js b/app/javascript/packages/build-sass/index.js index cad1aae6e76..9348c688ad4 100644 --- a/app/javascript/packages/build-sass/index.js +++ b/app/javascript/packages/build-sass/index.js @@ -35,7 +35,7 @@ export async function buildFile(file, options) { quietDeps: true, }); - let outFile = basename(file, '.scss'); + let outFile = `${basename(basename(file, '.css.scss'), '.scss')}.css`; const lightningResult = lightningTransform({ filename: outFile, diff --git a/app/javascript/packages/build-sass/index.spec.js b/app/javascript/packages/build-sass/index.spec.js new file mode 100644 index 00000000000..c85c00b1c7f --- /dev/null +++ b/app/javascript/packages/build-sass/index.spec.js @@ -0,0 +1,26 @@ +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { stat } from 'node:fs/promises'; +import { buildFile } from './index.js'; + +const cwd = dirname(fileURLToPath(import.meta.url)); + +describe('buildFile', () => { + context('with .css.scss file extension', () => { + it('writes a file with the same basename and a .css extension', async () => { + const fixtureDir = join(cwd, 'fixtures/css-scss-extension'); + await buildFile(join(fixtureDir, 'styles.css.scss'), { outDir: fixtureDir }); + + await stat(join(fixtureDir, 'styles.css')); + }); + }); + + context('with .scss file extension', () => { + it('writes a file with the same basename and a .css extension', async () => { + const fixtureDir = join(cwd, 'fixtures/scss-extension'); + await buildFile(join(fixtureDir, 'styles.scss'), { outDir: fixtureDir }); + + await stat(join(fixtureDir, 'styles.css')); + }); + }); +}); diff --git a/app/jobs/address_proofing_job.rb b/app/jobs/address_proofing_job.rb index bce9e13aa88..9a757726b7f 100644 --- a/app/jobs/address_proofing_job.rb +++ b/app/jobs/address_proofing_job.rb @@ -54,6 +54,8 @@ def address_proofer base_url: IdentityConfig.store.lexisnexis_base_url, username: IdentityConfig.store.lexisnexis_username, password: IdentityConfig.store.lexisnexis_password, + hmac_key_id: IdentityConfig.store.lexisnexis_hmac_key_id, + hmac_secret_key: IdentityConfig.store.lexisnexis_hmac_secret_key, request_mode: IdentityConfig.store.lexisnexis_request_mode, ) end diff --git a/app/jobs/document_proofing_job.rb b/app/jobs/document_proofing_job.rb deleted file mode 100644 index 8009426e42c..00000000000 --- a/app/jobs/document_proofing_job.rb +++ /dev/null @@ -1,165 +0,0 @@ -class DocumentProofingJob < ApplicationJob - include JobHelpers::StaleJobHelper - - queue_as :high_document_proofing - - discard_on JobHelpers::StaleJobHelper::StaleJobError - - def perform( - result_id:, - encrypted_arguments:, - trace_id:, - image_metadata:, - analytics_data:, - flow_path: - ) - timer = JobHelpers::Timer.new - - raise_stale_job! if stale_job?(enqueued_at) - - dcs = DocumentCaptureSession.find_by(result_id: result_id) - user = dcs.user - - decrypted_args = JSON.parse( - Encryption::Encryptors::BackgroundProofingArgEncryptor.new.decrypt(encrypted_arguments), - symbolize_names: true, - ) - document_args = decrypted_args[:document_arguments] - user_uuid = decrypted_args.fetch(:user_uuid, nil) - uuid_prefix = decrypted_args.fetch(:uuid_prefix, nil) - - encryption_key = Base64.decode64(document_args[:encryption_key].to_s) - front_image_iv = Base64.decode64(document_args[:front_image_iv].to_s) - back_image_iv = Base64.decode64(document_args[:back_image_iv].to_s) - front_image_url = document_args[:front_image_url] - back_image_url = document_args[:back_image_url] - - front_image = decrypt_image_from_s3( - timer: timer, name: :front, url: front_image_url, iv: front_image_iv, key: encryption_key, - ) - back_image = decrypt_image_from_s3( - timer: timer, name: :back, url: back_image_url, iv: back_image_iv, key: encryption_key, - ) - - analytics = build_analytics(dcs) - doc_auth_client = build_doc_auth_client(analytics, dcs) - - proofer_result = timer.time('proof_documents') do - doc_auth_client.post_images( - front_image: front_image, - back_image: back_image, - image_source: image_source(image_metadata), - user_uuid: user_uuid, - uuid_prefix: uuid_prefix, - ) - end - - dcs.store_doc_auth_result( - result: proofer_result.to_h, # pii_from_doc is excluded from to_h to stop accidental logging - pii: proofer_result.pii_from_doc, - ) - - throttle = Throttle.new(user: user, throttle_type: :idv_doc_auth) - - analytics.idv_doc_auth_submitted_image_upload_vendor( - **proofer_result.to_h.merge( - state: proofer_result.pii_from_doc[:state], - state_id_type: proofer_result.pii_from_doc[:state_id_type], - async: true, - attempts: throttle.attempts, - remaining_attempts: throttle.remaining_count, - client_image_metrics: image_metadata, - flow_path: flow_path, - ).merge(analytics_data), - ) - ensure - logger.info( - { - name: 'ProofDocument', - trace_id: trace_id, - success: proofer_result&.success?, - timing: timer.results, - }.to_json, - ) - end - - private - - def build_analytics(document_capture_session) - Analytics.new( - user: document_capture_session.user, - request: nil, - sp: document_capture_session.issuer, - session: {}, - ) - end - - def build_doc_auth_client(analytics, document_capture_session) - DocAuthRouter.client( - vendor_discriminator: document_capture_session.uuid, - warn_notifier: proc { |attrs| analytics.doc_auth_warning(**attrs) }, - ) - end - - def encryption_helper - @encryption_helper ||= JobHelpers::EncryptionHelper.new - end - - def image_source(image_metadata) - if acuant_sdk_capture?(image_metadata) - DocAuth::ImageSources::ACUANT_SDK - else - DocAuth::ImageSources::UNKNOWN - end - end - - def normalize_image_file(file_or_data_url) - return file_or_data_url if !file_or_data_url.start_with?('data:') - - data_url_image = Idv::DataUrlImage.new(file_or_data_url) - data_url_image.read - rescue Idv::DataUrlImage::InvalidUrlFormatError - file_or_data_url - end - - def acuant_sdk_capture?(image_metadata) - image_metadata.dig(:front, :source) == Idp::Constants::Vendors::ACUANT && - image_metadata.dig(:back, :source) == Idp::Constants::Vendors::ACUANT - end - - def s3_helper - @s3_helper ||= JobHelpers::S3Helper.new - end - - def decrypt_image_from_s3(timer:, name:, url:, iv:, key:) - encrypted_image = timer.time("download.#{name}") do - if s3_helper.s3_url?(url) - s3_helper.download(url) - else - build_faraday.get(url) do |req| - req.options.context = { service_name: 'document_proofing_image_download' } - end.body.b - end - end - decrypted = timer.time("decrypt.#{name}") do - encryption_helper.decrypt(data: encrypted_image, iv: iv, key: key) - end - timer.time("decode.#{name}") do - normalize_image_file(decrypted) - end - end - - # @return [Faraday::Connection] builds a Faraday instance with our defaults - def build_faraday - Faraday.new do |conn| - conn.options.timeout = IdentityConfig.store.doc_auth_s3_request_timeout - conn.options.read_timeout = IdentityConfig.store.doc_auth_s3_request_timeout - conn.options.open_timeout = IdentityConfig.store.doc_auth_s3_request_timeout - conn.options.write_timeout = IdentityConfig.store.doc_auth_s3_request_timeout - conn.request :instrumentation, name: 'request_log.faraday' - - # raises errors on 4XX or 5XX responses - conn.response :raise_error - end - end -end diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index 712187b824e..650e1ef1a85 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -8,6 +8,7 @@ class ResolutionProofingJob < ApplicationJob CallbackLogData = Struct.new( :result, :resolution_success, + :residential_resolution_success, :state_id_success, :device_profiling_success, keyword_init: true, @@ -53,6 +54,7 @@ def perform( name: 'ProofResolution', trace_id: trace_id, resolution_success: callback_log_data&.resolution_success, + residential_resolution_success: callback_log_data&.residential_resolution_success, state_id_success: callback_log_data&.state_id_success, device_profiling_success: callback_log_data&.device_profiling_success, timing: timer.results, @@ -87,6 +89,7 @@ def make_vendor_proofing_requests( CallbackLogData.new( device_profiling_success: result.device_profiling_result.success?, resolution_success: result.resolution_result.success?, + residential_resolution_success: result.residential_resolution_result.success?, result: result.adjudicated_result.to_h, state_id_success: result.state_id_result.success?, ) diff --git a/app/models/profile.rb b/app/models/profile.rb index c274cd0cd00..f9c698e45a3 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -58,9 +58,8 @@ def activate end # rubocop:enable Rails/SkipsModelValidations - def activate_after_gpo_verification + def remove_gpo_deactivation_reason update!(gpo_verification_pending_at: nil) - activate end def activate_after_passing_review diff --git a/app/models/user.rb b/app/models/user.rb index 02323a85587..3fc8a3ef785 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -102,8 +102,16 @@ def pending_profile? pending_profile.present? end + def gpo_verification_pending_profile? + gpo_verification_pending_profile.present? + end + def pending_profile - profiles.gpo_verification_pending.order(created_at: :desc).first + gpo_verification_pending_profile + end + + def gpo_verification_pending_profile + profiles.where.not(gpo_verification_pending_at: nil).order(created_at: :desc).first end def fraud_review_eligible? diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 451701ee8f7..bd3cd2aada9 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -664,6 +664,19 @@ def idv_in_person_prepare_submitted(flow_path:, in_person_cta_variant:, **extra) ) end + # @param [String] nontransliterable_characters + # Nontransliterable characters submitted by user + def idv_in_person_proofing_nontransliterable_characters_submitted( + nontransliterable_characters:, + **extra + ) + track_event( + 'IdV: in person proofing characters submitted could not be transliterated', + nontransliterable_characters: nontransliterable_characters, + **extra, + ) + end + def idv_in_person_proofing_residential_address_submitted(**extra) track_event('IdV: in person proofing residential address submitted', **extra) end @@ -2830,6 +2843,13 @@ def telephony_otp_sent( ) end + # Tracks When users visit the add phone page + def add_phone_setup_visit + track_event( + 'Phone Setup Visited', + ) + end + # @param [Boolean] success # @param [Hash] errors # @param [String] sign_up_mfa_priority_bucket diff --git a/app/services/idv/agent.rb b/app/services/idv/agent.rb index 3bedd96cecf..f606ea173f0 100644 --- a/app/services/idv/agent.rb +++ b/app/services/idv/agent.rb @@ -57,26 +57,5 @@ def proof_address(document_capture_session, user_id:, issuer:, trace_id:) AddressProofingJob.perform_now(**job_arguments) end end - - def proof_document( - document_capture_session, - trace_id:, - image_metadata:, - analytics_data:, - flow_path: 'standard' - ) - encrypted_arguments = Encryption::Encryptors::BackgroundProofingArgEncryptor.new.encrypt( - @applicant.to_json, - ) - - DocumentProofingJob.perform_later( - encrypted_arguments: encrypted_arguments, - result_id: document_capture_session.result_id, - trace_id: trace_id, - image_metadata: image_metadata, - analytics_data: analytics_data, - flow_path: flow_path, - ) - end end end diff --git a/app/services/idv/cancel_verification_attempt.rb b/app/services/idv/cancel_verification_attempt.rb index 8df5c4210fe..014ccdb4467 100644 --- a/app/services/idv/cancel_verification_attempt.rb +++ b/app/services/idv/cancel_verification_attempt.rb @@ -7,11 +7,14 @@ def initialize(user:) end def call - user.profiles.gpo_verification_pending.each do |profile| - profile.update!( - active: false, - deactivation_reason: :verification_cancelled, - ) + user.profiles.each do |profile| + if profile.gpo_verification_pending? + profile.update!( + active: false, + deactivation_reason: :verification_cancelled, + gpo_verification_pending_at: nil, + ) + end end end end diff --git a/app/services/idv/gpo_mail.rb b/app/services/idv/gpo_mail.rb index b088c03f747..333d0b96a9c 100644 --- a/app/services/idv/gpo_mail.rb +++ b/app/services/idv/gpo_mail.rb @@ -12,6 +12,15 @@ def mail_spammed? max_events? && updated_within_last_month? end + def profile_too_old? + return false if !current_user.pending_profile + + min_creation_date = IdentityConfig.store. + gpo_max_profile_age_to_send_letter_in_days.days.ago + + current_user.pending_profile.created_at < min_creation_date + end + private attr_reader :current_user diff --git a/app/services/image_upload_presigned_url_generator.rb b/app/services/image_upload_presigned_url_generator.rb deleted file mode 100644 index 7e00190eff4..00000000000 --- a/app/services/image_upload_presigned_url_generator.rb +++ /dev/null @@ -1,22 +0,0 @@ -class ImageUploadPresignedUrlGenerator - include AwsS3Helper - - def presigned_image_upload_url(image_type:, transaction_id:) - keyname = "#{transaction_id}-#{image_type}" - - if !IdentityConfig.store.doc_auth_enable_presigned_s3_urls - nil - elsif !Identity::Hostdata.in_datacenter? - Rails.application.routes.url_helpers.test_fake_s3_url(key: keyname) - else - s3_presigned_url( - bucket_prefix: bucket_prefix, - keyname: keyname, - ).to_s - end - end - - def bucket_prefix - 'login-gov-idp-doc-capture'.freeze - end -end diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index d374828fd4f..3951b6beb29 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -40,6 +40,7 @@ def adjudicated_result double_address_verification: double_address_verification, stages: { resolution: resolution_result.to_h, + residential_address: residential_resolution_result.to_h, state_id: state_id_result.to_h, threatmetrix: device_profiling_result.to_h, }, @@ -56,18 +57,22 @@ def should_proof_state_id? def errors resolution_result.errors. + merge(residential_resolution_result.errors). merge(state_id_result.errors). merge(device_profiling_result.errors || {}) end def exception resolution_result.exception || + residential_resolution_result.exception || state_id_result.exception || device_profiling_result.exception end def timed_out? - resolution_result.timed_out? || state_id_result.timed_out? || + resolution_result.timed_out? || + residential_resolution_result.timed_out? || + state_id_result.timed_out? || device_profiling_result.timed_out? end diff --git a/app/services/usps_in_person_proofing/transliterable_validator.rb b/app/services/usps_in_person_proofing/transliterable_validator.rb index 7ab49ca5f00..ce966221caa 100644 --- a/app/services/usps_in_person_proofing/transliterable_validator.rb +++ b/app/services/usps_in_person_proofing/transliterable_validator.rb @@ -32,7 +32,7 @@ def initialize(options) # @param [ActiveModel::Validations] record def validate(record) return unless IdentityConfig.store.usps_ipp_transliteration_enabled - + nontransliterable_chars = Set.new @fields.each do |field| next unless record.respond_to?(field) @@ -42,12 +42,20 @@ def validate(record) invalid_chars = get_invalid_chars(value) next unless invalid_chars.present? + nontransliterable_chars += invalid_chars + record.errors.add( field, :nontransliterable_field, message: get_error_message(invalid_chars), ) end + + if nontransliterable_chars.present? + analytics.idv_in_person_proofing_nontransliterable_characters_submitted( + nontransliterable_characters: nontransliterable_chars.sort, + ) + end end def transliterator @@ -87,5 +95,9 @@ def get_invalid_chars(value) # Create sorted list of unique unsupported characters (result.unsupported_chars + additional_chars).sort.uniq end + + def analytics(user: AnonymousUser.new) + Analytics.new(user: user, request: nil, session: {}, sp: nil) + end end end diff --git a/app/views/idv/doc_auth/document_capture.html.erb b/app/views/idv/doc_auth/document_capture.html.erb index 19a7fbf9045..3fa7953f15a 100644 --- a/app/views/idv/doc_auth/document_capture.html.erb +++ b/app/views/idv/doc_auth/document_capture.html.erb @@ -4,8 +4,6 @@ flow_path: 'standard', sp_name: decorated_session.sp_name, failure_to_proof_url: idv_doc_auth_return_to_sp_url, - front_image_upload_url: front_image_upload_url, - back_image_upload_url: back_image_upload_url, acuant_sdk_upgrade_a_b_testing_enabled: acuant_sdk_upgrade_a_b_testing_enabled, use_alternate_sdk: use_alternate_sdk, acuant_version: acuant_version, diff --git a/app/views/idv/document_capture/show.html.erb b/app/views/idv/document_capture/show.html.erb index d760a1cc412..f43089f9d80 100644 --- a/app/views/idv/document_capture/show.html.erb +++ b/app/views/idv/document_capture/show.html.erb @@ -4,8 +4,6 @@ flow_path: 'standard', sp_name: decorated_session.sp_name, failure_to_proof_url: failure_to_proof_url, - front_image_upload_url: front_image_upload_url, - back_image_upload_url: back_image_upload_url, acuant_sdk_upgrade_a_b_testing_enabled: acuant_sdk_upgrade_a_b_testing_enabled, use_alternate_sdk: use_alternate_sdk, acuant_version: acuant_version, diff --git a/app/views/idv/gpo_verify/index.html.erb b/app/views/idv/gpo_verify/index.html.erb index 3db44090e8f..a0c634edf5e 100644 --- a/app/views/idv/gpo_verify/index.html.erb +++ b/app/views/idv/gpo_verify/index.html.erb @@ -41,7 +41,7 @@ <% end %> -<% if FeatureManagement.gpo_verification_enabled? && !@mail_spammed %> +<% if @user_can_request_another_gpo_code %> <%= link_to t('idv.messages.gpo.resend'), idv_gpo_path, class: 'display-block margin-bottom-2' %> <% end %> diff --git a/app/views/idv/hybrid_mobile/document_capture/show.html.erb b/app/views/idv/hybrid_mobile/document_capture/show.html.erb index 864995c91eb..13a6e24ebb4 100644 --- a/app/views/idv/hybrid_mobile/document_capture/show.html.erb +++ b/app/views/idv/hybrid_mobile/document_capture/show.html.erb @@ -4,8 +4,6 @@ flow_path: 'hybrid', sp_name: decorated_session.sp_name, failure_to_proof_url: idv_doc_auth_return_to_sp_url, - front_image_upload_url: front_image_upload_url, - back_image_upload_url: back_image_upload_url, acuant_sdk_upgrade_a_b_testing_enabled: acuant_sdk_upgrade_a_b_testing_enabled, use_alternate_sdk: use_alternate_sdk, acuant_version: acuant_version, diff --git a/app/views/idv/in_person/ready_to_verify/show.html.erb b/app/views/idv/in_person/ready_to_verify/show.html.erb index 149da2b8b17..4f315563d5f 100644 --- a/app/views/idv/in_person/ready_to_verify/show.html.erb +++ b/app/views/idv/in_person/ready_to_verify/show.html.erb @@ -40,6 +40,17 @@ <% c.item(heading: t('in_person_proofing.process.state_id.heading')) do %>
<%= t('in_person_proofing.process.state_id.info') %>
<% end %> + <% if @presenter.needs_proof_of_address? %> + <% c.item(heading: t('in_person_proofing.process.proof_of_address.heading')) do %> +<%= t('in_person_proofing.process.proof_of_address.info') %>
+<%= t('in_person_proofing.process.proof_of_address.physical_or_digital_copy') %>
+ <% end %> + <% end %> <% end %><%= t('in_person_proofing.body.barcode.questions') %> diff --git a/app/views/idv/link_sent/show.html.erb b/app/views/idv/link_sent/show.html.erb index 98555371610..b4a18d20abc 100644 --- a/app/views/idv/link_sent/show.html.erb +++ b/app/views/idv/link_sent/show.html.erb @@ -57,4 +57,4 @@ <%= javascript_packs_tag_once 'doc-capture-polling' %> <% end %> -<%= render 'idv/shared/back', action: 'cancel_link_sent', class: 'link-sent-back-link', step_url: :idv_doc_auth_url %> +<%= render 'idv/shared/back', action: 'cancel_link_sent', class: 'link-sent-back-link', step_url: :idv_doc_auth_step_url %> diff --git a/app/views/idv/session_errors/failure.html.erb b/app/views/idv/session_errors/failure.html.erb index 7070056b7b5..214f7755b7c 100644 --- a/app/views/idv/session_errors/failure.html.erb +++ b/app/views/idv/session_errors/failure.html.erb @@ -2,15 +2,8 @@ 'idv/shared/error', title: t('titles.failure.information_not_verified'), heading: t('idv.failure.sessions.heading'), + current_step: :verify_info, options: [ - decorated_session.sp_name && { - url: return_to_sp_failure_to_proof_path( - step: 'verify_info', - location: request.params[:action], - ), - text: t('idv.troubleshooting.options.get_help_at_sp', sp_name: decorated_session.sp_name), - new_tab: true, - }, { url: MarketingSite.contact_url, text: t('idv.troubleshooting.options.contact_support', app_name: APP_NAME), @@ -28,4 +21,17 @@ ), ) %>
++ + <%= link_to( + @sp_name ? + t('idv.failure.exit.with_sp', app_name: APP_NAME, sp_name: @sp_name) : + t('idv.failure.exit.without_sp'), + return_to_sp_failure_to_proof_path( + step: 'verify_id', + location: 'failure', + ), + ) %> + +
<% end %> diff --git a/app/views/idv/session_errors/warning.html.erb b/app/views/idv/session_errors/warning.html.erb index 9ff34b5fadc..efde3b5ab71 100644 --- a/app/views/idv/session_errors/warning.html.erb +++ b/app/views/idv/session_errors/warning.html.erb @@ -1,34 +1,17 @@ <% title t('titles.failure.information_not_verified') %> <%= render StatusPageComponent.new(status: :warning) do |c| %> - <% c.header { t('idv.failure.sessions.heading') } %> + <% c.header { t('idv.warning.sessions.heading') } %><%= t('idv.failure.sessions.warning') %>
-<%= t('idv.failure.attempts', count: @remaining_attempts) %>
+<%= t('idv.warning.attempts', count: @remaining_attempts) %>
<% c.action_button( action: ->(**tag_options, &block) { link_to(@try_again_path, **tag_options, &block) }, + class: 'margin-bottom-2', ) { t('idv.forgot_password.try_again') } %> - - <% c.troubleshooting_options do |tc| %> - <% tc.header { t('components.troubleshooting_options.default_heading') } %> - <% if user_session&.dig(:'idv/doc_auth', :had_barcode_read_failure) %> - <% tc.option( - url: idv_doc_auth_step_path(step: :redo_document_capture), - action: FormLinkComponent.method(:new), - method: :put, - ).with_content(t('idv.troubleshooting.options.add_new_photos')) %> - <% end %> - <% if decorated_session.sp_name %> - <% tc.option( - url: return_to_sp_failure_to_proof_path( - step: 'verify_info', - location: request.params[:action], - ), - new_tab: true, - ).with_content( - t('idv.troubleshooting.options.get_help_at_sp', sp_name: decorated_session.sp_name), - ) %> - <% end %> - <% end %> <% end %> + +<%= render PageFooterComponent.new do %> + <%= link_to t('links.cancel'), idv_cancel_path %> +<% end %> \ No newline at end of file diff --git a/app/views/idv/shared/_document_capture.html.erb b/app/views/idv/shared/_document_capture.html.erb index 17a14a21d83..a4260ea0a6f 100644 --- a/app/views/idv/shared/_document_capture.html.erb +++ b/app/views/idv/shared/_document_capture.html.erb @@ -4,11 +4,6 @@ <%= tag.meta name: 'acuant-sdk-initialization-creds', content: IdentityConfig.store.acuant_sdk_initialization_creds %> <%= stylesheet_link_tag 'document-capture' %> <% end %> -<% add_document_capture_image_urls_to_csp( - request, - [front_image_upload_url, back_image_upload_url], - ) -%> <%= tag.div id: 'document-capture-form', data: { app_name: APP_NAME, liveness_required: nil, @@ -18,9 +13,7 @@ step: :document_capture, ), document_capture_session_uuid: document_capture_session_uuid, - endpoint: FeatureManagement.document_capture_async_uploads_enabled? ? - api_verify_v2_document_capture_url : - api_verify_images_url, + endpoint: api_verify_images_url, status_endpoint: nil, glare_threshold: IdentityConfig.store.doc_auth_client_glare_threshold, sharpness_threshold: IdentityConfig.store.doc_auth_client_sharpness_threshold, @@ -35,8 +28,6 @@ flow_path: flow_path, cancel_url: idv_cancel_path, failure_to_proof_url: failure_to_proof_url, - front_image_upload_url: front_image_upload_url, - back_image_upload_url: back_image_upload_url, idv_in_person_url: Idv::InPersonConfig.enabled_for_issuer?(decorated_session.sp_issuer) ? idv_in_person_url : nil, security_and_privacy_how_it_works_url: MarketingSite.security_and_privacy_how_it_works_url, in_person_cta_variant_testing_enabled: IdentityConfig.store.in_person_cta_variant_testing_enabled, diff --git a/app/views/layouts/base.html.erb b/app/views/layouts/base.html.erb index 65bfd2a6b24..ca6388bd659 100644 --- a/app/views/layouts/base.html.erb +++ b/app/views/layouts/base.html.erb @@ -17,7 +17,7 @@ <%= content_tag( 'title', - content_for?(:title) ? raw("#{yield(:title)} - #{APP_NAME}") : APP_NAME, + content_for?(:title) ? raw("#{yield(:title)} | #{APP_NAME}") : APP_NAME, ) %> <%= javascript_tag(nonce: true) do %> @@ -74,7 +74,7 @@ local_assigns[:user_main_tag] == false ? 'div' : 'main', class: 'site-wrap bg-primary-lighter', id: local_assigns[:user_main_tag] == false ? nil : 'main-content', - ) do %> + ) do %> <% if @is_on_home_page && @sign_in_a_b_test_bucket == :banner %><%= t('in_person_proofing.process.state_id.info') %>
+ <% if @presenter.needs_proof_of_address? %> +<%= t('in_person_proofing.process.proof_of_address.info') %>
+<%= t('in_person_proofing.process.proof_of_address.physical_or_digital_copy') %>
+<%= t('in_person_proofing.body.barcode.questions') %> @@ -85,4 +100,4 @@ <% end %>
<%= t('in_person_proofing.body.expect.info') %>
\ No newline at end of file +<%= t('in_person_proofing.body.expect.info') %>
diff --git a/config/application.yml.default b/config/application.yml.default index 3092565687a..3aececcf79e 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -86,7 +86,6 @@ doc_auth_extend_timeout_by_minutes: 40 doc_capture_polling_enabled: true doc_auth_client_glare_threshold: 50 doc_auth_client_sharpness_threshold: 50 -doc_auth_enable_presigned_s3_urls: false doc_auth_s3_request_timeout: 5 doc_auth_error_dpi_threshold: 290 doc_auth_error_glare_threshold: 40 @@ -116,6 +115,7 @@ good_job_max_threads: 5 good_job_queues: 'default:5;low:1;*' good_job_queue_select_limit: 5_000 gpo_designated_receiver_pii: '{}' +gpo_max_profile_age_to_send_letter_in_days: 30 hide_phone_mfa_signup: false identity_pki_disabled: false identity_pki_local_dev: false diff --git a/config/initializers/async_exception.rb b/config/initializers/async_exception.rb deleted file mode 100644 index 87ceeb0f116..00000000000 --- a/config/initializers/async_exception.rb +++ /dev/null @@ -1,6 +0,0 @@ -# Guards against accidentally turning on the broken asynchronous document capture -# feature in production. If that feature ever gets fixed, delete this file. - -if Rails.env.production? && IdentityConfig.store.doc_auth_enable_presigned_s3_urls - raise 'Cannot initialize identity-idp project with async upload turned on' -end diff --git a/config/locales/errors/en.yml b/config/locales/errors/en.yml index 5a82331d6c2..2d993ccfdce 100644 --- a/config/locales/errors/en.yml +++ b/config/locales/errors/en.yml @@ -23,7 +23,6 @@ en: consent_form: Before you can continue, you must give us permission. Please check the box below and then click continue. document_capture_cancelled: You have cancelled uploading photos of your ID on your phone. - document_verification_timeout: The server took too long to respond. Please try again. phone_step_incomplete: You must go to your phone and upload photos of your ID before continuing. We sent you a link with instructions. send_link_throttle: You tried too many times, please try again in %{timeout}. diff --git a/config/locales/errors/es.yml b/config/locales/errors/es.yml index 6c59bbb4ad7..74c565d0bd1 100644 --- a/config/locales/errors/es.yml +++ b/config/locales/errors/es.yml @@ -23,7 +23,6 @@ es: consent_form: Antes de continuar, debe darnos permiso. Marque la casilla a continuación y luego haga clic en continuar. document_capture_cancelled: Ha cancelado la carga de fotos de su identificación en este teléfono. - document_verification_timeout: El servidor tardó demasiado en responder. Inténtalo de nuevo. phone_step_incomplete: Debe ir a su teléfono y cargar fotos de su identificación antes de continuar. Te enviamos un enlace con instrucciones. send_link_throttle: Ha intentado demasiadas veces, por favor, inténtelo de nuevo diff --git a/config/locales/errors/fr.yml b/config/locales/errors/fr.yml index c83d9af23f8..5b5ab3a727b 100644 --- a/config/locales/errors/fr.yml +++ b/config/locales/errors/fr.yml @@ -26,7 +26,6 @@ fr: Veuillez cocher la case ci-dessous puis cliquez sur continuer. document_capture_cancelled: Vous avez annulé le téléchargement de vos photos d’identité sur votre téléphone. - document_verification_timeout: Le serveur a mis trop de temps à répondre. Veuillez réessayer. phone_step_incomplete: Vous devez aller sur votre téléphone et télécharger des photos de votre identifiant avant de continuer. Nous vous avons envoyé un lien avec des instructions. diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml index 33f0b17279b..be1c4207575 100644 --- a/config/locales/idv/en.yml +++ b/config/locales/idv/en.yml @@ -109,10 +109,10 @@ en: you_entered: 'You entered:' sessions: exception: There was an internal error processing your request. - fail_html: 'Please try again in %{timeout}. For your security, - we limit the number of times you can attempt to verify personal - information online.' - heading: We could not find records matching your personal information. + fail_html: 'For your security, we limit the number of times you can attempt to + verify personal information online. Try again in + %{timeout}.' + heading: We couldn’t find records matching your personal information warning: Please check the information you entered and try again. Common mistakes are an incorrect Social Security number or ZIP Code. setup: @@ -229,7 +229,6 @@ en: need_assistance: 'Need immediate assistance? Here’s how to get help:' still_having_trouble: Still having trouble? options: - add_new_photos: Add new photos of your state‑issued ID contact_support: Contact %{app_name} Support doc_capture_tips: More tips for adding photos of your ID get_help_at_sp: Get help at %{sp_name} @@ -251,6 +250,12 @@ en: status_page_link: 'Get updates on our status page' technical_difficulties: Unfortunately, we are having technical difficulties and cannot verify your identity at this time. + warning: + attempts: + one: For security reasons, you have one attempt remaining. + other: For security reasons, you have %{count} attempts remaining. + sessions: + heading: We couldn’t find records matching your personal information welcome: no_js_header: You must enable JavaScript to verify your identity. no_js_intro: '%{sp_name} needs you to verify your identity. You need to enable diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml index dd0208c78b1..2b6744f0fbf 100644 --- a/config/locales/idv/es.yml +++ b/config/locales/idv/es.yml @@ -117,11 +117,10 @@ es: you_entered: 'Ud. entregó:' sessions: exception: Hubo un error interno al procesar su solicitud. - fail_html: 'Por favor, inténtelo de nuevo en %{timeout}. Por su - seguridad, limitamos el número de veces que puede intentar verificar - la información personal en línea.' - heading: No hemos podido encontrar registros que coincidan con su información - personal. + fail_html: 'Por su seguridad, limitamos el número de veces que puede intentar + verificar la información personal en línea. Inténtelo de nuevo + en %{timeout}.' + heading: No encontramos registros que coincidan con sus datos personales warning: Por favor, verifique la información que ingresó y vuelva a intentarlo. Los errores más comunes suelen producirse al ingresar un Número de Seguridad Social o un Código Postal incorrecto. @@ -242,7 +241,6 @@ es: need_assistance: '¿Necesita ayuda inmediata? Así es como puede obtener ayuda:' still_having_trouble: '¿Sigue teniendo dificultades?' options: - add_new_photos: Añada nuevas fotos de su ID emitido por el estado contact_support: Póngase en contacto con el servicio de asistencia de %{app_name} doc_capture_tips: Más consejos para agregar fotos de su identificación get_help_at_sp: Obtenga ayuda en %{sp_name} @@ -267,6 +265,12 @@ es: status_page_link: 'Consulte las actualizaciones en nuestra página de estado' technical_difficulties: Lamentablemente, debido a problemas técnicos por nuestra parte, tal vez no podamos verificar su identidad en estos momentos. + warning: + attempts: + one: Por motivos de seguridad, le quedan un intento. + other: Por motivos de seguridad, le quedan %{count} intentos. + sessions: + heading: No encontramos registros que coincidan con sus datos personales welcome: no_js_header: Debe habilitar JavaScript para verificar su identidad. no_js_intro: '%{sp_name} requiere que usted verifique su identidad. Debe diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml index e9c61ca0fcc..8a950061d4e 100644 --- a/config/locales/idv/fr.yml +++ b/config/locales/idv/fr.yml @@ -122,11 +122,11 @@ fr: you_entered: 'Tu as soumis:' sessions: exception: Une erreur interne s’est produite lors du traitement de votre demande. - fail_html: 'Veuillez réessayer dans %{timeout}. Pour votre - sécurité, nous limitons le nombre de fois où vous pouvez tenter de - vérifier des informations personnelles en ligne.' - heading: Nous ne trouvons pas de données qui correspondent à vos informations - téléphoniques. + fail_html: 'Pour votre sécurité, nous limitons le nombre de fois où vous pouvez + tenter de vérifier des informations personnelles en ligne. + Réessayer dans %{timeout}.' + heading: Nous n’avons pas trouvé de dossiers correspondant à vos informations + personnelles téléphoniques. warning: Veuillez vérifier les informations que vous avez saisies et réessayer. Les erreurs les plus courantes sont un numéro de sécurité sociale ou un code postal incorrect. @@ -256,8 +256,6 @@ fr: obtenir de l’aide:' still_having_trouble: Vous rencontrez toujours des difficultés? options: - add_new_photos: Ajoutez de nouvelles photos de votre carte d’identité délivrée - par l’État. contact_support: Contacter le service d’assistance de %{app_name} doc_capture_tips: Plus de conseils pour ajouter des photos de votre carte d’identité get_help_at_sp: Demandez de l’aide à %{sp_name} @@ -281,6 +279,13 @@ fr: status_page_link: 'Obtenez des mises à jour sur notre page de statut' technical_difficulties: Malheureusement, nous rencontrons des difficultés techniques et ne pouvons pas vérifier votre identité pour le moment. + warning: + attempts: + one: Pour des raisons de sécurité, il vous reste une tentative. + other: Pour des raisons de sécurité, il vous reste %{count} tentatives. + sessions: + heading: Nous n’avons pas trouvé de dossiers correspondant à vos informations + personnelles welcome: no_js_header: Vous devez activer JavaScript pour vérifier votre identité. no_js_intro: '%{sp_name} a besoin de vous pour vérifier votre identité. Vous diff --git a/config/locales/in_person_proofing/en.yml b/config/locales/in_person_proofing/en.yml index 73413070b14..736af7b8a69 100644 --- a/config/locales/in_person_proofing/en.yml +++ b/config/locales/in_person_proofing/en.yml @@ -152,6 +152,18 @@ en: heading: Show your %{app_name} barcode info: The retail associate needs to scan your barcode at the top of this page. You can print this page or show it on your mobile device. + proof_of_address: + acceptable_proof: + - Lease, Mortgage, or Deed of Trust + - Voter Registration + - Vehicle Registration Card + - Home or Vehicle Insurance Policy + heading: Proof of your current address + info: 'You need a proof of address if your current address is different than the + address on your ID. Acceptable forms of proof of address are:' + physical_or_digital_copy: You can bring a physical copy or show a digital copy + of the document. You cannot show a photo or screenshot of the + document. state_id: heading: Show your State Driver’s License or State Non-Driver’s Identification Card diff --git a/config/locales/in_person_proofing/es.yml b/config/locales/in_person_proofing/es.yml index 38dcc007b86..06b1e33e2e9 100644 --- a/config/locales/in_person_proofing/es.yml +++ b/config/locales/in_person_proofing/es.yml @@ -167,6 +167,19 @@ es: info: El empleado deberá escanear el código de barras que aparece en la parte superior de esta página. Puede imprimir esta página o mostrarla en su dispositivo móvil. + proof_of_address: + acceptable_proof: + - Arrendamiento, hipoteca o escritura de fideicomiso + - Registro de votantes + - Tarjeta de registro del vehículo + - Póliza de seguro del hogar o del vehículo + heading: Prueba de su dirección actual + info: 'Necesita un justificante de domicilio si su dirección actual es diferente + a la que figura en su cédula de identidad. Los comprobantes de + domicilio aceptables son:' + physical_or_digital_copy: Puede traer una copia física o mostrar una copia + digital del documento. No puede mostrar una foto o una captura de + pantalla del documento. state_id: heading: Muestre su licencia de conducir estatal o su tarjeta de identificación estatal de no conductor diff --git a/config/locales/in_person_proofing/fr.yml b/config/locales/in_person_proofing/fr.yml index 14a386f35a0..3761caa760d 100644 --- a/config/locales/in_person_proofing/fr.yml +++ b/config/locales/in_person_proofing/fr.yml @@ -169,6 +169,19 @@ fr: heading: Montrez votre code-barres %{app_name} info: Le vendeur doit scanner votre code-barres en haut de cette page. Vous pouvez imprimer cette page ou la montrer sur votre appareil mobile. + proof_of_address: + acceptable_proof: + - Bail, hypothèque ou acte de fiducie + - Inscription sur les listes électorales + - Carte d’immatriculation du véhicule + - Police d’assurance habitation ou véhicule + heading: Preuve de votre adresse actuelle + info: 'Vous avez besoin d’un justificatif de domicile si votre adresse actuelle + est différente de l’adresse figurant sur votre document d’identité. + Les justificatifs d’adresse acceptés sont les suivants:' + physical_or_digital_copy: Vous pouvez apporter une copie physique ou montrer une + copie numérique du document. Vous ne pouvez pas montrer une photo ou + une capture d’écran du document. state_id: heading: Présentez votre permis de conduire de l’État ou votre carte d’identité de non-conducteur de l’État diff --git a/config/routes.rb b/config/routes.rb index 972a55e8161..686d061f278 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -390,11 +390,6 @@ post '/confirmations' => 'personal_key#update' end - namespace :api do - post '/verify/v2/document_capture' => 'verify/document_capture#create' - delete '/verify/v2/document_capture_errors' => 'verify/document_capture_errors#delete' - end - get '/account/verify' => 'idv/gpo_verify#index', as: :idv_gpo_verify post '/account/verify' => 'idv/gpo_verify#create' if FeatureManagement.gpo_verification_enabled? diff --git a/lib/feature_management.rb b/lib/feature_management.rb index ca69bdf75e7..961a1ad2dc5 100644 --- a/lib/feature_management.rb +++ b/lib/feature_management.rb @@ -105,10 +105,6 @@ def self.doc_capture_polling_enabled? IdentityConfig.store.doc_capture_polling_enabled end - def self.document_capture_async_uploads_enabled? - IdentityConfig.store.doc_auth_enable_presigned_s3_urls - end - def self.otp_expired_redirect_enabled? IdentityConfig.store.allow_otp_countdown_expired_redirect end diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 14188c0b757..a879db66881 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -156,7 +156,6 @@ def self.build_store(config_map) config.add(:doc_auth_attempt_window_in_minutes, type: :integer) config.add(:doc_auth_client_glare_threshold, type: :integer) config.add(:doc_auth_client_sharpness_threshold, type: :integer) - config.add(:doc_auth_enable_presigned_s3_urls, type: :boolean) config.add(:doc_auth_error_dpi_threshold, type: :integer) config.add(:doc_auth_error_glare_threshold, type: :integer) config.add(:doc_auth_error_sharpness_threshold, type: :integer) @@ -193,6 +192,7 @@ def self.build_store(config_map) config.add(:good_job_queues, type: :string) config.add(:good_job_queue_select_limit, type: :integer) config.add(:gpo_designated_receiver_pii, type: :json, options: { symbolize_names: true }) + config.add(:gpo_max_profile_age_to_send_letter_in_days, type: :integer) config.add(:hide_phone_mfa_signup, type: :boolean) config.add(:hmac_fingerprinter_key, type: :string) config.add(:hmac_fingerprinter_key_queue, type: :json) diff --git a/spec/components/javascript_required_component_spec.rb b/spec/components/javascript_required_component_spec.rb index 3048cf38c89..5e03ff6c2cc 100644 --- a/spec/components/javascript_required_component_spec.rb +++ b/spec/components/javascript_required_component_spec.rb @@ -25,9 +25,7 @@ end it 'loads css resource for setting session key in JavaScript-disabled environments' do - expect(rendered).to have_css('noscript link') do |node| - node[:href] == no_js_detect_css_path - end + expect(rendered).to have_css("noscript link[href='#{no_js_detect_css_path}']") end context 'with intro' do diff --git a/spec/config/initializers/async_exception_spec.rb b/spec/config/initializers/async_exception_spec.rb deleted file mode 100644 index 9159d83b333..00000000000 --- a/spec/config/initializers/async_exception_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'rails_helper' - -RSpec.describe 'async_error' do - let(:enabled) { false } - subject do - load Rails.root.join('config', 'initializers', 'async_exception.rb').to_s - end - - before do - allow(Rails.env).to receive(:production?).and_return(true) - allow(IdentityConfig.store).to receive(:doc_auth_enable_presigned_s3_urls).and_return(enabled) - end - - context 'async uploads are not enabled' do - it 'does not raise an error' do - expect { subject }.not_to raise_error - end - end - - context 'async uploads are enabled' do - let(:enabled) { true } - - it 'does raise an error' do - expect { subject }.to raise_error - end - end -end diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb index a0b32f57083..365ec98eb10 100644 --- a/spec/controllers/accounts_controller_spec.rb +++ b/spec/controllers/accounts_controller_spec.rb @@ -63,7 +63,7 @@ user = create( :user, :fully_registered, - profiles: [build(:profile, deactivation_reason: :gpo_verification_pending)], + profiles: [build(:profile, gpo_verification_pending_at: 1.day.ago)], ) sign_in user diff --git a/spec/controllers/api/verify/base_controller_spec.rb b/spec/controllers/api/verify/base_controller_spec.rb deleted file mode 100644 index af4e76511ac..00000000000 --- a/spec/controllers/api/verify/base_controller_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'rails_helper' - -describe Api::Verify::BaseController do - describe '#create' do - subject(:response) { post :create } - - controller Api::Verify::BaseController do - def create - render json: {} - end - end - - before { routes.draw { get '/' => 'api/verify/base#create' } } - - it 'renders as unauthorized (401)' do - expect(response.status).to eq(401) - end - - context 'with authenticated user' do - before { stub_sign_in } - - it 'renders as ok (200)' do - expect(response.status).to eq(200) - end - - context 'with request forgery protection enabled' do - around do |ex| - ActionController::Base.allow_forgery_protection = true - ex.run - ActionController::Base.allow_forgery_protection = false - end - - it 'renders as ok (200)' do - expect(response.status).to eq(200) - end - end - end - end -end diff --git a/spec/controllers/api/verify/document_capture_controller_spec.rb b/spec/controllers/api/verify/document_capture_controller_spec.rb deleted file mode 100644 index fd398e70603..00000000000 --- a/spec/controllers/api/verify/document_capture_controller_spec.rb +++ /dev/null @@ -1,137 +0,0 @@ -require 'rails_helper' - -describe Api::Verify::DocumentCaptureController do - include PersonalKeyValidator - include SamlAuthHelper - - let(:encryption_key) { 'encryption-key' } - let(:front_image_url) { 'http://example.com/front' } - let(:front_image_iv) { 'front-iv' } - let(:back_image_url) { 'http://example.com/back' } - let(:back_image_iv) { 'back-iv' } - let(:front_image_metadata) do - { width: 40, height: 40, mimeType: 'image/png', source: 'upload' } - end - let(:back_image_metadata) do - { width: 20, height: 20, mimeType: 'image/png', source: 'upload' } - end - let(:image_metadata) { { front: front_image_metadata, back: back_image_metadata } } - let!(:document_capture_session) { DocumentCaptureSession.create!(user: create(:user)) } - let(:document_capture_session_uuid) { document_capture_session.uuid } - let(:password) { 'iambatman' } - let(:user) { create(:user, :fully_registered) } - let(:flow_path) { 'standard' } - let(:analytics_data) do - { browser_attributes: - { browser_bot: false, - browser_device_name: 'Unknown', - browser_mobile: false, - browser_name: 'Unknown Browser', - browser_platform_name: 'Unknown', - browser_platform_version: '0', - browser_version: '0.0', - user_agent: 'Rails Testing' } } - end - - before do - stub_sign_in(user) if user - end - - it 'extends behavior of base api class' do - expect(subject).to be_kind_of Api::Verify::BaseController - end - - describe '#create' do - it 'renders as bad request (400)' do - post :create - - expect(response.status).to eq(400) - end - - context 'signed out' do - let(:user) { nil } - - it 'renders as unauthorized (401)' do - post :create - - expect(response.status).to eq(401) - end - - context 'with hybrid effective user' do - before { session[:doc_capture_user_id] = create(:user).id } - - it 'renders as bad request (400)' do - post :create - - expect(response.status).to eq(400) - end - end - end - - context 'When user document is submitted to be verified' do - it 'returns inprogress status when create is called' do - agent = instance_double(Idv::Agent) - allow(Idv::Agent).to receive(:new).with( - { - user_uuid: user.uuid, - uuid_prefix: nil, - document_arguments: { - 'encryption_key' => encryption_key, - 'front_image_iv' => front_image_iv, - 'back_image_iv' => back_image_iv, - 'front_image_url' => front_image_url, - 'back_image_url' => back_image_url, - }, - }, - ).and_return(agent) - - expect(agent).to receive(:proof_document).with( - document_capture_session, - trace_id: nil, - image_metadata: image_metadata, - analytics_data: analytics_data, - flow_path: flow_path, - ) - - post :create, params: { - encryption_key: encryption_key, - front_image_iv: front_image_iv, - back_image_iv: back_image_iv, - front_image_url: front_image_url, - back_image_url: back_image_url, - front_image_metadata: front_image_metadata.to_json, - back_image_metadata: back_image_metadata.to_json, - document_capture_session_uuid: document_capture_session_uuid, - flow_path: flow_path, - } - expect(JSON.parse(response.body, symbolize_names: true)).to eq( - success: true, - status: 'in_progress', - ) - expect(response.status).to eq 202 - end - - context 'When the request does not have all the parameters' do - it 'returns 400 and gives error message' do - agent = instance_double(Idv::Agent) - allow(Idv::Agent).to receive(:new).and_return(agent) - expect(agent).to_not receive(:proof_document) - - post :create, params: { - encryption_key: encryption_key, - front_image_iv: nil, - back_image_iv: back_image_iv, - front_image_url: front_image_url, - back_image_url: back_image_url, - document_capture_session_uuid: document_capture_session_uuid, - } - - expect(JSON.parse(response.body)['errors'].keys.first).to eq('front_image_iv') - expect(JSON.parse(response.body)['errors']['front_image_iv'][0]). - to eq('Please fill in this field.') - expect(response.status).to eq 400 - end - end - end - end -end diff --git a/spec/controllers/api/verify/document_capture_errors_controller_spec.rb b/spec/controllers/api/verify/document_capture_errors_controller_spec.rb deleted file mode 100644 index af2c39ee19f..00000000000 --- a/spec/controllers/api/verify/document_capture_errors_controller_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -require 'rails_helper' - -describe Api::Verify::DocumentCaptureErrorsController do - let(:user) { create(:user) } - - it 'extends behavior of base api class' do - expect(subject).to be_kind_of Api::Verify::BaseController - end - - describe '#delete' do - it 'renders as unauthorized (401)' do - delete :delete - - expect(response.status).to eq(401) - end - - shared_examples 'deleting document capture errors' do - let(:params) { nil } - - subject(:response) { delete :delete, params: params } - let(:parsed_body) { JSON.parse(response.body, symbolize_names: true) } - - it 'renders errors for missing fields' do - expect(response.status).to eq 400 - expect(parsed_body).to eq( - { errors: { document_capture_session_uuid: [t('errors.messages.blank')] } }, - ) - end - - context 'with invalid document capture session' do - let(:params) { { document_capture_session_uuid: 'invalid' } } - - it 'renders errors for invalid document capture session' do - expect(response.status).to eq 400 - expect(parsed_body).to eq( - { errors: { document_capture_session_uuid: ['Invalid document capture session'] } }, - ) - end - end - - context 'with valid document capture session' do - let(:document_capture_session) do - DocumentCaptureSession.create(user: user, ocr_confirmation_pending: true) - end - let(:params) { { document_capture_session_uuid: document_capture_session.uuid } } - - it 'deletes errors and renders successful response' do - expect { response }. - to change { document_capture_session.reload.ocr_confirmation_pending }. - from(true). - to(false) - - expect(response.status).to eq 200 - expect(parsed_body).to eq({}) - end - end - end - - context 'with signed in user' do - before { stub_sign_in(user) } - - it_behaves_like 'deleting document capture errors' - end - - context 'with hybrid effective user' do - before { session[:doc_capture_user_id] = user.id } - - it_behaves_like 'deleting document capture errors' - end - end -end diff --git a/spec/controllers/concerns/idv/step_indicator_concern_spec.rb b/spec/controllers/concerns/idv/step_indicator_concern_spec.rb index 622bd9c8d24..f26d95d1907 100644 --- a/spec/controllers/concerns/idv/step_indicator_concern_spec.rb +++ b/spec/controllers/concerns/idv/step_indicator_concern_spec.rb @@ -49,7 +49,7 @@ def force_gpo end context 'with a pending profile' do - let(:profile) { create(:profile, deactivation_reason: :gpo_verification_pending) } + let(:profile) { create(:profile, gpo_verification_pending_at: 1.day.ago) } it 'returns doc auth gpo steps' do expect(steps).to eq doc_auth_step_indicator_steps_gpo @@ -90,7 +90,7 @@ def force_gpo let(:profile) do create( :profile, - deactivation_reason: :gpo_verification_pending, + gpo_verification_pending_at: 1.day.ago, proofing_components: { 'document_check' => Idp::Constants::Vendors::USPS }, ) end diff --git a/spec/controllers/concerns/idv_step_concern_spec.rb b/spec/controllers/concerns/idv_step_concern_spec.rb index 7036e2be004..956725bf887 100644 --- a/spec/controllers/concerns/idv_step_concern_spec.rb +++ b/spec/controllers/concerns/idv_step_concern_spec.rb @@ -163,4 +163,80 @@ def show end end end + + describe '#confirm_no_pending_in_person_enrollment' do + controller Idv::StepController do + before_action :confirm_no_pending_in_person_enrollment + end + + before(:each) do + sign_in(user) + allow(subject).to receive(:current_user).and_return(user) + routes.draw do + get 'show' => 'idv/step#show' + end + end + + context 'without pending in person enrollment' do + it 'does not redirect' do + get :show + + expect(response.body).to eq 'Hello' + expect(response).to_not redirect_to idv_in_person_ready_to_verify_url + expect(response.status).to eq 200 + end + end + + context 'with pending in person enrollment' do + let(:user) { create(:user, :with_pending_in_person_enrollment, :fully_registered) } + + before do + allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true) + end + + it 'redirects to in person ready to verify page' do + get :show + + expect(response).to redirect_to idv_in_person_ready_to_verify_url + end + end + end + + describe '#confirm_no_pending_gpo_profile' do + controller Idv::StepController do + before_action :confirm_no_pending_gpo_profile + end + + before(:each) do + sign_in(user) + allow(subject).to receive(:current_user).and_return(user) + routes.draw do + get 'show' => 'idv/step#show' + end + end + + context 'without pending gpo profile' do + it 'does not redirect' do + get :show + + expect(response.body).to eq 'Hello' + expect(response).to_not redirect_to idv_in_person_ready_to_verify_url + expect(response.status).to eq 200 + end + end + + context 'with pending gpo profile' do + let(:user) { create(:user, :with_pending_gpo_profile, :fully_registered) } + + before do + allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true) + end + + it 'redirects to enter your code page' do + get :show + + expect(response).to redirect_to idv_gpo_verify_url + end + end + end end diff --git a/spec/controllers/idv/doc_auth_controller_spec.rb b/spec/controllers/idv/doc_auth_controller_spec.rb index 85a90a18336..50a1b1e45d4 100644 --- a/spec/controllers/idv/doc_auth_controller_spec.rb +++ b/spec/controllers/idv/doc_auth_controller_spec.rb @@ -27,8 +27,6 @@ stub_sign_in(user) if user stub_analytics allow(@analytics).to receive(:track_event) - allow(Identity::Hostdata::EC2).to receive(:load). - and_return(OpenStruct.new(region: 'us-west-2', domain: 'example.com')) end describe 'unauthenticated' do diff --git a/spec/controllers/idv/document_capture_controller_spec.rb b/spec/controllers/idv/document_capture_controller_spec.rb index 0a78914e78a..f9cdbfca38e 100644 --- a/spec/controllers/idv/document_capture_controller_spec.rb +++ b/spec/controllers/idv/document_capture_controller_spec.rb @@ -117,8 +117,6 @@ end it 'does not raise an exception when stored_result is nil' do - allow(FeatureManagement).to receive(:document_capture_async_uploads_enabled?). - and_return(false) allow(subject).to receive(:stored_result).and_return(nil) put :update end diff --git a/spec/controllers/idv/gpo_controller_spec.rb b/spec/controllers/idv/gpo_controller_spec.rb index 2cef9dd43ef..4129c41991a 100644 --- a/spec/controllers/idv/gpo_controller_spec.rb +++ b/spec/controllers/idv/gpo_controller_spec.rb @@ -15,6 +15,7 @@ :confirm_two_factor_authenticated, :confirm_idv_needed, :confirm_mail_not_spammed, + :confirm_profile_not_too_old, ) end @@ -95,6 +96,34 @@ expect(assigns(:step_indicator_current_step)).to eq(:get_a_letter) end end + + context 'user has a pending profile' do + let(:profile_created_at) { Time.zone.now } + let(:pending_profile) do + create( + :profile, + :with_pii, + user: user, + created_at: profile_created_at, + ) + end + before do + allow(user).to receive(:pending_profile).and_return(pending_profile) + end + + it 'renders ok' do + get :index + expect(response).to be_ok + end + + context 'but pending profile is too old to send another letter' do + let(:profile_created_at) { Time.zone.now - 31.days } + it 'redirects back to /verify' do + get :index + expect(response).to redirect_to(idv_path) + end + end + end end describe '#create' do diff --git a/spec/controllers/idv/gpo_verify_controller_spec.rb b/spec/controllers/idv/gpo_verify_controller_spec.rb index 11a3253dc97..09c2464ff52 100644 --- a/spec/controllers/idv/gpo_verify_controller_spec.rb +++ b/spec/controllers/idv/gpo_verify_controller_spec.rb @@ -6,16 +6,19 @@ let(:otp) { 'ABC123' } let(:submitted_otp) { otp } let(:user) { create(:user) } + let(:profile_created_at) { Time.zone.now } let(:pending_profile) do create( :profile, :with_pii, user: user, proofing_components: proofing_components, + created_at: profile_created_at, ) end let(:proofing_components) { nil } let(:threatmetrix_enabled) { false } + let(:gpo_enabled) { true } before do stub_analytics @@ -32,6 +35,7 @@ allow(IdentityConfig.store).to receive(:proofing_device_profiling). and_return(threatmetrix_enabled ? :enabled : :disabled) + allow(IdentityConfig.store).to receive(:enable_usps_verification).and_return(gpo_enabled) end describe '#index' do @@ -48,6 +52,11 @@ expect(response).to render_template('idv/gpo_verify/index') end + it 'sets @user_can_request_another_gpo_code to true' do + action + expect(assigns(:user_can_request_another_gpo_code)).to eql(true) + end + it 'shows throttled page is user is throttled' do Throttle.new(throttle_type: :verify_gpo_key, user: user).increment_to_throttled! @@ -55,6 +64,14 @@ expect(response).to render_template(:throttled) end + + context 'but that profile is > 30 days old' do + let(:profile_created_at) { 31.days.ago } + it 'sets @user_can_request_another_gpo_code to false' do + action + expect(assigns(:user_can_request_another_gpo_code)).to eql(false) + end + end end context 'user does not have pending profile' do diff --git a/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb b/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb index 1db190c44f2..bebc780ddf1 100644 --- a/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb +++ b/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb @@ -146,9 +146,6 @@ end it 'does not raise an exception when stored_result is nil' do - allow(FeatureManagement).to receive(:document_capture_async_uploads_enabled?). - and_return(false) - allow(subject).to receive(:stored_result).and_return(nil) put :update diff --git a/spec/controllers/idv/link_sent_controller_spec.rb b/spec/controllers/idv/link_sent_controller_spec.rb index 2ddfa540ef1..5e085dd1190 100644 --- a/spec/controllers/idv/link_sent_controller_spec.rb +++ b/spec/controllers/idv/link_sent_controller_spec.rb @@ -98,12 +98,4 @@ end end end - - context 'feature flag disabled' do - let(:feature_flag_enabled) { false } - it 'returns a 404' do - get :show - expect(response.status).to eql(404) - end - end end diff --git a/spec/controllers/idv/session_errors_controller_spec.rb b/spec/controllers/idv/session_errors_controller_spec.rb index 5c546ed6780..34b9340c21e 100644 --- a/spec/controllers/idv/session_errors_controller_spec.rb +++ b/spec/controllers/idv/session_errors_controller_spec.rb @@ -213,6 +213,14 @@ expect(assigns(:expires_at)).to be_kind_of(Time) end + it 'assigns sp_name' do + decorated_session = double + allow(decorated_session).to receive(:sp_name).and_return('Example SP') + allow(controller).to receive(:decorated_session).and_return(decorated_session) + get action + expect(assigns(:sp_name)).to eql('Example SP') + end + it 'logs an event with attempts remaining' do expect(@analytics).to receive(:track_event).with( 'IdV: session error visited', diff --git a/spec/controllers/idv/sessions_controller_spec.rb b/spec/controllers/idv/sessions_controller_spec.rb index 4b9bf2396e1..44b418bbd95 100644 --- a/spec/controllers/idv/sessions_controller_spec.rb +++ b/spec/controllers/idv/sessions_controller_spec.rb @@ -75,7 +75,7 @@ let(:user) do create( :user, - profiles: [create(:profile, deactivation_reason: :gpo_verification_pending)], + profiles: [create(:profile, gpo_verification_pending_at: 1.day.ago)], ) end diff --git a/spec/controllers/users/phones_controller_spec.rb b/spec/controllers/users/phones_controller_spec.rb index 3f8a1f18122..f9d7678c67e 100644 --- a/spec/controllers/users/phones_controller_spec.rb +++ b/spec/controllers/users/phones_controller_spec.rb @@ -16,6 +16,12 @@ expect(response).to render_template(:add) expect(response.request.flash[:alert]).to be_nil end + + it 'tracks analytics' do + expect(@analytics).to receive(:track_event). + with('Phone Setup Visited') + get :add + end end context 'user exceeds phone number limit' do @@ -104,5 +110,132 @@ expect(response).to render_template('users/phone_setup/spam_protection') end end + + context 'invalid number' do + it 'tracks an event when the number is invalid' do + sign_in(user) + + stub_analytics + result = { + success: false, + errors: { + phone: [ + t('errors.messages.improbable_phone'), + t( + 'two_factor_authentication.otp_delivery_preference.voice_unsupported', + location: '', + ), + ], + }, + error_details: { + phone: [ + :improbable_phone, + t( + 'two_factor_authentication.otp_delivery_preference.voice_unsupported', + location: '', + ), + ], + }, + otp_delivery_preference: 'sms', + area_code: nil, + carrier: 'Test Mobile Carrier', + country_code: nil, + phone_type: :mobile, + types: [], + pii_like_keypaths: [[:errors, :phone], [:error_details, :phone]], + } + + expect(@analytics).to receive(:track_event). + with('Multi-Factor Authentication: phone setup', result) + + post :create, params: { + new_phone_form: { + phone: '703-555-010', + international_code: 'US', + }, + } + + expect(response).to render_template(:add) + end + end + + context 'with SMS' do + it 'prompts to confirm the number' do + sign_in(user) + + stub_analytics + + result = { + success: true, + errors: {}, + otp_delivery_preference: 'sms', + area_code: '703', + carrier: 'Test Mobile Carrier', + country_code: 'US', + phone_type: :mobile, + types: [:fixed_or_mobile], + pii_like_keypaths: [[:errors, :phone], [:error_details, :phone]], + } + + expect(@analytics).to receive(:track_event). + with('Multi-Factor Authentication: phone setup', result) + + post( + :create, + params: { + new_phone_form: { phone: '703-555-0100', + international_code: 'US' }, + }, + ) + + expect(response).to redirect_to( + otp_send_path( + otp_delivery_selection_form: { otp_delivery_preference: 'sms', + otp_make_default_number: false }, + ), + ) + + expect(subject.user_session[:context]).to eq 'confirmation' + end + end + + context 'without selection' do + it 'prompts to confirm via SMS by default' do + sign_in(user) + + stub_analytics + result = { + success: true, + errors: {}, + otp_delivery_preference: 'sms', + area_code: '703', + carrier: 'Test Mobile Carrier', + country_code: 'US', + phone_type: :mobile, + types: [:fixed_or_mobile], + pii_like_keypaths: [[:errors, :phone], [:error_details, :phone]], + } + + expect(@analytics).to receive(:track_event). + with('Multi-Factor Authentication: phone setup', result) + + patch( + :create, + params: { + new_phone_form: { phone: '703-555-0100', + international_code: 'US' }, + }, + ) + + expect(response).to redirect_to( + otp_send_path( + otp_delivery_selection_form: { otp_delivery_preference: 'sms', + otp_make_default_number: false }, + ), + ) + + expect(subject.user_session[:context]).to eq 'confirmation' + end + end end end diff --git a/spec/controllers/users/sessions_controller_spec.rb b/spec/controllers/users/sessions_controller_spec.rb index 3bdc6b8135c..f62cc526563 100644 --- a/spec/controllers/users/sessions_controller_spec.rb +++ b/spec/controllers/users/sessions_controller_spec.rb @@ -351,7 +351,7 @@ user = create(:user, :fully_registered) create( :profile, - deactivation_reason: :gpo_verification_pending, + gpo_verification_pending_at: 1.day.ago, user: user, pii: { ssn: '1234' } ) @@ -623,7 +623,7 @@ it 'redirects to the verify profile page' do profile = create( :profile, - deactivation_reason: :gpo_verification_pending, + gpo_verification_pending_at: 1.day.ago, pii: { ssn: '6666', dob: '1920-01-01' }, ) user = profile.user diff --git a/spec/factories/users.rb b/spec/factories/users.rb index f53b4e19b7d..df00aac3a8d 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -203,6 +203,19 @@ end end + trait :with_pending_gpo_profile do + after :build do |user| + profile = create(:profile, :with_pii, user: user) + profile.gpo_verification_pending_at = 1.day.ago + # This sets the deactivation_reason to enum value :gpo_verification_pending + profile.gpo_verification_pending! + gpo_code = create(:gpo_confirmation_code) + profile.gpo_confirmation_codes << gpo_code + device = create(:device, user: user) + create(:event, user: user, device: device, event_type: :gpo_mail_sent) + end + end + trait :proofed_with_gpo do proofed after :build do |user| diff --git a/spec/features/idv/actions/cancel_link_sent_action_spec.rb b/spec/features/idv/actions/cancel_link_sent_action_spec.rb index 2cf54a9f177..e90d5e499e2 100644 --- a/spec/features/idv/actions/cancel_link_sent_action_spec.rb +++ b/spec/features/idv/actions/cancel_link_sent_action_spec.rb @@ -4,7 +4,11 @@ include IdvStepHelper include DocAuthHelper + let(:new_controller_enabled) { false } + before do + allow(IdentityConfig.store).to receive(:doc_auth_link_sent_controller_enabled). + and_return(new_controller_enabled) sign_in_and_2fa_user complete_doc_auth_steps_before_link_sent_step end @@ -14,4 +18,15 @@ expect(page).to have_current_path(idv_doc_auth_upload_step) end + + context 'new SendLink controller is enabled' do + let(:new_controller_enabled) { true } + + it 'returns to link sent step', :js do + expect(page).to have_current_path(idv_link_sent_path) + click_doc_auth_back_link + + expect(page).to have_current_path(idv_doc_auth_upload_step) + end + end end diff --git a/spec/features/idv/actions/redo_document_capture_action_spec.rb b/spec/features/idv/actions/redo_document_capture_action_spec.rb index 40e7ff685ae..bc45ad8e775 100644 --- a/spec/features/idv/actions/redo_document_capture_action_spec.rb +++ b/spec/features/idv/actions/redo_document_capture_action_spec.rb @@ -41,17 +41,17 @@ expect(page).not_to have_css('[role="status"]') end - it 'shows a troubleshooting option to allow the user to return to upload new images' do + it 'shows a troubleshooting option to allow the user to cancel and return to SP' do click_idv_continue expect(page).to have_link( - t('idv.troubleshooting.options.add_new_photos'), - href: idv_doc_auth_step_path(step: :redo_document_capture), + t('links.cancel'), + href: idv_cancel_path, ) - click_link t('idv.troubleshooting.options.add_new_photos') + click_link t('links.cancel') - expect(current_path).to eq(idv_doc_auth_upload_step) + expect(current_path).to eq(idv_cancel_path) end context 'on mobile', driver: :headless_chrome_mobile do diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb index d9c69b9f4c1..210f76ec1ea 100644 --- a/spec/features/idv/analytics_spec.rb +++ b/spec/features/idv/analytics_spec.rb @@ -28,7 +28,7 @@ 'IdV: doc auth ssn submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify visited' => { flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify submitted' => { flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', double_address_verification: false, resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: { client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', response_body: { error: 'TMx response body was empty' } } } } } }, + 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', double_address_verification: false, resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, residential_address: { errors: {}, exception: nil, reference: '', success: true, timed_out: false, transaction_id: '', vendor_name: 'ResidentialAddressNotRequired' }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: { client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', response_body: { error: 'TMx response body was empty' } } } } } }, 'IdV: phone of record visited' => { proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' } }, 'IdV: phone confirmation form' => { success: true, errors: {}, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' }, otp_delivery_preference: 'sms' }, 'IdV: phone confirmation vendor' => { success: true, errors: {}, vendor: { exception: nil, vendor_name: 'AddressMock', transaction_id: 'address-mock-transaction-id-123', timed_out: false, reference: '' }, new_phone_added: false, proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' }, area_code: '202', country_code: 'US', phone_fingerprint: anything }, @@ -63,7 +63,7 @@ 'IdV: doc auth ssn submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify visited' => { flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify submitted' => { flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', double_address_verification: false, should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: { client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', response_body: { error: 'TMx response body was empty' } } } } } }, + 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', double_address_verification: false, should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, residential_address: { errors: {}, exception: nil, reference: '', success: true, timed_out: false, transaction_id: '', vendor_name: 'ResidentialAddressNotRequired' }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: { client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', response_body: { error: 'TMx response body was empty' } } } } } }, 'IdV: phone of record visited' => { proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' } }, 'IdV: USPS address letter requested' => { resend: false, proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' } }, 'IdV: review info visited' => { address_verification_method: 'gpo', proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass', address_check: 'gpo_letter' } }, @@ -129,8 +129,6 @@ fake_analytics.user = controller.analytics_user fake_analytics end - allow_any_instance_of(DocumentProofingJob).to receive(:build_analytics). - and_return(fake_analytics) allow(IdentityConfig.store).to receive(:idv_acuant_sdk_upgrade_a_b_testing_enabled). and_return(false) end diff --git a/spec/features/idv/doc_auth/document_capture_spec.rb b/spec/features/idv/doc_auth/document_capture_spec.rb index 7a1fb912452..bfc16edd4fb 100644 --- a/spec/features/idv/doc_auth/document_capture_spec.rb +++ b/spec/features/idv/doc_auth/document_capture_spec.rb @@ -7,14 +7,9 @@ let(:max_attempts) { IdentityConfig.store.doc_auth_max_attempts } let(:user) { user_with_2fa } - let(:doc_auth_enable_presigned_s3_urls) { false } let(:fake_analytics) { FakeAnalytics.new } let(:sp_name) { 'Test SP' } before do - allow(IdentityConfig.store).to receive(:doc_auth_enable_presigned_s3_urls). - and_return(doc_auth_enable_presigned_s3_urls) - allow(Identity::Hostdata::EC2).to receive(:load). - and_return(OpenStruct.new(region: 'us-west-2', account_id: '123456789')) allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics) allow_any_instance_of(ServiceProviderSessionDecorator).to receive(:sp_name).and_return(sp_name) 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 5332ca59844..f601cd5f19d 100644 --- a/spec/features/idv/doc_auth/verify_info_step_spec.rb +++ b/spec/features/idv/doc_auth/verify_info_step_spec.rb @@ -166,18 +166,21 @@ context 'resolution throttling' do let(:max_resolution_attempts) { 3 } - # proof_ssn_max_attempts is 10, vs 5 for resolution, so it doesn't get triggered - it 'throttles resolution and continues when it expires' do + before(:each) do allow(IdentityConfig.store).to receive(:idv_max_attempts). and_return(max_resolution_attempts) - expect(fake_attempts_tracker).to receive(:idv_verification_rate_limited).at_least(1).times. - with({ throttle_context: 'single-session' }) - sign_in_and_2fa_user complete_doc_auth_steps_before_ssn_step fill_out_ssn_form_with_ssn_that_fails_resolution click_idv_continue + end + + # proof_ssn_max_attempts is 10, vs 5 for resolution, so it doesn't get triggered + it 'throttles resolution and continues when it expires' do + expect(fake_attempts_tracker).to receive(:idv_verification_rate_limited).at_least(1).times. + with({ throttle_context: 'single-session' }) + (max_resolution_attempts - 2).times do click_idv_continue expect(page).to have_current_path(idv_session_errors_warning_path) @@ -187,7 +190,7 @@ # Check that last attempt shows correct warning text click_idv_continue expect(page).to have_current_path(idv_session_errors_warning_path) - expect(page).to have_content(t('idv.failure.attempts.one')) + expect(page).to have_content(t('idv.warning.attempts.one')) click_try_again click_idv_continue @@ -209,6 +212,11 @@ expect(page).to have_current_path(idv_phone_path) end end + + it 'allows user to cancel identify verification' do + click_link t('links.cancel') + expect(page).to have_current_path(idv_cancel_path(step: 'verify')) + end end context 'ssn throttling' do diff --git a/spec/features/idv/hybrid_mobile/hybrid_mobile_spec.rb b/spec/features/idv/hybrid_mobile/hybrid_mobile_spec.rb index 24b5557ad68..e444817f816 100644 --- a/spec/features/idv/hybrid_mobile/hybrid_mobile_spec.rb +++ b/spec/features/idv/hybrid_mobile/hybrid_mobile_spec.rb @@ -9,8 +9,6 @@ before do allow(FeatureManagement).to receive(:doc_capture_polling_enabled?).and_return(true) - allow(Identity::Hostdata::EC2).to receive(:load). - and_return(OpenStruct.new(region: 'us-west-2', account_id: '123456789')) end before do diff --git a/spec/features/idv/in_person_spec.rb b/spec/features/idv/in_person_spec.rb index c4512542201..337443d62a9 100644 --- a/spec/features/idv/in_person_spec.rb +++ b/spec/features/idv/in_person_spec.rb @@ -243,6 +243,14 @@ 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) + + # confirm that user cannot visit other IdV pages before completing in-person proofing + visit idv_doc_auth_agreement_step + expect(page).to have_current_path(idv_in_person_ready_to_verify_path) + visit idv_ssn_url + expect(page).to have_current_path(idv_in_person_ready_to_verify_path) + visit idv_verify_info_url + expect(page).to have_current_path(idv_in_person_ready_to_verify_path) end it 'allows the user to cancel and start over from the beginning', allow_browser_log: true do diff --git a/spec/features/idv/proofing_components_spec.rb b/spec/features/idv/proofing_components_spec.rb index 6cab5c19fd9..3bd39f0e92d 100644 --- a/spec/features/idv/proofing_components_spec.rb +++ b/spec/features/idv/proofing_components_spec.rb @@ -12,8 +12,6 @@ before do allow(IdentityConfig.store).to receive(:ruby_workers_idv_enabled). and_return(ruby_workers_idv_enabled) - allow(IdentityConfig.store).to receive(:doc_auth_enable_presigned_s3_urls). - and_return(doc_auth_enable_presigned_s3_urls) visit_idp_from_sp_with_ial2(:oidc) register_user(email) @@ -28,7 +26,6 @@ context 'sync proofing', js: true do let(:ruby_workers_idv_enabled) { false } - let(:doc_auth_enable_presigned_s3_urls) { false } it 'records proofing components' do proofing_components = user.active_profile.proofing_components diff --git a/spec/features/idv/steps/gpo_otp_verification_step_spec.rb b/spec/features/idv/steps/gpo_otp_verification_step_spec.rb index 675615f3884..158d4cf31f8 100644 --- a/spec/features/idv/steps/gpo_otp_verification_step_spec.rb +++ b/spec/features/idv/steps/gpo_otp_verification_step_spec.rb @@ -7,7 +7,7 @@ let(:profile) do create( :profile, - deactivation_reason: :gpo_verification_pending, + gpo_verification_pending_at: 2.days.ago, pii: { ssn: '123-45-6789', dob: '1970-01-01' }, fraud_review_pending_at: fraud_review_pending_timestamp, fraud_rejection_at: fraud_rejection_timestamp, diff --git a/spec/features/idv/steps/gpo_step_spec.rb b/spec/features/idv/steps/gpo_step_spec.rb index 10b6ef2ea59..b9167fbab7f 100644 --- a/spec/features/idv/steps/gpo_step_spec.rb +++ b/spec/features/idv/steps/gpo_step_spec.rb @@ -33,6 +33,43 @@ expect_user_to_be_unverified(user) expect(page).to have_content(t('idv.titles.come_back_later')) expect(page).to have_current_path(idv_come_back_later_path) + + # Confirm that user cannot visit other IdV pages while unverified + visit idv_doc_auth_agreement_step + expect(page).to have_current_path(idv_gpo_verify_path) + visit idv_ssn_url + expect(page).to have_current_path(idv_gpo_verify_path) + visit idv_verify_info_url + expect(page).to have_current_path(idv_gpo_verify_path) + end + + context 'too much time has passed' do + let(:days_passed) { 31 } + let(:max_days_before_resend_disabled) { 30 } + + before do + allow(IdentityConfig.store).to receive(:gpo_max_profile_age_to_send_letter_in_days). + and_return(max_days_before_resend_disabled) + end + + it 'does not present the user the option to to resend' do + complete_idv_and_sign_out + travel_to(days_passed.days.from_now) do + sign_in_live_with_2fa(user) + expect(page).to have_current_path(idv_gpo_verify_path) + expect(page).not_to have_css('.usa-button', text: t('idv.buttons.mail.resend')) + end + end + + it 'does not allow the user to go to the resend page manually' do + complete_idv_and_sign_out + travel_to(days_passed.days.from_now) do + sign_in_live_with_2fa(user) + visit idv_gpo_path + expect(page).to have_current_path(idv_gpo_verify_path) + expect(page).not_to have_css('.usa-button', text: t('idv.buttons.mail.resend')) + end + end end it 'allows the user to return to gpo otp confirmation' do @@ -44,7 +81,7 @@ expect_user_to_be_unverified(user) end - def complete_idv_and_return_to_gpo_step + def complete_idv_and_sign_out start_idv_from_sp complete_idv_steps_before_gpo_step(user) click_on t('idv.buttons.mail.send') @@ -53,6 +90,10 @@ def complete_idv_and_return_to_gpo_step visit root_path click_on t('forms.verify_profile.return_to_profile') first(:link, t('links.sign_out')).click + end + + def complete_idv_and_return_to_gpo_step + complete_idv_and_sign_out sign_in_live_with_2fa(user) click_on t('idv.messages.gpo.resend') end diff --git a/spec/features/saml/ial2_sso_spec.rb b/spec/features/saml/ial2_sso_spec.rb index 9dcd2d9861c..682293d25ad 100644 --- a/spec/features/saml/ial2_sso_spec.rb +++ b/spec/features/saml/ial2_sso_spec.rb @@ -79,7 +79,7 @@ def sign_out_user let(:profile) do create( :profile, - deactivation_reason: :gpo_verification_pending, + gpo_verification_pending_at: 1.day.ago, pii: { ssn: '6666', dob: '1920-01-01' }, ) end diff --git a/spec/features/users/password_reset_with_pending_profile_spec.rb b/spec/features/users/password_reset_with_pending_profile_spec.rb index a0413977831..5b6b60154b6 100644 --- a/spec/features/users/password_reset_with_pending_profile_spec.rb +++ b/spec/features/users/password_reset_with_pending_profile_spec.rb @@ -8,7 +8,7 @@ scenario 'password reset email includes warning for pending profile' do profile = create( :profile, - deactivation_reason: :gpo_verification_pending, + gpo_verification_pending_at: 1.day.ago, pii: { ssn: '666-66-1234', dob: '1920-01-01', phone: '+1 703-555-9999' }, user: user, ) diff --git a/spec/features/users/verify_profile_spec.rb b/spec/features/users/verify_profile_spec.rb index ec527ab97e2..a9e96e1ab0f 100644 --- a/spec/features/users/verify_profile_spec.rb +++ b/spec/features/users/verify_profile_spec.rb @@ -9,7 +9,7 @@ before do profile = create( :profile, - deactivation_reason: :gpo_verification_pending, + gpo_verification_pending_at: 1.day.ago, pii: { ssn: '666-66-1234', dob: '1920-01-01', phone: '+1 703-555-9999' }, user: user, ) diff --git a/spec/forms/api/verify/document_capture_errors_delete_form_spec.rb b/spec/forms/api/verify/document_capture_errors_delete_form_spec.rb deleted file mode 100644 index 799e333f295..00000000000 --- a/spec/forms/api/verify/document_capture_errors_delete_form_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'rails_helper' - -describe Api::Verify::DocumentCaptureErrorsDeleteForm do - let(:document_capture_session_uuid) { nil } - subject(:form) do - described_class.new(document_capture_session_uuid: document_capture_session_uuid) - end - - describe '#submit' do - context 'without a document capture session uuid' do - it 'is returns an unsuccessful form response' do - result, document_capture_session = form.submit - - expect(result.success?).to eq(false) - expect(result.errors).to eq( - { document_capture_session_uuid: ['Please fill in this field.'] }, - ) - expect(document_capture_session).to be_nil - end - end - - context 'with an invalid document capture session uuid' do - let(:document_capture_session_uuid) { 'wrong' } - - it 'is returns an unsuccessful form response' do - result, document_capture_session = form.submit - - expect(result.success?).to eq(false) - expect(result.errors).to eq( - { document_capture_session_uuid: ['Invalid document capture session'] }, - ) - expect(document_capture_session).to be_nil - end - end - - context 'with a valid document capture session uuid' do - let(:document_capture_session) { DocumentCaptureSession.create } - let(:document_capture_session_uuid) { document_capture_session.uuid } - - it 'is returns an successful form response' do - result, resolved_document_capture_session = form.submit - - expect(result.success?).to eq(true) - expect(result.errors).to eq({}) - expect(resolved_document_capture_session).to eq(document_capture_session) - end - end - end -end diff --git a/spec/forms/gpo_verify_form_spec.rb b/spec/forms/gpo_verify_form_spec.rb index 7e02b63a53c..f04e5f49cd2 100644 --- a/spec/forms/gpo_verify_form_spec.rb +++ b/spec/forms/gpo_verify_form_spec.rb @@ -14,7 +14,7 @@ create( :profile, user: user, - deactivation_reason: :gpo_verification_pending, + gpo_verification_pending_at: 1.day.ago, proofing_components: proofing_components, ) end @@ -153,7 +153,7 @@ create( :profile, user: user, - deactivation_reason: :gpo_verification_pending, + gpo_verification_pending_at: 1.day.ago, fraud_review_pending_at: 1.day.ago, ) end diff --git a/spec/forms/idv/api_document_verification_form_spec.rb b/spec/forms/idv/api_document_verification_form_spec.rb deleted file mode 100644 index dd5a30c203f..00000000000 --- a/spec/forms/idv/api_document_verification_form_spec.rb +++ /dev/null @@ -1,115 +0,0 @@ -require 'rails_helper' - -RSpec.describe Idv::ApiDocumentVerificationForm do - subject(:form) do - Idv::ApiDocumentVerificationForm.new( - { - encryption_key: encryption_key, - front_image_url: front_image_url, - front_image_iv: front_image_iv, - back_image_url: back_image_url, - back_image_iv: back_image_iv, - document_capture_session_uuid: document_capture_session_uuid, - }, - analytics: analytics, - irs_attempts_api_tracker: irs_attempts_api_tracker, - ) - end - - let(:encryption_key) { 'encryption-key' } - let(:front_image_url) { 'http://example.com/front' } - let(:front_image_iv) { 'front-iv' } - let(:back_image_url) { 'http://example.com/back' } - let(:back_image_iv) { 'back-iv' } - let!(:document_capture_session) { DocumentCaptureSession.create!(user: create(:user)) } - let(:document_capture_session_uuid) { document_capture_session.uuid } - let(:analytics) { FakeAnalytics.new } - let(:irs_attempts_api_tracker) { IrsAttemptsApiTrackingHelper::FakeAttemptsTracker.new } - - describe '#valid?' do - context 'with all valid images' do - it 'is valid' do - expect(form.valid?).to eq(true) - expect(form.errors).to be_blank - end - end - - context 'when iv is missing' do - let(:front_image_iv) { nil } - - it 'is not valid' do - expect(form.valid?).to eq(false) - expect(form.errors.attribute_names).to eq([:front_image_iv]) - expect(form.errors[:front_image_iv]).to eq(['Please fill in this field.']) - end - end - - context 'when encryption key is missing' do - let(:encryption_key) { nil } - - it 'is not valid' do - expect(form.valid?).to eq(false) - expect(form.errors.attribute_names).to eq([:encryption_key]) - expect(form.errors[:encryption_key]).to eq(['Please fill in this field.']) - end - end - - context 'when url is invalid' do - let(:front_image_url) { 'nonsense' } - - it 'is not valid' do - expect(form.valid?).to eq(false) - expect(form.errors.attribute_names).to eq([:front_image_url]) - expect(form.errors[:front_image_url]).to eq([t('doc_auth.errors.not_a_file')]) - end - end - - context 'when document_capture_session_uuid param is missing' do - let(:document_capture_session_uuid) { nil } - - it 'is not valid' do - expect(form.valid?).to eq(false) - expect(form.errors.attribute_names).to eq([:document_capture_session]) - expect(form.errors[:document_capture_session]).to eq(['Please fill in this field.']) - end - end - - context 'when document_capture_session_uuid does not correspond to a record' do - let(:document_capture_session_uuid) { 'unassociated-test-uuid' } - - it 'is not valid' do - expect(form.valid?).to eq(false) - expect(form.errors.attribute_names).to eq([:document_capture_session]) - expect(form.errors[:document_capture_session]).to eq(['Please fill in this field.']) - end - end - - context 'when throttled from submission' do - before do - Throttle.new( - throttle_type: :idv_doc_auth, - user: document_capture_session.user, - ).increment_to_throttled! - form.submit - end - - it 'is not valid' do - expect(irs_attempts_api_tracker).to receive(:idv_document_upload_rate_limited) - expect(form.valid?).to eq(false) - expect(form.errors.attribute_names).to eq([:limit]) - expect(form.errors[:limit]).to eq([I18n.t('errors.doc_auth.throttled_heading')]) - expect(analytics).to have_logged_event( - 'Throttler Rate Limit Triggered', - throttle_type: :idv_doc_auth, - ) - end - end - end - - describe '#submit' do - it 'includes remaining_attempts' do - response = form.submit - expect(response.extra[:remaining_attempts]).to be_a_kind_of(Numeric) - end - end -end diff --git a/spec/helpers/aws_s3_helper_spec.rb b/spec/helpers/aws_s3_helper_spec.rb deleted file mode 100644 index 6cc61c46d10..00000000000 --- a/spec/helpers/aws_s3_helper_spec.rb +++ /dev/null @@ -1,99 +0,0 @@ -require 'rails_helper' -describe 'AwsS3Helper' do - let(:session_uuid) { SecureRandom.uuid } - let(:env) { 'dev' } - let(:account_id) { '123456789' } - let(:region) { 'us-west-2' } - let(:prefix) { 'login-gov-idp-doc-capture' } - let(:image_type) { 'front' } - let(:bucket) { "#{prefix}-#{env}.#{account_id}-#{region}" } - let(:query_keys) do - %w[ - X-Amz-Algorithm - X-Amz-Credential - X-Amz-Date - X-Amz-Expires - X-Amz-SignedHeaders - X-Amz-Signature - ] - end - - before do - allow(Identity::Hostdata::EC2).to receive(:load). - and_raise(Net::OpenTimeout) - end - - describe '#s3_presigned_url' do - let(:client_stub) { Aws::S3::Client.new(region: region, stub_responses: true) } - - before do - client_stub.stub_responses(:list_buckets, { buckets: [{ name: bucket }] }) - resource_stub = Aws::S3::Resource.new(client: client_stub) - - allow(Identity::Hostdata).to receive(:env).and_return(env) - allow(Identity::Hostdata).to receive(:aws_region).and_return(region) - allow(Identity::Hostdata).to receive(:aws_account_id).and_return(account_id) - allow(helper).to receive(:s3_resource).and_return(resource_stub) - end - - it 'returns a URL' do - url = URI( - helper.s3_presigned_url( - bucket_prefix: prefix, - keyname: "#{session_uuid}-#{image_type}", - ), - ) - query = Hash[*url.query.split(/[&=]/)] - - expect(url.host).to eq("s3.#{region}.amazonaws.com") - expect(url.path).to eq("/#{bucket}/#{session_uuid}-#{image_type}") - expect(query['X-Amz-Algorithm']).to eq('AWS4-HMAC-SHA256') - expect(query.keys).to match_array(query_keys) - end - - it 'requires a keyname' do - expect { helper.s3_presigned_url(bucket_prefix: prefix, keyname: '') }. - to raise_error(ArgumentError, 'keyname is required') - end - - it 'requires a bucket_prefix' do - expect { helper.s3_presigned_url(bucket_prefix: '', keyname: 'image_type') }. - to raise_error(ArgumentError, 'bucket_prefix is required') - end - - it 'is created with an expiration' do - key = "#{session_uuid}-#{image_type}" - object = Aws::S3::Object.new(bucket_name: bucket, key: key, client: client_stub) - allow(helper).to receive(:s3_object).and_return(object) - expect(object).to receive(:presigned_url).with( - kind_of(Symbol), - hash_including(expires_in: helper.presigned_url_expiration_in_seconds), - ).and_call_original - - helper.s3_presigned_url( - bucket_prefix: prefix, - keyname: key, - ) - end - end - - describe '#s3_resource' do - context 'AWS credentials are not set' do - before do - allow(Identity::Hostdata).to receive(:aws_region).and_return(region) - allow(Aws::S3::Resource).to receive(:new). - and_raise(Aws::Sigv4::Errors::MissingCredentialsError, 'Credentials not set') - end - - it 'returns nil' do - expect(helper.s3_resource).to be_nil - end - end - end - - describe '#presigned_url_expiration_in_seconds' do - it 'returns a number' do - expect(helper.presigned_url_expiration_in_seconds).to be_a_kind_of(Numeric) - end - end -end diff --git a/spec/jobs/document_proofing_job_spec.rb b/spec/jobs/document_proofing_job_spec.rb deleted file mode 100644 index e458eb28876..00000000000 --- a/spec/jobs/document_proofing_job_spec.rb +++ /dev/null @@ -1,305 +0,0 @@ -require 'rails_helper' - -RSpec.describe DocumentProofingJob, type: :job do - let(:front_image_url) { 'http://bucket.s3.amazonaws.com/bar1' } - let(:back_image_url) { 'http://bucket.s3.amazonaws.com/bar2' } - let(:encryption_key) { SecureRandom.random_bytes(32) } - let(:front_image_iv) { SecureRandom.random_bytes(12) } - let(:back_image_iv) { SecureRandom.random_bytes(12) } - let(:trace_id) { SecureRandom.uuid } - let(:source) { nil } - let(:front_image_metadata) { { mimeType: 'image/png', source: source } } - let(:back_image_metadata) { { mimeType: 'image/png', source: source } } - let(:image_metadata) { { front: front_image_metadata, back: back_image_metadata } } - - let(:applicant_pii) do - { - first_name: 'Johnny', - last_name: 'Appleseed', - uuid: SecureRandom.hex, - dob: '01/01/1970', - ssn: '123456789', - phone: '18888675309', - state: 'MT', - state_id_type: 'drivers_license', - } - end - - let(:body) { { document: applicant_pii }.to_json } - - before do - encrypt_and_stub_s3(body: body, url: front_image_url, iv: front_image_iv, key: encryption_key) - encrypt_and_stub_s3(body: body, url: back_image_url, iv: back_image_iv, key: encryption_key) - end - - let(:encrypted_arguments) do - Encryption::Encryptors::BackgroundProofingArgEncryptor.new.encrypt( - { - document_arguments: { - encryption_key: Base64.encode64(encryption_key), - front_image_iv: Base64.encode64(front_image_iv), - back_image_iv: Base64.encode64(back_image_iv), - front_image_url: front_image_url, - back_image_url: back_image_url, - }, - }.to_json, - ) - end - - let(:user) { create(:user) } - let(:analytics) { FakeAnalytics.new } - let(:document_capture_session) do - DocumentCaptureSession.create(user_id: user.id, result_id: SecureRandom.hex) - end - - describe '.perform_later' do - it 'stores results' do - DocumentProofingJob.perform_later( - result_id: document_capture_session.result_id, - encrypted_arguments: encrypted_arguments, - trace_id: trace_id, - image_metadata: image_metadata, - analytics_data: {}, - flow_path: 'standard', - ) - - result = document_capture_session.load_doc_auth_async_result - expect(result).to be_present - end - end - - describe '#perform' do - let(:job_analytics) { FakeAnalytics.new } - let(:instance) { DocumentProofingJob.new } - subject(:perform) do - instance.perform( - result_id: document_capture_session.result_id, - encrypted_arguments: encrypted_arguments, - trace_id: trace_id, - image_metadata: image_metadata, - analytics_data: {}, - flow_path: 'standard', - ) - end - - before do - allow(instance).to receive(:build_analytics). - with(document_capture_session).and_return(job_analytics) - end - - context 'with a successful response from the proofer' do - before do - expect(DocAuthRouter).to receive(:doc_auth_vendor).and_return('acuant') - - url = URI.join('https://example.com', '/AssureIDService/Document/Instance') - stub_request(:post, url).to_return(body: '"this-is-a-test-instance-id"') - doc_url = 'https://example.com/AssureIDService/Document/this-is-a-test-instance-id' - stub_request(:post, "#{doc_url}/Image?light=0&side=0").to_return(body: '') - stub_request(:post, "#{doc_url}/Image?light=0&side=1").to_return(body: '') - stub_request(:get, doc_url).to_return(body: '{"Result":1}') - stub_request(:get, "#{doc_url}/Field/Image?key=Photo").to_return(body: '') - stub_request(:post, 'https://example.login.gov/api/callbacks/proof-document/:token'). - to_return(body: '') - - allow_any_instance_of(DocAuth::Acuant::Responses::GetResultsResponse). - to receive(:pii_from_doc).and_return(applicant_pii) - end - - it 'returns a successful response' do - perform - - result = document_capture_session.load_doc_auth_async_result - - expect(result.result).to eq( - alert_failure_count: 0, - vendor: 'Acuant', - doc_auth_result: 'Passed', - billed: true, - errors: {}, - log_alert_results: {}, - attention_with_barcode: false, - image_metrics: {}, - processed_alerts: { failed: [], passed: [] }, - success: true, - exception: nil, - tamper_result: nil, - classification_info: nil, - address_line2_present: false, - ) - - expect(job_analytics).to have_logged_event( - 'IdV: doc auth image upload vendor submitted', - success: true, - errors: {}, - attention_with_barcode: false, - exception: nil, - vendor: 'Acuant', - billed: true, - doc_auth_result: 'Passed', - processed_alerts: { failed: [], passed: [] }, - alert_failure_count: 0, - image_metrics: {}, - state: 'MT', - state_id_type: 'drivers_license', - async: true, - attempts: 0, - remaining_attempts: IdentityConfig.store.doc_auth_max_attempts, - client_image_metrics: { - front: front_image_metadata, - back: back_image_metadata, - }, - tamper_result: nil, - flow_path: 'standard', - log_alert_results: {}, - classification_info: nil, - address_line2_present: false, - ) - - expect(result.pii_from_doc).to eq(applicant_pii) - end - - it 'logs the trace_id and timing info' do - expect(instance.logger).to receive(:info) do |message| - expect(JSON.parse(message, symbolize_names: true)).to include( - trace_id: trace_id, - timing: hash_including( - 'decrypt.back': kind_of(Float), - 'decrypt.front': kind_of(Float), - 'download.back': kind_of(Float), - 'download.front': kind_of(Float), - ), - ) - end - - perform - end - end - - context 'with local image URLs instead of S3 URLs' do - let(:front_image_url) { 'http://example.com/bar1' } - let(:back_image_url) { 'http://example.com/bar2' } - - before do - data = { document: applicant_pii }.to_json - encryption_helper = JobHelpers::EncryptionHelper.new - - stub_request(:get, front_image_url).to_return( - body: encryption_helper.encrypt(data: data, key: encryption_key, iv: front_image_iv), - ) - stub_request(:get, back_image_url).to_return( - body: encryption_helper.encrypt(data: data, key: encryption_key, iv: back_image_iv), - ) - end - - it 'still downloads and decrypts the content' do - perform - - expect(a_request(:get, front_image_url)).to have_been_made - expect(a_request(:get, back_image_url)).to have_been_made - end - end - - describe 'image source' do - let(:source) { nil } - let(:front_image_metadata) { { mimeType: 'image/png', source: source } } - let(:back_image_metadata) { { mimeType: 'image/png', source: source } } - let(:image_source) { nil } - - before do - expect_any_instance_of(DocAuth::Mock::DocAuthMockClient). - to receive(:post_images). - with(hash_including(image_source: image_source)). - and_call_original - end - - context 'manual uploads' do - let(:source) { 'upload' } - let(:image_source) { DocAuth::ImageSources::UNKNOWN } - - it 'sets image source to unknown' do - perform - end - end - - context 'mixed sources' do - let(:source) { 'upload' } - let(:back_image_metadata) do - { width: 20, height: 20, mimeType: 'image/png', source: 'acuant' }.to_json - end - let(:image_source) { DocAuth::ImageSources::UNKNOWN } - - it 'sets image source to unknown' do - perform - end - end - - context 'acuant images' do - let(:source) { 'acuant' } - let(:image_source) { DocAuth::ImageSources::ACUANT_SDK } - - it 'sets image source to acuant sdk' do - perform - end - end - - context 'malformed image metadata' do - let(:source) { 'upload' } - let(:front_image_metadata) { nil } - let(:image_source) { DocAuth::ImageSources::UNKNOWN } - - it 'sets image source to unknown' do - perform - end - end - end - - context 'a stale job' do - before { instance.enqueued_at = 10.minutes.ago } - - it 'bails and does not do any proofing' do - expect(DocAuthRouter).to_not receive(:doc_auth_vendor) - - expect { perform }.to raise_error(JobHelpers::StaleJobHelper::StaleJobError) - end - end - - context 'with data url body' do - let(:body) { DocAuthImageFixtures.document_front_image_data_uri } - - it 'decrypts the image correctly' do - expect_any_instance_of(DocAuth::Mock::DocAuthMockClient). - to receive(:post_images). - with(hash_including(front_image: DocAuthImageFixtures.document_front_image.b)). - and_call_original - - perform - end - end - - context 'with jpg file body' do - let(:body) { DocAuthImageFixtures.document_front_image } - - it 'decrypts the image correctly' do - expect_any_instance_of(DocAuth::Mock::DocAuthMockClient). - to receive(:post_images). - with(hash_including(front_image: DocAuthImageFixtures.document_front_image.b)). - and_call_original - - perform - end - end - - context 'with invalid data url body' do - let(:body) { 'data:"' } - - it 'gracefully degrades' do - expect_any_instance_of(DocAuth::Mock::DocAuthMockClient). - to receive(:post_images). - with(hash_including(front_image: nil)). - and_call_original - - perform - end - end - end -end diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb index 061dc1a4fca..84fc0c97232 100644 --- a/spec/jobs/resolution_proofing_job_spec.rb +++ b/spec/jobs/resolution_proofing_job_spec.rb @@ -360,6 +360,7 @@ result_context = result[:context] result_context_stages = result_context[:stages] result_context_stages_resolution = result_context_stages[:resolution] + result_context_residential_address = result_context_stages[:residential_address] result_context_stages_state_id = result_context_stages[:state_id] result_context_stages_threatmetrix = result_context_stages[:threatmetrix] @@ -385,6 +386,19 @@ expect(result_context_stages_resolution[:attributes_requiring_additional_verification]). to eq([]) + # result[:context][:stages][:residential_address] + expect(result_context_residential_address[:vendor_name]).to eq('lexisnexis:instant_verify') + expect(result_context_residential_address[:errors]).to include(:"Execute Instant Verify") + expect(result_context_residential_address[:exception]).to eq(nil) + expect(result_context_residential_address[:success]).to eq(true) + expect(result_context_residential_address[:timed_out]).to eq(false) + expect(result_context_residential_address[:transaction_id]).to eq('123456') + expect(result_context_residential_address[:reference]).to eq('Reference1') + expect(result_context_residential_address[:can_pass_with_additional_verification]). + to eq(false) + expect(result_context_residential_address[:attributes_requiring_additional_verification]). + to eq([]) + # result[:context][:stages][:state_id] expect(result_context_stages_state_id[:vendor_name]).to eq('aamva:state_id') expect(result_context_stages_state_id[:errors]).to eq({}) diff --git a/spec/lib/feature_management_spec.rb b/spec/lib/feature_management_spec.rb index 3a35ef7e26f..f01287d1dd0 100644 --- a/spec/lib/feature_management_spec.rb +++ b/spec/lib/feature_management_spec.rb @@ -311,20 +311,6 @@ end end - describe '#document_capture_async_uploads_enabled?' do - it 'returns true when IdentityConfig presigned S3 URL setting is true' do - allow(IdentityConfig.store).to receive(:doc_auth_enable_presigned_s3_urls) { true } - - expect(FeatureManagement.document_capture_async_uploads_enabled?).to eq(true) - end - - it 'returns false when IdentityConfig presigned S3 URL setting is false' do - allow(IdentityConfig.store).to receive(:doc_auth_enable_presigned_s3_urls) { false } - - expect(FeatureManagement.document_capture_async_uploads_enabled?).to eq(false) - end - end - describe 'log_to_stdout?' do context 'outside the test environment' do before { allow(Rails.env).to receive(:test?).and_return(false) } diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 64b4f534b02..ac353761efe 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -546,6 +546,7 @@ def expect_email_body_to_have_help_and_contact_links current_address_matches_id: current_address_matches_id, ) end + describe '#in_person_ready_to_verify' do let(:mail) do UserMailer.with(user: user, email_address: email_address).in_person_ready_to_verify( @@ -556,6 +557,25 @@ def expect_email_body_to_have_help_and_contact_links it_behaves_like 'a system email' it_behaves_like 'an email that respects user email locale preference' + context 'double address verification is not enabled' do + it 'renders the body' do + expect(mail.html_part.body). + to have_content( + t('in_person_proofing.process.proof_of_address.heading'), + ) + end + end + + context 'double address verification is enabled' do + let(:capture_secondary_id_enabled) { true } + it 'renders the body' do + expect(mail.html_part.body). + to_not have_content( + t('in_person_proofing.process.proof_of_address.heading'), + ) + end + end + it 'renders the body' do expect(mail.html_part.body). to have_content( @@ -577,6 +597,25 @@ def expect_email_body_to_have_help_and_contact_links it_behaves_like 'a system email' it_behaves_like 'an email that respects user email locale preference' + context 'double address verification is not enabled' do + it 'renders the body' do + expect(mail.html_part.body). + to have_content( + t('in_person_proofing.process.proof_of_address.heading'), + ) + end + end + + context 'double address verification is enabled' do + let(:capture_secondary_id_enabled) { true } + it 'renders the body' do + expect(mail.html_part.body). + to_not have_content( + t('in_person_proofing.process.proof_of_address.heading'), + ) + end + end + it 'renders the body' do expect(mail.html_part.body). to have_content( diff --git a/spec/models/profile_spec.rb b/spec/models/profile_spec.rb index 25bdade93a2..e0310da4315 100644 --- a/spec/models/profile_spec.rb +++ b/spec/models/profile_spec.rb @@ -307,15 +307,15 @@ end end - describe '#activate_after_gpo_verification' do - it 'activates a profile after gpo verification' do + describe '#remove_gpo_deactivation_reason' do + it 'removes the gpo_verification_pending_at deactivation reason' do profile = create( :profile, user: user, active: false, gpo_verification_pending_at: 1.day.ago ) - profile.activate_after_gpo_verification + profile.remove_gpo_deactivation_reason - expect(profile).to be_active + expect(profile.gpo_verification_pending?).to be(false) end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e9c53315166..e74fe2bca5c 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -462,18 +462,18 @@ end describe '#pending_profile' do - context 'when a profile with a gpo_verification_pending deactivation_reason exists' do - it 'returns the most recent profile' do + context 'when a pending profile exists' do + it 'returns with the most recent profile' do user = User.new _old_profile = create( :profile, - :gpo_verification_pending, + gpo_verification_pending_at: 1.day.ago, created_at: 1.day.ago, user: user, ) new_profile = create( :profile, - :gpo_verification_pending, + gpo_verification_pending_at: Time.zone.now, user: user, ) @@ -481,6 +481,34 @@ end end + context 'when pending profile does not exist' do + it 'returns nil' do + user = User.new + create( + :profile, + deactivation_reason: :encryption_error, + user: user, + ) + + expect(user.pending_profile).to be_nil + end + end + end + + describe '#gpo_verification_pending_profile' do + context 'when a profile with a gpo_verification_pending_at timestamp exists' do + it 'returns the profile' do + user = User.new + profile = create( + :profile, + gpo_verification_pending_at: Time.zone.now, + user: user, + ) + + expect(user.gpo_verification_pending_profile).to eq profile + end + end + context 'when a gpo_verification_pending profile does not exist' do it 'returns nil' do user = User.new @@ -496,7 +524,7 @@ user: user, ) - expect(user.pending_profile).to be_nil + expect(user.gpo_verification_pending_profile).to be_nil end end end diff --git a/spec/presenters/idv/gpo_presenter_spec.rb b/spec/presenters/idv/gpo_presenter_spec.rb index 96940c3c90e..8d1b80329d8 100644 --- a/spec/presenters/idv/gpo_presenter_spec.rb +++ b/spec/presenters/idv/gpo_presenter_spec.rb @@ -66,7 +66,7 @@ describe '#fallback_back_path' do context 'when the user has a pending profile' do it 'returns the verify account path' do - create(:profile, user: user, deactivation_reason: :gpo_verification_pending) + create(:profile, user: user, gpo_verification_pending_at: 1.day.ago) expect(subject.fallback_back_path).to eq('/account/verify') end end diff --git a/spec/services/idv/cancel_verification_attempt_spec.rb b/spec/services/idv/cancel_verification_attempt_spec.rb index 848c820f4ae..673020b708d 100644 --- a/spec/services/idv/cancel_verification_attempt_spec.rb +++ b/spec/services/idv/cancel_verification_attempt_spec.rb @@ -2,7 +2,7 @@ describe Idv::CancelVerificationAttempt do let(:user) { create(:user, profiles: profiles) } - let(:profiles) { [create(:profile, deactivation_reason: :gpo_verification_pending)] } + let(:profiles) { [create(:profile, gpo_verification_pending_at: 1.day.ago)] } subject { described_class.new(user: user) } @@ -18,7 +18,7 @@ context 'the user has multiple pending profiles' do let(:profiles) do - super().push(create(:profile, deactivation_reason: :gpo_verification_pending)) + super().push(create(:profile, gpo_verification_pending_at: 2.days.ago)) end it 'deactivates both profiles' do @@ -50,14 +50,14 @@ context 'when there are pending profiles for other users' do it 'only updates profiles for the specificed user' do - other_profile = create(:profile, deactivation_reason: :gpo_verification_pending) + other_profile = create(:profile, gpo_verification_pending_at: 1.day.ago) subject.call expect(profiles[0].active).to eq(false) expect(profiles[0].reload.deactivation_reason).to eq('verification_cancelled') expect(other_profile.active).to eq(false) - expect(other_profile.reload.deactivation_reason).to eq('gpo_verification_pending') + expect(other_profile.reload.gpo_verification_pending?).to eq(true) end end end diff --git a/spec/services/image_upload_presigned_url_generator_spec.rb b/spec/services/image_upload_presigned_url_generator_spec.rb deleted file mode 100644 index 665805a1842..00000000000 --- a/spec/services/image_upload_presigned_url_generator_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -require 'rails_helper' - -RSpec.describe ImageUploadPresignedUrlGenerator do - include Rails.application.routes.url_helpers - - subject(:generator) { ImageUploadPresignedUrlGenerator.new } - - describe '#presigned_image_upload_url' do - subject(:presigned_image_upload_url) do - generator.presigned_image_upload_url(image_type: image_type, transaction_id: transaction_id) - end - - let(:image_type) { 'front' } - let(:transaction_id) { SecureRandom.uuid } - - before do - expect(IdentityConfig.store). - to receive(:doc_auth_enable_presigned_s3_urls).and_return(doc_auth_enable_presigned_s3_urls) - end - - context 'when doc_auth_enable_presigned_s3_urls is disabled' do - let(:doc_auth_enable_presigned_s3_urls) { false } - - it 'is nil' do - expect(presigned_image_upload_url).to eq(nil) - end - end - - context 'when doc_auth_enable_presigned_s3_urls is enabled' do - let(:doc_auth_enable_presigned_s3_urls) { true } - - before do - expect(Identity::Hostdata).to receive(:in_datacenter?).and_return(in_datacenter) - end - - context 'when run locally' do - let(:in_datacenter) { false } - - it 'is a local fake S3 URL' do - expect(presigned_image_upload_url). - to eq(test_fake_s3_url(key: "#{transaction_id}-#{image_type}")) - end - end - - context 'when run in the datacenter' do - let(:in_datacenter) { true } - - let(:real_s3_url) { 'https://s3.example.com/key/id/1234' } - - it 'is a real S3 url' do - # from aws_s3_helper - expect(generator).to receive(:s3_presigned_url). - with(hash_including(keyname: "#{transaction_id}-#{image_type}")). - and_return(real_s3_url) - - expect(presigned_image_upload_url).to eq(real_s3_url) - end - end - end - end -end diff --git a/spec/services/usps_in_person_proofing/transliterable_validator_spec.rb b/spec/services/usps_in_person_proofing/transliterable_validator_spec.rb index e06a8ff13a8..cb016ac3027 100644 --- a/spec/services/usps_in_person_proofing/transliterable_validator_spec.rb +++ b/spec/services/usps_in_person_proofing/transliterable_validator_spec.rb @@ -107,8 +107,10 @@ context 'failing transliteration' do let(:invalid_field) { 'def' } + let(:analytics) { FakeAnalytics.new } before do + allow(validator).to receive(:analytics).and_return(analytics) allow(validator.transliterator).to receive(:transliterate).with('def'). and_return( UspsInPersonProofing::Transliterator::TransliterationResult.new( @@ -128,6 +130,15 @@ expect(error.type).to eq(:nontransliterable_field) expect(error.options[:message]).to eq(message) end + + it 'logs nontransliterable characters' do + validator.validate(model) + + expect(analytics).to have_logged_event( + 'IdV: in person proofing characters submitted could not be transliterated', + nontransliterable_characters: ['*', '3', 'C'], + ) + end end context 'with callable error message' do @@ -210,6 +221,11 @@ let(:fields) { [:other_invalid_field, :valid_field, :invalid_field] } let(:invalid_field) { '123' } let(:other_invalid_field) { "\#@$%" } + let(:analytics) { FakeAnalytics.new } + + before do + allow(validator).to receive(:analytics).and_return(analytics) + end it 'sets multiple validation messages' do validator.validate(model) @@ -217,6 +233,15 @@ expect(errors).to include(:invalid_field) expect(errors).to include(:other_invalid_field) end + + it 'logs nontransliterable characters' do + validator.validate(model) + + expect(analytics).to have_logged_event( + 'IdV: in person proofing characters submitted could not be transliterated', + nontransliterable_characters: ['#', '$', '%', '1', '2', '3', '@'], + ) + end end end diff --git a/spec/support/idv_examples/gpo_otp_verification.rb b/spec/support/idv_examples/gpo_otp_verification.rb index 2d358e97982..b3adc5da569 100644 --- a/spec/support/idv_examples/gpo_otp_verification.rb +++ b/spec/support/idv_examples/gpo_otp_verification.rb @@ -19,7 +19,6 @@ else expect(profile.active).to be(false) expect(profile.fraud_review_pending?).to eq(fraud_review_pending) if fraud_review_pending - expect(profile.verified_at).to_not eq(nil) if fraud_review_pending expect(profile.deactivation_reason).to eq(nil) if fraud_review_pending end diff --git a/spec/views/idv/gpo_verify/index.html.erb_spec.rb b/spec/views/idv/gpo_verify/index.html.erb_spec.rb new file mode 100644 index 00000000000..c60065c63de --- /dev/null +++ b/spec/views/idv/gpo_verify/index.html.erb_spec.rb @@ -0,0 +1,39 @@ +require 'rails_helper' + +describe 'idv/gpo_verify/index.html.erb' do + let(:user) do + create(:user) + end + + let(:pii) do + {} + end + + before do + allow(view).to receive(:step_indicator_steps).and_return({}) + @gpo_verify_form = GpoVerifyForm.new( + user: user, + pii: pii, + otp: '1234', + ) + end + + context 'user is allowed to request another GPO letter' do + before do + @user_can_request_another_gpo_code = true + render + end + it 'includes the send another letter link' do + expect(rendered).to have_link(t('idv.messages.gpo.resend'), href: idv_gpo_path) + end + end + context 'user is NOT allowed to request another GPO letter' do + before do + @user_can_request_another_gpo_code = false + render + end + it 'does not include the send another letter link' do + expect(rendered).not_to have_link(t('idv.messages.gpo.resend'), href: idv_gpo_path) + end + end +end diff --git a/spec/views/idv/in_person/ready_to_verify/show.html.erb_spec.rb b/spec/views/idv/in_person/ready_to_verify/show.html.erb_spec.rb index e1c96bb0291..6ba33ce9988 100644 --- a/spec/views/idv/in_person/ready_to_verify/show.html.erb_spec.rb +++ b/spec/views/idv/in_person/ready_to_verify/show.html.erb_spec.rb @@ -65,6 +65,26 @@ ) end + context 'with enrollment where current address matches id' do + let(:current_address_matches_id) { true } + + it 'renders without proof of address instructions' do + render + + expect(rendered).not_to have_content(t('in_person_proofing.process.proof_of_address.heading')) + end + end + + context 'with enrollment where current address does not match id' do + let(:current_address_matches_id) { false } + + it 'renders with proof of address instructions' do + render + + expect(rendered).to have_content(t('in_person_proofing.process.proof_of_address.heading')) + end + end + context 'with enrollment where selected_location_details is present' do it 'renders a location' do render diff --git a/spec/views/idv/session_errors/failure.html.erb_spec.rb b/spec/views/idv/session_errors/failure.html.erb_spec.rb index f34585eeb4a..63cd918ca23 100644 --- a/spec/views/idv/session_errors/failure.html.erb_spec.rb +++ b/spec/views/idv/session_errors/failure.html.erb_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' describe 'idv/session_errors/failure.html.erb' do - let(:sp_name) { 'Example SP' } + let(:sp_name) { nil } let(:timeout_hours) { 6 } around do |ex| @@ -9,20 +9,15 @@ end before do - decorated_session = instance_double(ServiceProviderSessionDecorator, sp_name: sp_name) - allow(view).to receive(:decorated_session).and_return(decorated_session) allow(IdentityConfig.store).to receive(:idv_attempt_window_in_hours).and_return(timeout_hours) @expires_at = Time.zone.now + timeout_hours.hours + @sp_name = sp_name render end it 'renders a list of troubleshooting options' do - expect(rendered).to have_link( - t('idv.troubleshooting.options.get_help_at_sp', sp_name: sp_name), - href: return_to_sp_failure_to_proof_path(step: 'verify_info', location: 'failure'), - ) expect(rendered).to have_link( t('idv.troubleshooting.options.contact_support', app_name: APP_NAME), href: MarketingSite.contact_url, @@ -39,4 +34,26 @@ ), ) end + + it 'links back to the failure_to_proof URL' do + expect(rendered).to have_link( + t('idv.failure.exit.without_sp'), + href: return_to_sp_failure_to_proof_path(step: 'verify_id', location: 'failure'), + ) + end + + context 'with an associated service provider' do + let(:sp_name) { 'Example SP' } + + it 'links back to the SP failure_to_proof URL' do + expect(rendered).to have_link( + t( + 'idv.failure.exit.with_sp', + sp_name: sp_name, + app_name: 'Login.gov', + ), + href: return_to_sp_failure_to_proof_path(step: 'verify_id', location: 'failure'), + ) + end + end end diff --git a/spec/views/idv/session_errors/warning.html.erb_spec.rb b/spec/views/idv/session_errors/warning.html.erb_spec.rb index ec6fa5e6aaf..71d1e6eabef 100644 --- a/spec/views/idv/session_errors/warning.html.erb_spec.rb +++ b/spec/views/idv/session_errors/warning.html.erb_spec.rb @@ -22,35 +22,11 @@ end it 'shows remaining attempts' do - expect(rendered).to have_text(t('idv.failure.attempts', count: remaining_attempts)) + expect(rendered).to have_text(t('idv.warning.attempts', count: remaining_attempts)) end - it 'does not display troubleshooting options' do - expect(rendered).not_to have_content(t('components.troubleshooting_options.default_heading')) - end - - context 'with an associated service provider' do - let(:sp_name) { 'Example SP' } - - it 'renders troubleshooting option to get help at service provider' do - expect(rendered).to have_content(t('components.troubleshooting_options.default_heading')) - expect(rendered).to have_link( - t('idv.troubleshooting.options.get_help_at_sp', sp_name: sp_name), - href: return_to_sp_failure_to_proof_path(step: 'verify_info', location: 'warning'), - ) - end - end - - context 'with a flow session which had a barcode attention document capture result' do - let(:user_session) { { 'idv/doc_auth': { had_barcode_read_failure: true } } } - - it 'renders troubleshooting option to retake photos' do - expect(rendered).to have_content(t('components.troubleshooting_options.default_heading')) - expect(rendered).to have_link( - t('idv.troubleshooting.options.add_new_photos'), - href: idv_doc_auth_step_path(step: :redo_document_capture), - ) - end + it 'shows a cancel link' do + expect(rendered).to have_link(t('links.cancel'), href: idv_cancel_path) end context 'with a nil user_session' do @@ -58,11 +34,8 @@ it 'does not render troubleshooting option to retake photos' do expect(rendered).to have_link(t('idv.failure.button.warning'), href: try_again_path) - expect(rendered).to_not have_content(t('components.troubleshooting_options.default_heading')) - expect(rendered).to_not have_link( - t('idv.troubleshooting.options.add_new_photos'), - href: idv_doc_auth_step_path(step: :redo_document_capture), - ) + expect(rendered).to have_text(t('idv.warning.attempts', count: remaining_attempts)) + expect(rendered).to have_link(t('links.cancel'), href: idv_cancel_path) end end end diff --git a/spec/views/idv/shared/_document_capture.html.erb_spec.rb b/spec/views/idv/shared/_document_capture.html.erb_spec.rb index 5fa68c60112..383fc251d97 100644 --- a/spec/views/idv/shared/_document_capture.html.erb_spec.rb +++ b/spec/views/idv/shared/_document_capture.html.erb_spec.rb @@ -3,7 +3,6 @@ describe 'idv/shared/_document_capture.html.erb' do include Devise::Test::ControllerHelpers - let(:async_uploads_enabled) { false } let(:document_capture_session_uuid) { nil } let(:sp_name) { nil } let(:sp_issuer) { nil } @@ -11,8 +10,6 @@ let(:failure_to_proof_url) { return_to_sp_failure_to_proof_path } let(:in_person_proofing_enabled) { false } let(:in_person_proofing_enabled_issuer) { nil } - let(:front_image_upload_url) { nil } - let(:back_image_upload_url) { nil } let(:acuant_sdk_upgrade_a_b_testing_enabled) { false } let(:use_alternate_sdk) { false } let(:acuant_version) { '1.3.3.7' } @@ -28,8 +25,6 @@ allow(view).to receive(:decorated_session).and_return(decorated_session) allow(view).to receive(:url_for).and_return('https://example.com/') - allow(FeatureManagement).to receive(:document_capture_async_uploads_enabled?). - and_return(async_uploads_enabled) allow(Idv::InPersonConfig).to receive(:enabled_for_issuer?) do |issuer| if issuer.nil? in_person_proofing_enabled @@ -47,8 +42,6 @@ sp_name: sp_name, flow_path: flow_path, failure_to_proof_url: failure_to_proof_url, - front_image_upload_url: front_image_upload_url, - back_image_upload_url: back_image_upload_url, acuant_sdk_upgrade_a_b_testing_enabled: acuant_sdk_upgrade_a_b_testing_enabled, use_alternate_sdk: use_alternate_sdk, acuant_version: acuant_version, @@ -57,35 +50,6 @@ } end - describe 'async upload urls' do - context 'when async upload is disabled' do - let(:async_uploads_enabled) { false } - - it 'does not modify CSP connect_src headers' do - render_partial - - connect_src = controller.request.content_security_policy.connect_src - expect(connect_src).to eq( - ["'self'", '*.nr-data.net'], - ) - end - end - - context 'when async upload are enabled' do - let(:async_uploads_enabled) { true } - let(:front_image_upload_url) { 'https://s3.example.com/bucket/a?X-Amz-Security-Token=UAOL2' } - let(:back_image_upload_url) { 'https://s3.example.com/bucket/b?X-Amz-Security-Token=UAOL2' } - - it 'does modifies CSP connect_src headers to include upload urls' do - render_partial - - connect_src = controller.request.content_security_policy.connect_src - expect(connect_src).to include('https://s3.example.com/bucket/a') - expect(connect_src).to include('https://s3.example.com/bucket/b') - end - end - end - describe 'in person url' do context 'when in person proofing is disabled' do let(:in_person_proofing_enabled) { false } diff --git a/spec/views/layouts/application.html.erb_spec.rb b/spec/views/layouts/application.html.erb_spec.rb index 94d95663f0a..a6bb4ba9008 100644 --- a/spec/views/layouts/application.html.erb_spec.rb +++ b/spec/views/layouts/application.html.erb_spec.rb @@ -73,7 +73,7 @@ render doc = Nokogiri::HTML(rendered) - expect(doc.at_css('title').text).to include("Something with 'single quotes' - Login.gov") + expect(doc.at_css('title').text).to eq("Something with 'single quotes' | #{APP_NAME}") end it 'properly works with > in the title tag' do @@ -82,7 +82,7 @@ render doc = Nokogiri::HTML(rendered) - expect(doc.at_css('title').text).to include('Symbols <> - Login.gov') + expect(doc.at_css('title').text).to eq("Symbols <> | #{APP_NAME}") end end @@ -91,7 +91,7 @@ render doc = Nokogiri::HTML(rendered) - expect(doc.at_css('title').text).to include('Login.gov') + expect(doc.at_css('title').text).to eq(APP_NAME) end end end diff --git a/spec/views/sign_up/passwords/new.html.erb_spec.rb b/spec/views/sign_up/passwords/new.html.erb_spec.rb index a88f25003d2..b7e6651b7ee 100644 --- a/spec/views/sign_up/passwords/new.html.erb_spec.rb +++ b/spec/views/sign_up/passwords/new.html.erb_spec.rb @@ -1,12 +1,14 @@ require 'rails_helper' describe 'sign_up/passwords/new.html.erb' do + let(:user) { build_stubbed(:user) } + before do - user = build_stubbed(:user) allow(view).to receive(:current_user).and_return(nil) allow(view).to receive(:params).and_return(confirmation_token: 123) allow(view).to receive(:request_id).and_return(nil) + @email_address = user.email_addresses.first @password_form = PasswordForm.new(user) render @@ -29,6 +31,17 @@ ) end + it 'includes the user email address as a hidden field' do + # Reference: + # - https://www.chromium.org/developers/design-documents/create-amazing-password-forms/#use-hidden-fields-for-implicit-information + # - https://www.chromium.org/developers/design-documents/form-styles-that-chromium-understands/ + expect(user.email).to be_present + expect(rendered).to have_css( + "input[type='text'][name='username'][value='#{user.email}'][autocomplete='username']", + visible: false, + ) + end + it 'includes a form to cancel account creation' do expect(rendered).to have_link(t('links.cancel_account_creation')) end