diff --git a/.github/workflows/create-deploy-pr.yml b/.github/workflows/create-deploy-pr.yml
deleted file mode 100644
index 7027e598bb5..00000000000
--- a/.github/workflows/create-deploy-pr.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-name: Create deploy PR
-on:
- workflow_dispatch:
- inputs:
- deploy_type:
- description: 'Type of deploy'
- required: true
- type: choice
- options:
- - Normal
- - Patch
- source:
- description: 'Source branch/SHA (If blank, the current SHA running on staging will be used)'
- required: false
- type: string
-permissions:
- pull-requests: write
- contents: write
-jobs:
- create-pr:
- name: Create PR
- runs-on: ubuntu-latest
- env:
- GH_TOKEN: ${{ github.token }}
- PATCH: ${{ inputs.deploy_type == 'Patch' && 1 || 0 }}
- SOURCE: ${{ inputs.source }}
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0 # Get all commits
- - uses: ruby/setup-ruby@v1
- - run: scripts/create-deploy-pr
diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml
deleted file mode 100644
index 9f9bd2fd323..00000000000
--- a/.github/workflows/create-release.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-name: Create release
-run-name: "Create release based on ${{ github.event.pull_request.title }}"
-on:
- pull_request:
- types:
- - closed
- branches:
- - 'stages/prod'
-jobs:
- create-release:
- name: Create release after PR merge
- if: github.event.pull_request.merged == true
- runs-on: ubuntu-latest
- env:
- GH_TOKEN: ${{ github.token }}
- steps:
- - uses: actions/checkout@v4
- - run: scripts/create-release ${{ github.event.pull_request.number }}
\ No newline at end of file
diff --git a/Gemfile.lock b/Gemfile.lock
index d13d559d42c..20acfbd4e2d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -442,7 +442,7 @@ GEM
pg (1.5.4)
pg_query (4.2.3)
google-protobuf (>= 3.22.3)
- phonelib (0.8.6)
+ phonelib (0.8.7)
pkcs11 (0.3.4)
premailer (1.21.0)
addressable
diff --git a/app/controllers/api/internal/two_factor_authentication/auth_app_controller.rb b/app/controllers/api/internal/two_factor_authentication/auth_app_controller.rb
new file mode 100644
index 00000000000..f108c15b62a
--- /dev/null
+++ b/app/controllers/api/internal/two_factor_authentication/auth_app_controller.rb
@@ -0,0 +1,56 @@
+module Api
+ module Internal
+ module TwoFactorAuthentication
+ class AuthAppController < ApplicationController
+ include CsrfTokenConcern
+ include ReauthenticationRequiredConcern
+
+ before_action :render_unauthorized, unless: :recently_authenticated_2fa?
+
+ after_action :add_csrf_token_header_to_response
+
+ respond_to :json
+
+ def update
+ result = ::TwoFactorAuthentication::AuthAppUpdateForm.new(
+ user: current_user,
+ configuration_id: params[:id],
+ ).submit(name: params[:name])
+
+ analytics.auth_app_update_name_submitted(**result.to_h)
+
+ if result.success?
+ render json: { success: true }
+ else
+ render json: { success: false, error: result.first_error_message }, status: :bad_request
+ end
+ end
+
+ def destroy
+ result = ::TwoFactorAuthentication::AuthAppDeleteForm.new(
+ user: current_user,
+ configuration_id: params[:id],
+ ).submit
+
+ analytics.auth_app_delete_submitted(**result.to_h)
+
+ if result.success?
+ create_user_event(:authenticator_disabled)
+ revoke_remember_device(current_user)
+ event = PushNotification::RecoveryInformationChangedEvent.new(user: current_user)
+ PushNotification::HttpPush.deliver(event)
+ render json: { success: true }
+ else
+ render json: { success: false, error: result.first_error_message }, status: :bad_request
+ end
+ end
+
+ private
+
+ def render_unauthorized
+ render json: { error: 'Unauthorized' }, status: :unauthorized
+ end
+ end
+ end
+ end
+end
diff --git a/app/controllers/idv/in_person/usps_locations_controller.rb b/app/controllers/idv/in_person/usps_locations_controller.rb
index f1b97b207a8..67b1f10dc98 100644
--- a/app/controllers/idv/in_person/usps_locations_controller.rb
+++ b/app/controllers/idv/in_person/usps_locations_controller.rb
@@ -7,7 +7,6 @@ class UspsLocationsController < ApplicationController
include RenderConditionConcern
include UspsInPersonProofing
include EffectiveUser
- include UspsInPersonProofing
check_or_render_not_found -> { InPersonConfig.enabled? }
diff --git a/app/controllers/idv/welcome_controller.rb b/app/controllers/idv/welcome_controller.rb
index c16ea600c45..77e84bbb04d 100644
--- a/app/controllers/idv/welcome_controller.rb
+++ b/app/controllers/idv/welcome_controller.rb
@@ -13,7 +13,7 @@ def show
call('welcome', :view, true)
@sp_name = decorated_sp_session.sp_name || APP_NAME
- @title = t('doc_auth.headings.getting_started', sp_name: @sp_name)
+ @title = t('doc_auth.headings.welcome', sp_name: @sp_name)
end
def update
diff --git a/app/controllers/test/oidc_test_controller.rb b/app/controllers/test/oidc_test_controller.rb
new file mode 100644
index 00000000000..a77b4849e8e
--- /dev/null
+++ b/app/controllers/test/oidc_test_controller.rb
@@ -0,0 +1,147 @@
+require './spec/support/oidc_auth_helper'
+module Test
+ class OidcTestController < ApplicationController
+ include OidcAuthHelper
+
+ BIOMETRIC_REQUIRED = 'biometric-comparison-required'
+
+ def initialize
+ @client_id = 'urn:gov:gsa:openidconnect:sp:test'
+ super
+ end
+
+ def index
+ # default to require
+ @start_url_selfie = "#{test_oidc_auth_request_url}?ial=biometric-comparison-required"
+ @start_url_ial2 = "#{test_oidc_auth_request_url}?ial=2"
+ @start_url_ial1 = "#{test_oidc_auth_request_url}?ial=1"
+ update_service_provider
+ end
+
+ def auth_request
+ ial = prepare_step_up_flow(ial: params[:ial])
+
+ idp_url = authorization_url(
+ ial: ial,
+ aal: params[:aal],
+ )
+
+ Rails.logger.info("Redirecting to #{idp_url}")
+
+ redirect_to(idp_url)
+ end
+
+ def auth_result
+ redirect_to('/')
+ end
+
+ def logout
+ redirect_to(logout_uri)
+ end
+
+ def authorization_url(ial:, aal: nil)
+ authorization_endpoint = openid_configuration[:authorization_endpoint]
+ params = ial2_params(
+ client_id: client_id,
+ acr_values: acr_values(ial: ial, aal: aal),
+ biometric_comparison_required: ial == BIOMETRIC_REQUIRED,
+ state: random_value,
+ nonce: random_value,
+ )
+ request_params = params.merge(
+ scope: scopes_for(ial),
+ redirect_uri: test_oidc_auth_result_url,
+ ).compact.to_query
+ "#{authorization_endpoint}?#{request_params}"
+ end
+
+ def prepare_step_up_flow(ial:)
+ if ial == 'step-up'
+ ial = '1'
+ end
+ ial
+ end
+
+ def scopes_for(ial)
+ case ial
+ when '0'
+ 'openid email social_security_number'
+ when '1', nil
+ 'openid email'
+ when '2', BIOMETRIC_REQUIRED
+ 'openid email profile social_security_number phone address'
+ else
+ raise ArgumentError.new("Unexpected IAL: #{ial.inspect}")
+ end
+ end
+
+ def acr_values(ial:, aal:)
+ ial_value = {
+ '0' => 'http://idmanagement.gov/ns/assurance/ial/0',
+ nil => 'http://idmanagement.gov/ns/assurance/ial/1',
+ '' => 'http://idmanagement.gov/ns/assurance/ial/1',
+ '1' => 'http://idmanagement.gov/ns/assurance/ial/1',
+ '2' => 'http://idmanagement.gov/ns/assurance/ial/2',
+ 'biometric-comparison-required' => 'http://idmanagement.gov/ns/assurance/ial/2',
+ }[ial]
+ aal_value = {
+ '2' => 'http://idmanagement.gov/ns/assurance/aal/2',
+ '2-phishing_resistant' => 'http://idmanagement.gov/ns/assurance/aal/2?phishing_resistant=true',
+ '2-hspd12' => 'http://idmanagement.gov/ns/assurance/aal/2?hspd12=true',
+ }[aal]
+ [ial_value, aal_value].compact.join(' ')
+ end
+
+ def json(response)
+ JSON.parse(response.to_s).with_indifferent_access
+ end
+
+ def random_value
+ SecureRandom.hex
+ end
+
+ def client_id
+ @client_id
+ end
+
+ private
+
+ def logout_uri
+ endpoint = openid_configuration[:end_session_endpoint]
+ request_params = {
+ client_id: client_id,
+ post_logout_redirect_uri: '/',
+ state: SecureRandom.hex,
+ }.to_query
+
+ "#{endpoint}?#{request_params}"
+ end
+
+ def openid_configuration
+ @openid_configuration ||= OpenidConnectConfigurationPresenter.new.configuration
+ end
+
+ def idp_public_key
+ @idp_public_key ||= load_idp_public_key
+ end
+
+ def load_idp_public_key
+ keys = OpenidConnectCertsPresenter.new.certs[:keys]
+ JSON::JWK.new(keys.first).to_key
+ end
+
+ def update_service_provider
+ return @service_provider if defined?(@service_provider)
+ @service_provider = ServiceProvider.find_by(issuer: client_id)
+ # inject root url
+ changed = false
+ [test_oidc_logout_url, test_oidc_auth_result_url, root_url].each do |url|
+ if @service_provider&.redirect_uris && !@service_provider.redirect_uris.include?(url)
+ @service_provider.redirect_uris.append(url)
+ changed = true
+ end
+ end
+ @service_provider.save! if changed
+ end
+ end
+end
diff --git a/app/controllers/two_factor_authentication/webauthn_verification_controller.rb b/app/controllers/two_factor_authentication/webauthn_verification_controller.rb
index df9c15076d9..f4cd119bc25 100644
--- a/app/controllers/two_factor_authentication/webauthn_verification_controller.rb
+++ b/app/controllers/two_factor_authentication/webauthn_verification_controller.rb
@@ -4,6 +4,7 @@ class WebauthnVerificationController < ApplicationController
include TwoFactorAuthenticatable
before_action :check_sp_required_mfa
+ before_action :check_if_device_supports_platform_auth, only: :show
before_action :confirm_webauthn_enabled, only: :show
def show
@@ -33,6 +34,17 @@ def confirm
private
+ def check_if_device_supports_platform_auth
+ return unless user_session.has_key?(:platform_authenticator_available)
+ if platform_authenticator? && !device_supports_webauthn_platform?
+ redirect_to login_two_factor_options_url
+ end
+ end
+
+ def device_supports_webauthn_platform?
+ user_session.delete(:platform_authenticator_available) == true
+ end
+
def handle_webauthn_result(result)
if result.success?
handle_valid_webauthn
diff --git a/app/controllers/users/auth_app_controller.rb b/app/controllers/users/auth_app_controller.rb
new file mode 100644
index 00000000000..2082362dc04
--- /dev/null
+++ b/app/controllers/users/auth_app_controller.rb
@@ -0,0 +1,65 @@
+module Users
+ class AuthAppController < ApplicationController
+ include ReauthenticationRequiredConcern
+
+ before_action :confirm_two_factor_authenticated
+ before_action :confirm_recently_authenticated_2fa
+ before_action :set_form
+ before_action :validate_configuration_exists
+
+ def edit; end
+
+ def update
+ result = form.submit(name: params.dig(:form, :name))
+
+ analytics.auth_app_update_name_submitted(**result.to_h)
+
+ if result.success?
+ flash[:success] = t('two_factor_authentication.auth_app.renamed')
+ redirect_to account_path
+ else
+ flash.now[:error] = result.first_error_message
+ render :edit
+ end
+ end
+
+ def destroy
+ result = form.submit
+
+ analytics.auth_app_delete_submitted(**result.to_h)
+
+ if result.success?
+ flash[:success] = t('two_factor_authentication.auth_app.deleted')
+ create_user_event(:authenticator_disabled)
+ revoke_remember_device(current_user)
+ event = PushNotification::RecoveryInformationChangedEvent.new(user: current_user)
+ PushNotification::HttpPush.deliver(event)
+ redirect_to account_path
+ else
+ flash[:error] = result.first_error_message
+ redirect_to edit_auth_app_path(id: params[:id])
+ end
+ end
+
+ private
+
+ def form
+ @form ||= form_class.new(user: current_user, configuration_id: params[:id])
+ end
+
+ alias_method :set_form, :form
+
+ def form_class
+ case action_name
+ when 'edit', 'update'
+ TwoFactorAuthentication::AuthAppUpdateForm
+ when 'destroy'
+ TwoFactorAuthentication::AuthAppDeleteForm
+ end
+ end
+
+ def validate_configuration_exists
+ render_not_found if form.configuration.blank?
+ end
+ end
+end
diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb
index 7b019aa33da..651c8258c8d 100644
--- a/app/controllers/users/sessions_controller.rb
+++ b/app/controllers/users/sessions_controller.rb
@@ -121,6 +121,8 @@ def handle_valid_authentication
user_id: current_user.id,
email: auth_params[:email],
)
+ user_session[:platform_authenticator_available] =
+ params[:platform_authenticator_available] == 'true'
redirect_to next_url_after_valid_authentication
end
diff --git a/app/controllers/users/two_factor_authentication_controller.rb b/app/controllers/users/two_factor_authentication_controller.rb
index d3b1602f3f1..ec8980a3a9f 100644
--- a/app/controllers/users/two_factor_authentication_controller.rb
+++ b/app/controllers/users/two_factor_authentication_controller.rb
@@ -1,6 +1,7 @@
module Users
class TwoFactorAuthenticationController < ApplicationController
include TwoFactorAuthenticatable
+ include ApplicationHelper
include ActionView::Helpers::DateHelper
before_action :check_remember_device_preference
diff --git a/app/controllers/users/webauthn_setup_controller.rb b/app/controllers/users/webauthn_setup_controller.rb
index e71ffc0fd2b..a9b64774af4 100644
--- a/app/controllers/users/webauthn_setup_controller.rb
+++ b/app/controllers/users/webauthn_setup_controller.rb
@@ -85,7 +85,8 @@ def confirm
if result.success?
process_valid_webauthn(form)
else
- process_invalid_webauthn(form)
+ flash.now[:error] = result.first_error_message
+ render :new
end
end
@@ -205,24 +206,6 @@ def need_to_set_up_additional_mfa?
in_multi_mfa_selection_flow? && mfa_selection_count < 2
end
- def process_invalid_webauthn(form)
- if form.name_taken
- if form.platform_authenticator?
- flash.now[:error] = t('errors.webauthn_platform_setup.unique_name')
- else
- flash.now[:error] = t('errors.webauthn_setup.unique_name')
- end
- elsif form.platform_authenticator?
- flash[:error] = t('errors.webauthn_platform_setup.general_error')
- else
- flash[:error] = t(
- 'errors.webauthn_setup.general_error_html',
- link_html: t('errors.webauthn_setup.additional_methods_link'),
- )
- end
- render :new
- end
-
def new_params
params.permit(:platform, :error)
end
diff --git a/app/forms/two_factor_authentication/auth_app_delete_form.rb b/app/forms/two_factor_authentication/auth_app_delete_form.rb
new file mode 100644
index 00000000000..6a49f4513e4
--- /dev/null
+++ b/app/forms/two_factor_authentication/auth_app_delete_form.rb
@@ -0,0 +1,57 @@
+module TwoFactorAuthentication
+ class AuthAppDeleteForm
+ include ActiveModel::Model
+ include ActionView::Helpers::TranslationHelper
+
+ attr_reader :user, :configuration_id
+
+ validate :validate_configuration_exists
+ validate :validate_has_multiple_mfa
+
+ def initialize(user:, configuration_id:)
+ @user = user
+ @configuration_id = configuration_id
+ end
+
+ def submit
+ success = valid?
+
+ configuration.destroy if success
+
+ FormResponse.new(
+ success:,
+ errors:,
+ extra: extra_analytics_attributes,
+ serialize_error_details_only: true,
+ )
+ end
+
+ def configuration
+ @configuration ||= user.auth_app_configurations.find_by(id: configuration_id)
+ end
+
+ private
+
+ def validate_configuration_exists
+ return if configuration.present?
+ errors.add(
+ :configuration_id,
+ :configuration_not_found,
+ message: t('errors.manage_authenticator.internal_error'),
+ )
+ end
+
+ def validate_has_multiple_mfa
+ return if !configuration || MfaPolicy.new(user).multiple_factors_enabled?
+ errors.add(
+ :configuration_id,
+ :only_method,
+ message: t('errors.manage_authenticator.remove_only_method_error'),
+ )
+ end
+
+ def extra_analytics_attributes
+ { configuration_id: }
+ end
+ end
+end
diff --git a/app/forms/two_factor_authentication/auth_app_update_form.rb b/app/forms/two_factor_authentication/auth_app_update_form.rb
new file mode 100644
index 00000000000..cd6bb2dbcbb
--- /dev/null
+++ b/app/forms/two_factor_authentication/auth_app_update_form.rb
@@ -0,0 +1,68 @@
+module TwoFactorAuthentication
+ class AuthAppUpdateForm
+ include ActiveModel::Model
+ include ActionView::Helpers::TranslationHelper
+
+ attr_reader :user, :configuration_id
+
+ validate :validate_configuration_exists
+ validate :validate_unique_name
+
+ def initialize(user:, configuration_id:)
+ @user = user
+ @configuration_id = configuration_id
+ end
+
+ def submit(name:)
+ @name = name
+
+ success = valid?
+ if valid?
+ configuration.name = name
+ success = configuration.valid?
+ errors.merge!(configuration.errors)
+ configuration.save if success
+ end
+
+ FormResponse.new(
+ success:,
+ errors:,
+ extra: extra_analytics_attributes,
+ serialize_error_details_only: true,
+ )
+ end
+
+ def name
+ return @name if defined?(@name)
+ @name = configuration&.name
+ end
+
+ def configuration
+ @configuration ||= user.auth_app_configurations.find_by(id: configuration_id)
+ end
+
+ private
+
+ def validate_configuration_exists
+ return if configuration.present?
+ errors.add(
+ :configuration_id,
+ :configuration_not_found,
+ message: t('errors.manage_authenticator.internal_error'),
+ )
+ end
+
+ def validate_unique_name
+ return unless user.auth_app_configurations.where.not(id: configuration_id).find_by(name:)
+ errors.add(
+ :name,
+ :duplicate,
+ message: t('errors.manage_authenticator.unique_name_error'),
+ )
+ end
+
+ def extra_analytics_attributes
+ { configuration_id: }
+ end
+ end
+end
diff --git a/app/forms/webauthn_setup_form.rb b/app/forms/webauthn_setup_form.rb
index 55ea451c109..84678797690 100644
--- a/app/forms/webauthn_setup_form.rb
+++ b/app/forms/webauthn_setup_form.rb
@@ -1,14 +1,15 @@
class WebauthnSetupForm
include ActiveModel::Model
- validates :user, presence: true
- validates :challenge, presence: true
- validates :attestation_object, presence: true
- validates :client_data_json, presence: true
- validates :name, presence: true
+ validates :user,
+ :challenge,
+ :attestation_object,
+ :client_data_json,
+ :name,
+ presence: { message: proc { |object| object.send(:generic_error_message) } }
validate :name_is_unique
- attr_reader :attestation_response, :name_taken
+ attr_reader :attestation_response
def initialize(user:, user_session:, device_name:)
@user = user
@@ -43,6 +44,17 @@ def platform_authenticator?
!!@platform_authenticator
end
+ def generic_error_message
+ if platform_authenticator?
+ I18n.t('errors.webauthn_platform_setup.general_error')
+ else
+ I18n.t(
+ 'errors.webauthn_setup.general_error_html',
+ link_html: I18n.t('errors.webauthn_setup.additional_methods_link'),
+ )
+ end
+ end
+
private
attr_reader :success, :transports, :invalid_transports
@@ -71,8 +83,12 @@ def name_is_unique
count
@name = "#{@name} (#{num_existing_devices})"
else
- errors.add :name, I18n.t('errors.webauthn_setup.unique_name'), type: :unique_name
- @name_taken = true
+ name_error = if platform_authenticator?
+ I18n.t('errors.webauthn_platform_setup.unique_name')
+ else
+ I18n.t('errors.webauthn_setup.unique_name')
+ end
+ errors.add :name, name_error, type: :unique_name
end
end
@@ -85,20 +101,24 @@ def valid_attestation_response?(protocol)
end
def safe_response(original_origin)
- @attestation_response.valid?(@challenge.pack('c*'), original_origin)
+ response = @attestation_response.valid?(@challenge.pack('c*'), original_origin)
+ add_attestation_error unless response
+ response
rescue StandardError
+ add_attestation_error
+ false
+ end
+
+ def add_attestation_error
if @platform_authenticator
- errors.add :name, I18n.t(
- 'errors.webauthn_platform_setup.attestation_error',
- link: MarketingSite.contact_url,
- ), type: :attestation_error
+ errors.add :name, I18n.t('errors.webauthn_platform_setup.general_error'),
+ type: :attestation_error
else
errors.add :name, I18n.t(
- 'errors.webauthn_setup.attestation_error',
- link: MarketingSite.contact_url,
+ 'errors.webauthn_setup.general_error_html',
+ link_html: I18n.t('errors.webauthn_setup.additional_methods_link'),
), type: :attestation_error
end
- false
end
def process_authenticator_data_value(data_value)
diff --git a/app/javascript/packages/webauthn/index.ts b/app/javascript/packages/webauthn/index.ts
index 7539dcc2507..a21ec105f22 100644
--- a/app/javascript/packages/webauthn/index.ts
+++ b/app/javascript/packages/webauthn/index.ts
@@ -2,6 +2,8 @@ export { default as enrollWebauthnDevice } from './enroll-webauthn-device';
export { default as extractCredentials } from './extract-credentials';
export { default as verifyWebauthnDevice } from './verify-webauthn-device';
export { default as isExpectedWebauthnError } from './is-expected-error';
+export { default as isWebauthnPlatformAuthenticatorAvailable } from './is-webauthn-platform-authenticator-available';
+export { default as isWebauthnPasskeySupported } from './is-webauthn-passkey-supported';
export * from './converters';
export type { VerifyCredentialDescriptor } from './verify-webauthn-device';
diff --git a/app/javascript/packs/platform-authenticator-available.ts b/app/javascript/packs/platform-authenticator-available.ts
new file mode 100644
index 00000000000..e77e7195b70
--- /dev/null
+++ b/app/javascript/packs/platform-authenticator-available.ts
@@ -0,0 +1,20 @@
+import {
+ isWebauthnPlatformAuthenticatorAvailable,
+ isWebauthnPasskeySupported,
+} from '@18f/identity-webauthn';
+
+async function platformAuthenticatorAvailable() {
+ const platformAuthenticatorAvailableInput = document.getElementById(
+ 'platform_authenticator_available',
+ ) as HTMLInputElement;
+ if (!platformAuthenticatorAvailableInput) {
+ return;
+ }
+ if (isWebauthnPasskeySupported() && (await isWebauthnPlatformAuthenticatorAvailable())) {
+ platformAuthenticatorAvailableInput.value = 'true';
+ } else {
+ platformAuthenticatorAvailableInput.value = 'false';
+ }
+}
+
+platformAuthenticatorAvailable();
diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb
index 60e74186606..b1934f1b620 100644
--- a/app/jobs/resolution_proofing_job.rb
+++ b/app/jobs/resolution_proofing_job.rb
@@ -19,8 +19,7 @@ def perform(
encrypted_arguments:,
trace_id:,
should_proof_state_id:,
- double_address_verification: nil,
- ipp_enrollment_in_progress: false,
+ ipp_enrollment_in_progress:,
user_id: nil,
threatmetrix_session_id: nil,
request_ip: nil,
@@ -46,7 +45,6 @@ def perform(
threatmetrix_session_id: threatmetrix_session_id,
request_ip: request_ip,
should_proof_state_id: should_proof_state_id,
- double_address_verification: double_address_verification,
ipp_enrollment_in_progress: ipp_enrollment_in_progress,
instant_verify_ab_test_discriminator: instant_verify_ab_test_discriminator,
)
@@ -75,7 +73,6 @@ def make_vendor_proofing_requests(
threatmetrix_session_id:,
request_ip:,
should_proof_state_id:,
- double_address_verification:,
ipp_enrollment_in_progress:,
instant_verify_ab_test_discriminator:
)
@@ -85,7 +82,6 @@ def make_vendor_proofing_requests(
threatmetrix_session_id: threatmetrix_session_id,
request_ip: request_ip,
should_proof_state_id: should_proof_state_id,
- double_address_verification: double_address_verification,
ipp_enrollment_in_progress: ipp_enrollment_in_progress,
timer: timer,
)
diff --git a/app/models/disposable_domain.rb b/app/models/disposable_domain.rb
deleted file mode 100644
index 082199f0970..00000000000
--- a/app/models/disposable_domain.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class DisposableDomain < ApplicationRecord
- class << self
- def disposable?(domain)
- return false if !domain.is_a?(String) || domain.empty?
-
- exists?(name: domain)
- end
- end
-end
diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb
index f1956723085..223af635040 100644
--- a/app/services/analytics_events.rb
+++ b/app/services/analytics_events.rb
@@ -188,6 +188,45 @@ def add_phone_setup_visit
)
end
+ # Tracks when a user deletes their auth app from account
+ # @param [Boolean] success
+ # @param [Hash] error_details
+ # @param [Integer] configuration_id
+ def auth_app_delete_submitted(
+ success:,
+ configuration_id:,
+ error_details: nil,
+ **extra
+ )
+ track_event(
+ :auth_app_delete_submitted,
+ success:,
+ error_details:,
+ configuration_id:,
+ **extra,
+ )
+ end
+
+ # When a user updates name for auth app
+ # @param [Boolean] success
+ # @param [Hash] error_details
+ # @param [Integer] configuration_id
+ # Tracks when user submits a name change for an Auth App configuration
+ def auth_app_update_name_submitted(
+ success:,
+ configuration_id:,
+ error_details: nil,
+ **extra
+ )
+ track_event(
+ :auth_app_update_name_submitted,
+ success:,
+ error_details:,
+ configuration_id:,
+ **extra,
+ )
+ end
+
# When a user views the "you are already signed in with the following email" screen
def authentication_confirmation
track_event('Authentication Confirmation')
@@ -4531,6 +4570,7 @@ def user_registration_cancellation(request_came_from:, **extra)
# @param [String] needs_completion_screen_reason
# @param [Array] sp_request_requested_attributes
# @param [Array] sp_session_requested_attributes
+ # @param [String, nil] disposable_email_domain Disposable email domain used for registration
def user_registration_complete(
ial2:,
service_provider_name:,
@@ -4539,6 +4579,7 @@ def user_registration_complete(
sp_session_requested_attributes:,
sp_request_requested_attributes: nil,
ialmax: nil,
+ disposable_email_domain: nil,
**extra
)
track_event(
@@ -4550,6 +4591,7 @@ def user_registration_complete(
needs_completion_screen_reason: needs_completion_screen_reason,
sp_request_requested_attributes: sp_request_requested_attributes,
sp_session_requested_attributes: sp_session_requested_attributes,
+ disposable_email_domain: disposable_email_domain,
**extra,
)
end
diff --git a/app/services/doc_auth/lexis_nexis/responses/true_id_response.rb b/app/services/doc_auth/lexis_nexis/responses/true_id_response.rb
index 0bdf08488c2..7d2e411e7f1 100644
--- a/app/services/doc_auth/lexis_nexis/responses/true_id_response.rb
+++ b/app/services/doc_auth/lexis_nexis/responses/true_id_response.rb
@@ -246,7 +246,8 @@ def all_passed?
transaction_status_passed? &&
true_id_product.present? &&
product_status_passed? &&
- doc_auth_result_passed?
+ doc_auth_result_passed? &&
+ (@liveness_checking_enabled ? selfie_success : true)
end
def selfie_result
diff --git a/app/services/doc_auth/mock/result_response.rb b/app/services/doc_auth/mock/result_response.rb
index fd3d6524655..2c9a24bebd1 100644
--- a/app/services/doc_auth/mock/result_response.rb
+++ b/app/services/doc_auth/mock/result_response.rb
@@ -52,8 +52,7 @@ def errors
# Error generator is not to be called when it's not failure
# allows us to test successful results
return {} if all_doc_capture_values_passing?(
- doc_auth_result, id_type_supported?,
- face_match_result
+ doc_auth_result, id_type_supported?
)
mock_args = {}
@@ -178,10 +177,10 @@ def doc_auth_result_from_success
end
end
- def all_doc_capture_values_passing?(doc_auth_result, id_type_supported, face_match_result)
+ def all_doc_capture_values_passing?(doc_auth_result, id_type_supported)
doc_auth_result == 'Passed' &&
id_type_supported &&
- (@selfie_check_performed ? face_match_result == 'Pass' : true)
+ (@selfie_check_performed ? selfie_success : true)
end
def parse_uri
diff --git a/app/services/idv/agent.rb b/app/services/idv/agent.rb
index 045af77a857..abad581798c 100644
--- a/app/services/idv/agent.rb
+++ b/app/services/idv/agent.rb
@@ -11,7 +11,7 @@ def proof_resolution(
user_id:,
threatmetrix_session_id:,
request_ip:,
- ipp_enrollment_in_progress: false
+ ipp_enrollment_in_progress:
)
document_capture_session.create_proofing_session
@@ -28,7 +28,6 @@ def proof_resolution(
user_id: user_id,
threatmetrix_session_id: threatmetrix_session_id,
request_ip: request_ip,
- double_address_verification: ipp_enrollment_in_progress,
ipp_enrollment_in_progress: ipp_enrollment_in_progress,
}
diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb
index 791a723733e..eddbd58229b 100644
--- a/app/services/proofing/resolution/progressive_proofer.rb
+++ b/app/services/proofing/resolution/progressive_proofer.rb
@@ -29,8 +29,7 @@ def proof(
threatmetrix_session_id:,
timer:,
user_email:,
- double_address_verification: nil,
- ipp_enrollment_in_progress: false
+ ipp_enrollment_in_progress:
)
device_profiling_result = proof_with_threatmetrix_if_needed(
applicant_pii: applicant_pii,
@@ -43,7 +42,6 @@ def proof(
residential_instant_verify_result = proof_residential_address_if_needed(
applicant_pii: applicant_pii,
timer: timer,
- double_address_verification: double_address_verification,
ipp_enrollment_in_progress: ipp_enrollment_in_progress,
)
@@ -56,7 +54,6 @@ def proof(
applicant_pii: applicant_pii_transformed,
timer: timer,
residential_instant_verify_result: residential_instant_verify_result,
- double_address_verification: double_address_verification,
ipp_enrollment_in_progress: ipp_enrollment_in_progress,
)
@@ -66,13 +63,11 @@ def proof(
residential_instant_verify_result: residential_instant_verify_result,
instant_verify_result: instant_verify_result,
should_proof_state_id: should_proof_state_id,
- double_address_verification: double_address_verification,
ipp_enrollment_in_progress: ipp_enrollment_in_progress,
)
ResultAdjudicator.new(
device_profiling_result: device_profiling_result,
- double_address_verification: double_address_verification,
ipp_enrollment_in_progress: ipp_enrollment_in_progress,
resolution_result: instant_verify_result,
should_proof_state_id: should_proof_state_id,
@@ -111,11 +106,9 @@ def proof_with_threatmetrix_if_needed(
end
end
- # rubocop:disable Lint/UnusedMethodArgument
def proof_residential_address_if_needed(
applicant_pii:,
timer:,
- double_address_verification: false,
ipp_enrollment_in_progress: false
)
return residential_address_unnecessary_result unless ipp_enrollment_in_progress
@@ -124,7 +117,6 @@ def proof_residential_address_if_needed(
resolution_proofer.proof(applicant_pii)
end
end
- # rubocop:enable Lint/UnusedMethodArgument
def residential_address_unnecessary_result
Proofing::Resolution::Result.new(
@@ -138,10 +130,8 @@ def resolution_cannot_pass
)
end
- # rubocop:disable Lint/UnusedMethodArgument
def proof_id_address_with_lexis_nexis_if_needed(applicant_pii:, timer:,
residential_instant_verify_result:,
- double_address_verification:,
ipp_enrollment_in_progress:)
if applicant_pii[:same_address_as_id] == 'true' && ipp_enrollment_in_progress
return residential_instant_verify_result
@@ -155,10 +145,9 @@ def proof_id_address_with_lexis_nexis_if_needed(applicant_pii:, timer:,
def should_proof_state_id_with_aamva?(ipp_enrollment_in_progress:, same_address_as_id:,
should_proof_state_id:, instant_verify_result:,
- residential_instant_verify_result:,
- double_address_verification:)
+ residential_instant_verify_result:)
return false unless should_proof_state_id
- # If the user is in double-address-verification and they have changed their address then
+ # If the user is in in-person-proofing and they have changed their address then
# they are not eligible for get-to-yes
if !ipp_enrollment_in_progress || same_address_as_id == 'true'
user_can_pass_after_state_id_check?(instant_verify_result)
@@ -166,19 +155,16 @@ def should_proof_state_id_with_aamva?(ipp_enrollment_in_progress:, same_address_
residential_instant_verify_result.success?
end
end
- # rubocop:enable Lint/UnusedMethodArgument
def proof_id_with_aamva_if_needed(
applicant_pii:, timer:,
residential_instant_verify_result:,
instant_verify_result:,
should_proof_state_id:,
- ipp_enrollment_in_progress:,
- double_address_verification:
+ ipp_enrollment_in_progress:
)
same_address_as_id = applicant_pii[:same_address_as_id]
should_proof_state_id_with_aamva = should_proof_state_id_with_aamva?(
- double_address_verification:,
ipp_enrollment_in_progress:,
same_address_as_id:,
should_proof_state_id:,
diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb
index 3d9a05224d4..0d71af7f763 100644
--- a/app/services/proofing/resolution/result_adjudicator.rb
+++ b/app/services/proofing/resolution/result_adjudicator.rb
@@ -2,8 +2,7 @@ module Proofing
module Resolution
class ResultAdjudicator
attr_reader :resolution_result, :state_id_result, :device_profiling_result,
- :double_address_verification, :ipp_enrollment_in_progress,
- :residential_resolution_result, :same_address_as_id
+ :ipp_enrollment_in_progress, :residential_resolution_result, :same_address_as_id
def initialize(
resolution_result:, # InstantVerify
@@ -12,14 +11,12 @@ def initialize(
should_proof_state_id:,
ipp_enrollment_in_progress:,
device_profiling_result:,
- same_address_as_id:,
- double_address_verification: true
+ same_address_as_id:
)
@resolution_result = resolution_result
@state_id_result = state_id_result
@should_proof_state_id = should_proof_state_id
@ipp_enrollment_in_progress = ipp_enrollment_in_progress
- @double_address_verification = double_address_verification
@device_profiling_result = device_profiling_result
@residential_resolution_result = residential_resolution_result
@same_address_as_id = same_address_as_id # this is a string, "true" or "false"
diff --git a/app/views/account_reset/recovery_options/show.html.erb b/app/views/account_reset/recovery_options/show.html.erb
index 8b953527e27..8593e3a0e66 100644
--- a/app/views/account_reset/recovery_options/show.html.erb
+++ b/app/views/account_reset/recovery_options/show.html.erb
@@ -10,8 +10,9 @@
<%= c.with_item(heading: t('account_reset.recovery_options.use_device')) do %>
<%= t('account_reset.recovery_options.check_webauthn_platform_info', app_name: APP_NAME) %>
+ <%= c.with_item(heading: t('account_reset.recovery_options.check_saved_credential')) do %>
+
- <% MfaContext.new(current_user).auth_app_configurations.each do |auth_app_configuration| %>
-
-
-
- <%= auth_app_configuration.name %>
-
-
- <% if MfaPolicy.new(current_user).multiple_factors_enabled? %>
-
- <%= render 'accounts/actions/disable_totp', id: auth_app_configuration.id %>
-
- <% end %>
-
+
+
+ <% MfaContext.new(current_user).auth_app_configurations.each do |configuration| %>
+ <%= render ManageableAuthenticatorComponent.new(
+ configuration:,
+ user_session:,
+ manage_url: edit_auth_app_path(id: configuration.id),
+ manage_api_url: api_internal_two_factor_authentication_auth_app_path(id: configuration.id),
+ custom_strings: {
+ deleted: t('two_factor_authentication.auth_app.deleted'),
+ renamed: t('two_factor_authentication.auth_app.renamed'),
+ manage_accessible_label: t('two_factor_authentication.auth_app.manage_accessible_label'),
+ },
+ role: 'list-item',
+ ) %>
<% end %>
+
<% if current_user.auth_app_configurations.count < IdentityConfig.store.max_auth_apps_per_account %>
<%= render ButtonComponent.new(
action: ->(**tag_options, &block) do
diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb
index f4550fd95ac..48c66b23065 100644
--- a/app/views/devise/sessions/new.html.erb
+++ b/app/views/devise/sessions/new.html.erb
@@ -47,6 +47,7 @@
},
},
) %>
+ <%= hidden_field_tag :platform_authenticator_available, id: 'platform_authenticator_available' %>
<%= f.submit t('links.sign_in'), full_width: true, wide: false %>
<% end %>
<% if @ial && desktop_device? %>
@@ -86,3 +87,5 @@
<% end %>
+<%= javascript_packs_tag_once('platform-authenticator-available') %>
+
diff --git a/app/views/idv/welcome/show.html.erb b/app/views/idv/welcome/show.html.erb
index 545c3886db9..0ce1009fd3c 100644
--- a/app/views/idv/welcome/show.html.erb
+++ b/app/views/idv/welcome/show.html.erb
@@ -1,107 +1,67 @@
-<% self.title = t('doc_auth.headings.welcome') %>
-
-<% content_for(:pre_flash_content) do %>
- <%= render StepIndicatorComponent.new(
- steps: Idv::StepIndicatorConcern::STEP_INDICATOR_STEPS,
- current_step: :getting_started,
- locale_scope: 'idv',
- class: 'margin-x-neg-2 margin-top-neg-4 tablet:margin-x-neg-6 tablet:margin-top-neg-4',
- ) %>
-<% end %>
-
- <%= render JavascriptRequiredComponent.new(
- header: t('idv.welcome.no_js_header'),
- intro: t('idv.welcome.no_js_intro', sp_name: decorated_sp_session.sp_name || APP_NAME),
- location: :idv_welcome,
- ) do %>
-
- <%= render PageHeadingComponent.new.with_content(t('doc_auth.headings.welcome')) %>
-
- <%= t('doc_auth.info.welcome', sp_name: decorated_sp_session.sp_name || APP_NAME) %>
-
+<% self.title = @title %>
+<%= render JavascriptRequiredComponent.new(
+ header: t('idv.welcome.no_js_header'),
+ intro: t('idv.welcome.no_js_intro', sp_name: @sp_name),
+ location: :idv_welcome,
+ ) do %>
+<%= render PageHeadingComponent.new.with_content(@title) %>
+
+ <%= t(
+ 'doc_auth.info.getting_started_html',
+ sp_name: @sp_name,
+ link_html: new_tab_link_to(
+ t('doc_auth.info.getting_started_learn_more'),
+ help_center_redirect_path(
+ category: 'verify-your-identity',
+ article: 'how-to-verify-your-identity',
+ flow: :idv,
+ step: :welcome,
+ location: 'intro_paragraph',
+ ),
+ ),
+ ) %>
+
-
<%= t('doc_auth.instructions.welcome') %>
+
<%= t('doc_auth.instructions.getting_started') %>
- <%= render ProcessListComponent.new(heading_level: :h3, class: 'margin-y-3') do |c| %>
+ <%= render ProcessListComponent.new(heading_level: :h3, class: 'margin-y-3') do |c| %>
+ <% if decorated_sp_session.selfie_required? %>
+ <%= c.with_item(heading: t('doc_auth.instructions.bullet1_with_selfie')) do %>
+
<%= t('doc_auth.instructions.text1_with_selfie') %>
+ <% end %>
+ <% else %>
<%= c.with_item(heading: t('doc_auth.instructions.bullet1')) do %>
<%= t('doc_auth.instructions.text1') %>
<% end %>
- <%= c.with_item(heading: t('doc_auth.instructions.bullet2')) do %>
-
<%= t('doc_auth.instructions.text2') %>
- <% end %>
- <%= c.with_item(heading: t('doc_auth.instructions.bullet3')) do %>
-
- <% t('doc_auth.instructions.text3_html').each do |bullet_item| %>
- - <%= bullet_item %>
- <% end %>
-
- <%= new_tab_link_to(
- t('idv.troubleshooting.options.learn_more_address_verification_options'),
- help_center_redirect_path(
- category: 'verify-your-identity',
- article: 'phone-number',
- flow: :idv,
- step: :welcome,
- location: 'you_will_need',
- ),
- ) %>
- <% end %>
<% end %>
-
- <%= simple_form_for :doc_auth,
- url: url_for,
- method: 'put',
- html: { autocomplete: 'off', class: 'margin-y-5 js-consent-continue-form' } do |f| %>
- <%= f.submit t('doc_auth.buttons.continue') %>
+ <%= c.with_item(heading: t('doc_auth.instructions.bullet2')) do %>
+
<%= t('doc_auth.instructions.text2') %>
<% end %>
+ <%= c.with_item(heading: t('doc_auth.instructions.bullet3')) do %>
+
<%= t('doc_auth.instructions.text3') %>
+ <% end %>
+ <%= c.with_item(heading: t('doc_auth.instructions.bullet4', app_name: APP_NAME)) do %>
+
<%= t('doc_auth.instructions.text4') %>
+ <% end %>
+ <% end %>
+<%= simple_form_for(
+ :doc_auth,
+ url: url_for,
+ method: 'put',
+ html: { autocomplete: 'off', class: 'margin-top-2 margin-bottom-5 js-consent-continue-form' },
+ ) do |f| %>
- <%= render(
- 'shared/troubleshooting_options',
- heading_tag: :h3,
- heading: t('idv.troubleshooting.headings.missing_required_items'),
- options: [
- {
- url: help_center_redirect_path(
- category: 'verify-your-identity',
- article: 'accepted-state-issued-identification',
- flow: :idv,
- step: :welcome,
- location: 'missing_items',
- ),
- text: t('idv.troubleshooting.options.supported_documents'),
- new_tab: true,
- },
- {
- url: help_center_redirect_path(
- category: 'verify-your-identity',
- article: 'phone-number',
- flow: :idv,
- step: :welcome,
- location: 'missing_items',
- ),
- text: t('idv.troubleshooting.options.learn_more_address_verification_options'),
- new_tab: true,
- },
- decorated_sp_session.sp_name && {
- url: return_to_sp_failure_to_proof_url(step: 'welcome', location: 'missing_items'),
- text: t('idv.troubleshooting.options.get_help_at_sp', sp_name: decorated_sp_session.sp_name),
- new_tab: true,
- },
- ].select(&:present?),
- ) %>
-
-
<%= t('doc_auth.instructions.privacy') %>
-
- <%= t('doc_auth.info.privacy', app_name: APP_NAME) %>
-
-
- <%= new_tab_link_to(
- t('doc_auth.instructions.learn_more'),
- policy_redirect_url(flow: :idv, step: :welcome, location: :footer),
+
+ <%= render(
+ SpinnerButtonComponent.new(
+ type: :submit,
+ big: true,
+ wide: true,
+ spin_on_click: false,
+ ).with_content(t('doc_auth.buttons.continue')),
) %>
-
-
+
+<% end %>
<%= render 'shared/cancel', link: idv_cancel_path(step: 'welcome') %>
<% end %>
-
<%= javascript_packs_tag_once('document-capture-welcome') %>
diff --git a/app/views/test/oidc_test/index.html.erb b/app/views/test/oidc_test/index.html.erb
new file mode 100644
index 00000000000..8b15eadb9e7
--- /dev/null
+++ b/app/views/test/oidc_test/index.html.erb
@@ -0,0 +1,7 @@
+<% self.title = 'OIDC Test Controller' %>
+
+
OIDC Test Controller
+
+<%= link_to 'Sign in with Biometric', @start_url_selfie, class: 'sign-in-bttn' %>
+<%= link_to 'Sign in with IAL2', @start_url_ial2, class: 'sign-in-bttn' %>
+<%= link_to 'Sign in with IAL1', @start_url_ial1, class: 'sign-in-bttn' %>
diff --git a/app/views/users/auth_app/edit.html.erb b/app/views/users/auth_app/edit.html.erb
new file mode 100644
index 00000000000..55bbe2409a5
--- /dev/null
+++ b/app/views/users/auth_app/edit.html.erb
@@ -0,0 +1,40 @@
+<% self.title = t('two_factor_authentication.auth_app.edit_heading') %>
+
+<%= render PageHeadingComponent.new.with_content(t('two_factor_authentication.auth_app.edit_heading')) %>
+
+<%= simple_form_for(
+ @form,
+ as: :form,
+ method: :put,
+ html: { autocomplete: 'off' },
+ url: auth_app_path(id: @form.configuration.id),
+ ) do |f| %>
+ <%= render ValidatedFieldComponent.new(
+ form: f,
+ name: :name,
+ label: t('two_factor_authentication.auth_app.nickname'),
+ ) %>
+
+ <%= f.submit(
+ t('two_factor_authentication.auth_app.change_nickname'),
+ class: 'display-block margin-top-5',
+ ) %>
+<% end %>
+
+<%= render ButtonComponent.new(
+ action: ->(**tag_options, &block) do
+ button_to(
+ auth_app_path(id: @form.configuration.id),
+ form: { aria: { label: t('two_factor_authentication.auth_app.delete') } },
+ **tag_options,
+ &block
+ )
+ end,
+ method: :delete,
+ big: true,
+ wide: true,
+ danger: true,
+ class: 'display-block margin-top-2',
+ ).with_content(t('two_factor_authentication.auth_app.delete')) %>
+
+<%= render 'shared/cancel', link: account_path %>
diff --git a/config/locales/account_reset/en.yml b/config/locales/account_reset/en.yml
index 7e44407eb36..5e2bc1bf198 100644
--- a/config/locales/account_reset/en.yml
+++ b/config/locales/account_reset/en.yml
@@ -37,10 +37,11 @@ en:
%{interval}, you will receive an email with
instructions to complete the deletion.
recovery_options:
- check_webauthn_platform: Use the same device you first set up face or touch unlock with
- check_webauthn_platform_info: If you set up face or touch unlock when you
- created your account make sure to use the same device you created your
- %{app_name} account on.
+ check_saved_credential: See if you have a saved credential
+ check_webauthn_platform_info: If you set up face or touch unlock, you may have
+ saved your credentials to a password manager, like iCloud Keychain or
+ Google Password Manager. Try using face or touch unlock on a browser
+ using that password manager.
header: Are you sure you want to delete your account?
help_text: If you’re locked out and still need access to %{app_name}, try these
steps instead.
@@ -48,6 +49,8 @@ en:
“remember device” option.
try_method_again: Try your authentication method again
use_device: Use another device
+ use_same_device: Otherwise, try using the same device where you set up face or
+ touch unlock.
request:
are_you_sure: Are you sure you don’t have access to any of your authentication methods?
delete_account: Delete your account
diff --git a/config/locales/account_reset/es.yml b/config/locales/account_reset/es.yml
index 6b31344231c..80b4214f4a3 100644
--- a/config/locales/account_reset/es.yml
+++ b/config/locales/account_reset/es.yml
@@ -38,11 +38,12 @@ es:
%{interval}, recibirá un correo electrónico con
instrucciones para completar la eliminación.
recovery_options:
- check_webauthn_platform: Utilice el mismo dispositivo con el que configuró el
- desbloqueo facial o táctil por primera vez.
- check_webauthn_platform_info: Si configuró el desbloqueo facial o táctil cuando
- creó su cuenta, asegúrese de utilizar el mismo dispositivo con el que
- creó su cuenta de %{app_name}.
+ check_saved_credential: Verifica si tienes una credencial almacenada
+ check_webauthn_platform_info: Si has habilitado el desbloqueo facial o táctil,
+ es probable que hayas guardado tus credenciales en una herramienta de
+ gestión de contraseñas, como iCloud Keychain o Google Password Manager.
+ Intenta realizar el desbloqueo facial o táctil en un navegador que
+ utilice ese gestor de contraseñas.
header: '¿Seguro que desea eliminar su cuenta?'
help_text: Si sigue sin poder acceder a %{app_name}, pruebe con estos pasos en
su lugar.
@@ -50,6 +51,8 @@ es:
opción “Recordar dispositivo”.
try_method_again: Pruebe nuevamente su método de autenticación.
use_device: Utilice otro dispositivo.
+ use_same_device: De lo contrario, inténtalo con el mismo dispositivo en el que
+ configuraste el desbloqueo facial o táctil.
request:
are_you_sure: '¿Estás seguro de que no tienes acceso a ninguno de tus métodos de
seguridad?'
diff --git a/config/locales/account_reset/fr.yml b/config/locales/account_reset/fr.yml
index 49afdbe4c53..90a29cbe5e0 100644
--- a/config/locales/account_reset/fr.yml
+++ b/config/locales/account_reset/fr.yml
@@ -38,12 +38,13 @@ fr:
Dans
%{interval}, vous recevrez un e-mail avec des
instructions pour terminer la suppression.
recovery_options:
- check_webauthn_platform: Utilisez le même appareil que celui avec lequel vous
- avez configuré le déverrouillage facial ou tactile
+ check_saved_credential: Vérifiez si vous avez des informations d’identification sauvegardées
check_webauthn_platform_info: Si vous avez configuré le déverrouillage facial ou
- tactile lorsque vous avez créé votre compte, assurez-vous d’utiliser le
- même appareil que celui avec lequel vous avez créé votre compte
- %{app_name}.
+ tactile, vous avez peut-être sauvegardé vos informations
+ d’identification dans un gestionnaire de mots de passe, tel que iCloud
+ Keychain ou Google Password Manager. Essayez d’utiliser le
+ déverrouillage facial ou tactile sur un navigateur utilisant ce
+ gestionnaire de mots de passe.
header: Êtes-vous sûr de vouloir supprimer votre compte?
help_text: Si vous êtes bloqué et que vous avez toujours besoin d’accéder à
%{app_name}, essayez plutôt ces étapes.
@@ -51,6 +52,8 @@ fr:
sélectionné l’option « mémoriser l’appareil ».
try_method_again: Essayez à nouveau votre méthode d’authentification
use_device: Utilisez un autre appareil
+ use_same_device: Sinon, essayez d’utiliser le même appareil où vous avez
+ configuré le déverrouillage facial ou tactile.
request:
are_you_sure: Êtes-vous sûr de n’avoir accès à aucune de vos méthodes de sécurité?
delete_account: Supprimer votre compte
diff --git a/config/locales/countries/en.yml b/config/locales/countries/en.yml
index d7660653bdc..c00745ccbba 100644
--- a/config/locales/countries/en.yml
+++ b/config/locales/countries/en.yml
@@ -24,7 +24,6 @@ en:
bh: Bahrain
bi: Burundi
bj: Benin
- bl: Berundi
bm: Bermuda
bn: Brunei
bo: Bolivia
diff --git a/config/locales/countries/es.yml b/config/locales/countries/es.yml
index df70433b973..e01754156fe 100644
--- a/config/locales/countries/es.yml
+++ b/config/locales/countries/es.yml
@@ -24,7 +24,6 @@ es:
bh: Bahrain
bi: Burundi
bj: Benin
- bl: Berundi
bm: Bermuda
bn: Brunei
bo: Bolivia
diff --git a/config/locales/countries/fr.yml b/config/locales/countries/fr.yml
index c6cfcc55912..b7b70b44fce 100644
--- a/config/locales/countries/fr.yml
+++ b/config/locales/countries/fr.yml
@@ -24,7 +24,6 @@ fr:
bh: Bahrain
bi: Burundi
bj: Benin
- bl: Berundi
bm: Bermuda
bn: Brunei
bo: Bolivie
diff --git a/config/locales/doc_auth/en.yml b/config/locales/doc_auth/en.yml
index 3bc08d364c2..40b0f6a3a9a 100644
--- a/config/locales/doc_auth/en.yml
+++ b/config/locales/doc_auth/en.yml
@@ -156,7 +156,6 @@ en:
document_capture_subheader_selfie: Photo of yourself
document_capture_with_selfie: Add photos of your ID and a photo of yourself
front: Front of your driver’s license or state ID
- getting_started: Let’s verify your identity for %{sp_name}
how_to_verify: Choose how you want to verify your identity
hybrid_handoff: How would you like to add your ID?
interstitial: We are processing your images
@@ -173,7 +172,7 @@ en:
verify_at_post_office: Verify your identity at a Post Office
verify_identity: Verify your identity
verify_online: Verify your identity online
- welcome: Get started verifying your identity
+ welcome: Let’s verify your identity for %{sp_name}
hybrid_flow_warning:
explanation_html: You’re using
%{app_name} to verify your
identity for access to
%{service_provider_name} and its
@@ -195,6 +194,9 @@ en:
exit:
with_sp: Exit %{app_name} and return to %{sp_name}
without_sp: Exit identity verification and go to your account page
+ getting_started_html: '%{sp_name} needs to make sure you are you — not someone
+ pretending to be you. %{link_html}'
+ getting_started_learn_more: Learn more about verifying your identity
how_to_verify: You have the option to verify your identity online, or in person
at a participating Post Office
how_to_verify_troubleshooting_options_header: Want to learn more about how to verify your identity?
@@ -213,9 +215,6 @@ en:
link_sent_complete_no_polling: When you are done, click Continue here to finish verifying your identity.
link_sent_complete_polling: The next step will load automatically.
no_ssn: You must have a Social Security number to finish verifying your identity.
- privacy: '%{app_name} is a secure, government website that adheres to the
- highest standards in data protection. We use your data to verify your
- identity.'
review_examples_of_photos: Review examples of how to take clear photos of your ID.
secure_account: We’ll encrypt your account with your password. Encryption means
your data is protected and only you will be able to access or change
@@ -244,30 +243,30 @@ en:
verify_online_instruction: You’ll take photos of your ID to verify your identity
fully online. Most users finish this process in one sitting.
verify_online_link_text: Learn more about verifying online
- welcome: '%{sp_name} needs to make sure you are you — not someone pretending to
- be you.'
you_entered: 'You entered:'
instructions:
- bullet1: State‑issued ID
- bullet2: Social Security number
- bullet3: Phone number OR home address
+ bullet1: Take photos of your ID
+ bullet1_with_selfie: Take photos of yourself and your ID
+ bullet2: Enter your Social Security number
+ bullet3: Match to your phone number
+ bullet4: Re-enter your %{app_name} password
consent: By checking this box, you are letting %{app_name} ask for, use, keep,
and share your personal information. We will use it to verify your
identity.
+ getting_started: 'You’ll need to:'
learn_more: Learn more about our privacy and security measures
- privacy: Our privacy and security standards
switch_back: Switch back to your computer to finish verifying your identity.
switch_back_image: Arrow pointing from phone to computer
test_ssn: In the test environment only SSNs that begin with “900-” or “666-” are
considered valid. Do not enter real PII in this field.
- text1: Your ID cannot be expired.
- text2: You will not need the card with you.
- text3_html:
- - '
Verify by phone: We’ll call or text your phone
- number. This takes a few minutes.'
- - '
Verify by mail: We’ll mail a letter to your home
- address. This takes
5 to 10 days.'
- welcome: 'You will need your:'
+ text1: Use your driver’s license or state ID card. Other forms of ID are not
+ accepted.
+ text1_with_selfie: Take a photo of yourself and take photos of your driver’s
+ license or state ID card. Other forms of ID are not accepted.
+ text2: You will not need your physical SSN card.
+ text3: Your phone number matches you to your personal information. After you
+ match, we’ll send you a code.
+ text4: Your password saves and encrypts your personal information.
tips:
document_capture_hint: Must be a JPG or PNG
document_capture_id_text1: Use a dark background
diff --git a/config/locales/doc_auth/es.yml b/config/locales/doc_auth/es.yml
index 9aa65026e33..ce537f99f04 100644
--- a/config/locales/doc_auth/es.yml
+++ b/config/locales/doc_auth/es.yml
@@ -187,7 +187,6 @@ es:
document_capture_subheader_selfie: Foto suya
document_capture_with_selfie: Incluir fotos de su identificación y una foto suya
front: Anverso de su licencia de conducir o identificación estatal
- getting_started: Vamos a verificar su identidad para %{sp_name}
how_to_verify: Elija cómo quiere verificar su identidad
hybrid_handoff: '¿Cómo desea añadir su documento de identidad?'
interstitial: Estamos procesando sus imágenes
@@ -204,7 +203,7 @@ es:
verify_at_post_office: Verifique su identidad en una oficina de correos
verify_identity: Verifique su identidad
verify_online: Verifique su identidad en línea
- welcome: Comience a verificar su identidad
+ welcome: Vamos a verificar tu identidad para %{sp_name}
hybrid_flow_warning:
explanation_html: Usted está utilizando
%{app_name} para
verificar su identidad y acceder a
@@ -229,6 +228,9 @@ es:
exit:
with_sp: Salir de %{app_name} y volver a %{sp_name}
without_sp: Salir de la verificación de identidad e ir a la página de su cuenta
+ getting_started_html: '%{sp_name} necesita asegurarse de que es usted y no es
+ alguien que se hace pasar por usted. %{link_html}'
+ getting_started_learn_more: Obtén más información sobre la verificación de tu identidad
how_to_verify: Tiene la opción de verificar su identidad en línea o en persona
en una oficina de correos participante.
how_to_verify_troubleshooting_options_header: ¿Quiere saber más sobre cómo verificar su identidad?
@@ -251,9 +253,6 @@ es:
que verifiques tu identidad a través de tu teléfono.
no_ssn: Debe tener un número de Seguro Social para finalizar la verificación de
su identidad.
- privacy: '%{app_name} es un sitio web gubernamental seguro que cumple con las
- normas más estrictas de protección de datos. Utilizamos sus datos para
- verificar su identidad.'
review_examples_of_photos: Revisa ejemplos de cómo hacer fotos nítidas de tu documento de identidad.
secure_account: Vamos a encriptar su cuenta con su contraseña. La encriptación
significa que sus datos están protegidos y solo usted podrá acceder o
@@ -286,31 +285,32 @@ es:
verify_online_instruction: Tomará fotografías de tu identificación para
verificar tu identidad completamente en línea.
verify_online_link_text: Obtenga más información sobre la verificación en línea
- welcome: '%{sp_name} necesita asegurarse de que es usted y no es alguien que se
- hace pasar por usted.'
you_entered: 'Ud. entregó:'
instructions:
- bullet1: Documento de identidad emitido por el estado.
- bullet2: Número de seguro social
- bullet3: Número de teléfono O domicilio
+ bullet1: Tomar fotos de tu identificación oficial
+ bullet1_with_selfie: Tomar fotos tuyas y de tu identificación oficial
+ bullet2: Ingresar tu Número de Seguro Social
+ bullet3: Confirmar tu número de teléfono
+ bullet4: Ingresar de nuevo tu contraseña de %{app_name}
consent: Al marcar esta casilla, usted permite que %{app_name} solicite,
utilice, conserve y comparta su información personal. Los utilizamos
para verificar su identidad.
+ getting_started: 'Necesitarás:'
learn_more: Obtenga más información sobre nuestras medidas de privacidad y seguridad
- privacy: Nuestras normas de privacidad y seguridad
switch_back: Regrese a su computadora para continuar con la verificación de su
identidad.
switch_back_image: Flecha que apunta del teléfono a la computadora
test_ssn: En el entorno de prueba solo los SSN que comienzan con “900-” o “666-”
se consideran válidos. No ingrese PII real en este campo.
- text1: Su documento de identidad no puede estar caducado.
- text2: No necesitará la tarjeta con usted.
- text3_html:
- - '
Verificar por teléfono: Le llamaremos o enviaremos
- un mensaje de texto a su número de teléfono. Esto lleva unos minutos'
- - '
Verificar por correo: Le enviaremos una carta a su
- domicilio. Esto tarda entre
5 y 10 días.'
- welcome: 'Necesitará su:'
+ text1: Utiliza tu licencia de conducir o identificación oficial estatal. No
+ aceptamos ningún otro tipo de identificación.
+ text1_with_selfie: Tómate una foto y toma fotografías de tu licencia de conducir
+ o identificación oficial estatal. No aceptamos ningún otro tipo de
+ identificación.
+ text2: No necesitarás tu tarjeta física del seguro social.
+ text3: Tu número de teléfono te vincula con tu información personal. Luego de
+ confirmar tu información, te enviaremos un código.
+ text4: Tu contraseña permitirá guardar y encriptar tu información personal.
tips:
document_capture_hint: Debe ser un JPG o PNG
document_capture_id_text1: Use un fondo oscuro
diff --git a/config/locales/doc_auth/fr.yml b/config/locales/doc_auth/fr.yml
index f4111c0fe0b..1c12cc52be6 100644
--- a/config/locales/doc_auth/fr.yml
+++ b/config/locales/doc_auth/fr.yml
@@ -195,7 +195,6 @@ fr:
document_capture_subheader_selfie: Photo de vous-même
document_capture_with_selfie: Ajoutez des photos de votre pièce d’identité et une photo de vous-même
front: Recto de votre permis de conduire ou de votre carte d’identité de l’État
- getting_started: Vérifions votre identité pour %{sp_name}
how_to_verify: Choisissez la manière dont vous souhaitez confirmer votre identité
hybrid_handoff: Comment voulez-vous ajouter votre identifiant ?
interstitial: Nous traitons vos images
@@ -212,7 +211,7 @@ fr:
verify_at_post_office: Confirmez votre identité un bureau de poste
verify_identity: Vérifier votre identité
verify_online: Confirmez votre identité en ligne
- welcome: Commencez à vérifier votre identité
+ welcome: Vérifions votre identité pour %{sp_name}
hybrid_flow_warning:
explanation_html: Vous utilisez
%{app_name} pour vérifier votre
identité et accéder à
%{service_provider_name} et à ses
@@ -236,6 +235,9 @@ fr:
exit:
with_sp: Quittez %{app_name} et retournez à %{sp_name}
without_sp: Quittez la vérification d’identité et accédez à la page de votre compte
+ getting_started_html: '%{sp_name} doit s’assurer que vous êtes bien vous, et non
+ quelqu’un qui se fait passer pour vous. %{link_html}'
+ getting_started_learn_more: Pour plus d’informations sur la vérification de votre identité
how_to_verify: Vous avez la possibilité de confirmer votre identité en ligne ou
en personne dans un bureau de poste participant.
how_to_verify_troubleshooting_options_header: Vous voulez en savoir plus sur la façon de vérifier votre identité?
@@ -260,9 +262,6 @@ fr:
téléphone.
no_ssn: Vous devez avoir un numéro de sécurité sociale pour terminer la
vérification de votre identité.
- privacy: '%{app_name} est un site gouvernemental sécurisé qui respecte les
- normes les plus strictes en matière de protection des données. Nous
- utilisons vos données pour vérifier votre identité.'
review_examples_of_photos: Examinez des exemples de photos claires de votre pièce d’identité.
secure_account: Nous chiffrerons votre compte avec votre mot de passe. Le
chiffrage signifie que vos données sont protégées et que vous êtes le/la
@@ -296,33 +295,32 @@ fr:
verify_online_instruction: Vous prendrez des photos de votre pièce d’identité
pour confirmer votre identité entièrement en ligne.
verify_online_link_text: En savoir plus sur la confirmation en ligne
- welcome: '%{sp_name} doit s’assurer que vous êtes bien vous, et non quelqu’un
- qui se fait passer pour vous.'
you_entered: 'Tu as soumis:'
instructions:
- bullet1: Carte d’identité délivrée par l’État
- bullet2: Numéro de sécurité sociale
- bullet3: Numéro de téléphone OU adresse du domicile
+ bullet1: Prendre des photos de votre pièce d’identité
+ bullet1_with_selfie: Prenez des photos de vous et de vos pièces d’identité
+ bullet2: Saisissez votre numéro de sécurité sociale
+ bullet3: Faites correspondre votre numéro de téléphone
+ bullet4: Saisissez à nouveau votre mot de passe %{app_name}
consent: En cochant cette case, vous autorisez %{app_name} à demander, utiliser,
conserver et partager vos renseignements personnels. Nous les utilisons
pour vérifier votre identité.
+ getting_started: 'Vous aurez besoin de :'
learn_more: En savoir plus sur nos mesures de confidentialité et de sécurité
- privacy: Nos normes de confidentialité et de sécurité
switch_back: Retournez sur votre ordinateur pour continuer à vérifier votre identité.
switch_back_image: Flèche pointant du téléphone vers l’ordinateur
test_ssn: Dans l’environnement de test seuls les SSN commençant par “900-” ou
“900-” sont considérés comme valides. N’entrez pas de vrais PII dans ce
champ.
- text1: Votre carte d’identité ne doit pas être expirée.
- text2: Vous n’aurez pas besoin de la carte sur vous.
- text3_html:
- - '
Vérification par téléphone : Nous appellerons ou
- enverrons un SMS à votre numéro de téléphone. Cela prend quelques
- minutes.'
- - '
Vérification par courrier : Nous vous enverrons une
- lettre à votre adresse personnelle. Cela prend
5 à 10
- jours.'
- welcome: 'Vous aurez besoin de votre:'
+ text1: Utilisez votre permis de conduire ou votre carte d’identité nationale.
+ Les autres pièces d’identité ne sont pas acceptées.
+ text1_with_selfie: Prenez une photo de vous et une de votre permis de conduire
+ ou carte d’identité nationale. Les autres pièces d’identité ne sont pas
+ acceptées.
+ text2: Vous n’aurez pas besoin de votre carte de sécurité sociale.
+ text3: Votre numéro de téléphone correspond à vos informations personnelles. Une
+ fois la correspondance établie, nous vous enverrons un code.
+ text4: Votre mot de passe enregistre et crypte vos informations personnelles.
tips:
document_capture_hint: Doit être un JPG ou PNG
document_capture_id_text1: Utilisez un fond sombre
diff --git a/config/locales/errors/en.yml b/config/locales/errors/en.yml
index 677a871510f..e3861954239 100644
--- a/config/locales/errors/en.yml
+++ b/config/locales/errors/en.yml
@@ -124,10 +124,6 @@ en:
account_setup_error: We were unable to add face or touch unlock. Please try again or %{link}.
already_registered: Face or touch unlock is already registered on this device.
Please try adding another authentication method.
- attestation_error: Sorry, but your platform authenticator doesn’t appear to be a
- FIDO platform authenticator. Please make sure your device is listed at
- https://fidoalliance.org/certification/fido-certified-products/ and if
- you believe this is our error, please contact at %{link}.
choose_another_method: choose another authentication method
general_error: We were unable to add face or touch unlock. Please try again or
choose another method.
@@ -138,10 +134,6 @@ en:
webauthn_setup:
additional_methods_link: choose another authentication method
already_registered: Security key already registered. Please try a different security key.
- attestation_error: Sorry, but your security key doesn’t appear to be a FIDO
- security key. Please make sure your device is listed at
- https://fidoalliance.org/certification/fido-certified-products/ and if
- you believe this is our error, please contact at %{link}.
general_error_html: We were unable to add the security key. Please try again or %{link_html}.
not_supported: Your browser doesn’t support security keys. Use the latest
version of Google Chrome, Microsoft Edge, Mozilla Firefox or Safari to
diff --git a/config/locales/errors/es.yml b/config/locales/errors/es.yml
index de0b30cc8f4..be20e24d5d1 100644
--- a/config/locales/errors/es.yml
+++ b/config/locales/errors/es.yml
@@ -134,11 +134,6 @@ es:
already_registered: Ya está registrado el desbloqueo con la cara o con la huella
digital en este dispositivo. Trate de agregar otro método de
autenticación.
- attestation_error: Lo sentimos, pero su desbloqueo facial o táctil no parece
- funcionar. Por favor, asegúrese de que su dispositivo está registrado en
- https://fidoalliance.org/certification/fido-certified-products/ y si
- cree que se trata de un error nuestro, póngase en contacto con nosotros
- en %{link}.
choose_another_method: elija otro método de autenticación
general_error: No pudimos agregar el desbloqueo con la cara o con la huella
digital. Inténtelo de nuevo o elija otro método de autenticación.
@@ -151,12 +146,6 @@ es:
additional_methods_link: elija otro método de autenticación
already_registered: Clave de seguridad ya registrada. Por favor, intente una
clave de seguridad diferente.
- attestation_error: Lo sentimos, pero su clave de seguridad no parece ser una
- clave de seguridad FIDO. Por favor, asegúrese de que su dispositivo
- aparezca en
- https://fidoalliance.org/certification/fido-certified-products/. Si
- considera que se trata de un error nuestro, póngase en contacto con
- %{link}.
general_error_html: No hemos podido añadir la clave de seguridad. Inténtelo de
nuevo o %{link_html}.
not_supported: Tu navegador no es compatible con llaves de seguridad. Utiliza la
diff --git a/config/locales/errors/fr.yml b/config/locales/errors/fr.yml
index e7274032aaa..fb52095e419 100644
--- a/config/locales/errors/fr.yml
+++ b/config/locales/errors/fr.yml
@@ -144,11 +144,6 @@ fr:
already_registered: Le déverrouillage facial ou le déverrouillage tactile est
déjà enregistré sur cet appareil. Veuillez essayer d’ajouter une autre
méthode d’authentification.
- attestation_error: Désolé, mais votre déverrouillage facial ou tactile ne semble
- pas fonctionner. Veuillez vous assurer que votre appareil est répertorié
- sur https://fidoalliance.org/certification/fido-certified-products/ et
- si vous pensez qu’il s’agit d’une erreur de notre part, veuillez nous
- contacter à %{link}.
choose_another_method: choisir une autre méthode d’authentification
general_error: Nous n’avons pas pu ajouter le déverrouillage facial ni le
déverrouillage tactile. Veuillez réessayer ou choisir une autre méthode
@@ -163,11 +158,6 @@ fr:
additional_methods_link: choisir une autre méthode d’authentification
already_registered: Clé de sécurité déjà enregistrée. Veuillez essayer une clé
de sécurité différente.
- attestation_error: Désolé, votre clé de sécurité ne semble pas être une clé de
- sécurité FIDO. Veuillez vérifier que votre appareil est répertorié sur
- https://fidoalliance.org/certification/fido-certified-products/. Si vous
- pensez qu’il s’agit d’une erreur de notre part, veuillez contacter
- %{link}.
general_error_html: Nous n’avons pas pu ajouter la clé de sécurité. Veuillez
réessayer ou %{link_html}.
not_supported: Votre navigateur ne prend pas en charge les clés de sécurité.
diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml
index 8f43f13463e..2b70b1cba1c 100644
--- a/config/locales/idv/en.yml
+++ b/config/locales/idv/en.yml
@@ -299,13 +299,11 @@ en:
unavailable: 'We are working to resolve an error'
troubleshooting:
headings:
- missing_required_items: Are you missing one of these items?
need_assistance: 'Need immediate assistance? Here’s how to get help:'
options:
contact_support: Contact %{app_name} Support
doc_capture_tips: Tips for taking clear photos of your ID
get_help_at_sp: Get help at %{sp_name}
- learn_more_address_verification_options: Learn more about verifying by phone or mail
learn_more_verify_by_mail: Learn more about verifying your address by mail
learn_more_verify_by_phone: Learn more about what phone number to use
learn_more_verify_by_phone_in_person: Learn more about verifying your phone number
diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml
index 9ef6880606f..9144315a446 100644
--- a/config/locales/idv/es.yml
+++ b/config/locales/idv/es.yml
@@ -317,13 +317,11 @@ es:
unavailable: Estamos trabajando para resolver un error
troubleshooting:
headings:
- missing_required_items: '¿Le falta alguno de estos puntos?'
need_assistance: '¿Necesita ayuda inmediata? Así es como puede obtener ayuda:'
options:
contact_support: Póngase en contacto con el servicio de asistencia de %{app_name}
doc_capture_tips: Sugerencias para obtener fotos nítidas de tu documento de identidad
get_help_at_sp: Obtenga ayuda en %{sp_name}
- learn_more_address_verification_options: Obtenga más información sobre la verificación por teléfono o por correo
learn_more_verify_by_mail: Obtenga más información sobre la verificación de su
dirección por correo
learn_more_verify_by_phone: Más información sobre qué número de teléfono usar
diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml
index f7bc8a88251..8966a537d34 100644
--- a/config/locales/idv/fr.yml
+++ b/config/locales/idv/fr.yml
@@ -328,14 +328,12 @@ fr:
unavailable: Nous travaillons à la résolution d’une erreur
troubleshooting:
headings:
- missing_required_items: Est-ce qu’il vous manque un de ces éléments?
need_assistance: 'Avez-vous besoin d’une assistance immédiate? Voici comment
obtenir de l’aide:'
options:
contact_support: Contacter le service d’assistance de %{app_name}
doc_capture_tips: Conseils pour prendre des photos claires de votre pièce d’identité
get_help_at_sp: Demandez de l’aide à %{sp_name}
- learn_more_address_verification_options: En savoir plus sur la vérification par téléphone ou par courrier
learn_more_verify_by_mail: En savoir plus sur la vérification de votre adresse par courrier
learn_more_verify_by_phone: Apprenez-en plus sur quel numéro de téléphone utiliser
learn_more_verify_by_phone_in_person: En savoir plus sur la vérification de votre numéro de téléphone
diff --git a/config/locales/two_factor_authentication/en.yml b/config/locales/two_factor_authentication/en.yml
index 0dd6bd01859..e55ae0021a5 100644
--- a/config/locales/two_factor_authentication/en.yml
+++ b/config/locales/two_factor_authentication/en.yml
@@ -20,6 +20,14 @@ en:
attempt_remaining_warning_html:
one: You have
%{count} attempt remaining.
other: You have
%{count} attempts remaining.
+ auth_app:
+ change_nickname: Change nickname
+ delete: Delete this device
+ deleted: Successfully deleted an authentication app method
+ edit_heading: Manage your authentication app settings
+ manage_accessible_label: Manage authentication app
+ nickname: Nickname
+ renamed: Successfully renamed your authentication app method
backup_code_header_text: Enter your backup code
backup_code_prompt: You can use this backup code once. After you submit it,
you’ll need to use a new backup code next time.
diff --git a/config/locales/two_factor_authentication/es.yml b/config/locales/two_factor_authentication/es.yml
index 01ef2bc2814..5f094a6efae 100644
--- a/config/locales/two_factor_authentication/es.yml
+++ b/config/locales/two_factor_authentication/es.yml
@@ -20,6 +20,15 @@ es:
attempt_remaining_warning_html:
one: Le quedan
%{count} intento.
other: Le quedan
%{count} intentos.
+ auth_app:
+ change_nickname: Cambiar apodo
+ delete: Eliminar este dispositivo
+ deleted: Se ha eliminado correctamente un método de aplicación de autenticación.
+ edit_heading: Gestionar la configuración de su aplicación de autenticación
+ manage_accessible_label: Gestionar la aplicación de autenticación
+ nickname: Apodo
+ renamed: Se ha cambiado correctamente el nombre de su método de aplicación de
+ autenticación.
backup_code_header_text: Ingrese su código de respaldo
backup_code_prompt: Puede utilizar este código de respaldo una vez. Tendrá que
usar un nuevo código de respaldo la próxima vez después de que lo envíe.
diff --git a/config/locales/two_factor_authentication/fr.yml b/config/locales/two_factor_authentication/fr.yml
index bca8d4bb545..01465df3f19 100644
--- a/config/locales/two_factor_authentication/fr.yml
+++ b/config/locales/two_factor_authentication/fr.yml
@@ -22,6 +22,14 @@ fr:
attempt_remaining_warning_html:
one: Il vous reste
%{count} tentative.
other: Il vous reste
%{count} tentatives.
+ auth_app:
+ change_nickname: Changer de pseudo
+ delete: Supprimer cet appareil
+ deleted: Suppression réussie d’une méthode d’application d’authentification
+ edit_heading: Gérer les paramètres de votre application d’authentification
+ manage_accessible_label: Gérer l’application d’authentification
+ nickname: Pseudo
+ renamed: Votre méthode d’application d’authentification a été renommée avec succès
backup_code_header_text: Entrez votre code de sauvegarde
backup_code_prompt: Vous pouvez utiliser ce code de sauvegarde une seule fois.
Après l’avoir envoyé, vous devrez utiliser un nouveau code de sauvegarde
diff --git a/config/routes.rb b/config/routes.rb
index 78ab0a170d6..e806fcbeaa0 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -23,6 +23,8 @@
namespace :two_factor_authentication do
put '/webauthn/:id' => 'webauthn#update', as: :webauthn
delete '/webauthn/:id' => 'webauthn#destroy', as: nil
+ put '/auth_app/:id' => 'auth_app#update', as: :auth_app
+ delete '/auth_app/:id' => 'auth_app#destroy', as: nil
end
end
end
@@ -147,6 +149,13 @@
get '/saml/decode_assertion' => 'saml_test#start'
post '/saml/decode_assertion' => 'saml_test#decode_response'
post '/saml/decode_slo_request' => 'saml_test#decode_slo_request'
+
+ get '/oidc/login' => 'oidc_test#index'
+ get '/oidc' => 'oidc_test#start'
+ get '/oidc/auth_request' => 'oidc_test#auth_request'
+ get '/oidc/auth_result' => 'oidc_test#auth_result'
+ get '/oidc/logout' => 'oidc_test#logout'
+
get '/piv_cac_entry' => 'piv_cac_authentication_test_subject#new'
post '/piv_cac_entry' => 'piv_cac_authentication_test_subject#create'
@@ -255,7 +264,9 @@
get '/manage/webauthn/:id' => 'users/webauthn#edit', as: :edit_webauthn
put '/manage/webauthn/:id' => 'users/webauthn#update', as: :webauthn
delete '/manage/webauthn/:id' => 'users/webauthn#destroy', as: nil
-
+ get '/manage/auth_app/:id' => 'users/auth_app#edit', as: :edit_auth_app
+ put '/manage/auth_app/:id' => 'users/auth_app#update', as: :auth_app
+ delete '/manage/auth_app/:id' => 'users/auth_app#destroy', as: nil
get '/account/personal_key' => 'accounts/personal_keys#new', as: :create_new_personal_key
post '/account/personal_key' => 'accounts/personal_keys#create'
diff --git a/config/service_providers.localdev.yml b/config/service_providers.localdev.yml
index a00dcea2759..23a75d0a838 100644
--- a/config/service_providers.localdev.yml
+++ b/config/service_providers.localdev.yml
@@ -454,7 +454,6 @@ development:
agency_id: 1
ial: 2
irs_attempts_api_enabled: true
- push_notification_url: http://localhost:9292/api/push_notifications
redirect_uris:
- 'http://localhost:9292/'
- 'http://localhost:9292/auth/result'
@@ -542,6 +541,18 @@ development:
- 'http://localhost:4000/'
- 'http://localhost:4000/auth/result'
+ 'urn:gov:gsa:openidconnect:sp:test':
+ agency_id: 1
+ ial: 2
+ return_to_sp_url: 'http://localhost:3000'
+ redirect_uris:
+ - 'http://localhost:3000/'
+ - 'http://localhost:3000/test/oidc/auth_result'
+ certs:
+ - 'sp_sinatra_demo'
+ friendly_name: 'Example Test OIDC SP'
+ in_person_proofing_enabled: true
+
# These are fake production service providers needed for the
# ServiceProviderSeeder tests. They are not actually used in production.
#
diff --git a/db/primary_migrate/20240110142935_drop_disposable_domains_table.rb b/db/primary_migrate/20240110142935_drop_disposable_domains_table.rb
new file mode 100644
index 00000000000..5e0a0a33dea
--- /dev/null
+++ b/db/primary_migrate/20240110142935_drop_disposable_domains_table.rb
@@ -0,0 +1,5 @@
+class DropDisposableDomainsTable < ActiveRecord::Migration[7.1]
+ def change
+ drop_table :disposable_domains
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 669b7801e88..8cdcaf3f109 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.1].define(version: 2024_01_10_141229) do
+ActiveRecord::Schema[7.1].define(version: 2024_01_10_142935) do
# These are extensions that must be enabled in order to support this database
enable_extension "citext"
enable_extension "pg_stat_statements"
@@ -95,11 +95,6 @@
t.index ["user_id", "last_used_at"], name: "index_device_user_id_last_used_at"
end
- create_table "disposable_domains", force: :cascade do |t|
- t.citext "name", null: false
- t.index ["name"], name: "index_disposable_domains_on_name", unique: true
- end
-
create_table "disposable_email_domains", force: :cascade do |t|
t.citext "name", null: false
t.index ["name"], name: "index_disposable_email_domains_on_name", unique: true
diff --git a/docs/local-development.md b/docs/local-development.md
index 923578765ca..bc560e8533f 100644
--- a/docs/local-development.md
+++ b/docs/local-development.md
@@ -64,7 +64,9 @@ asked to consent to share their information with the partner before being sent b
To simulate a true end-to-end user experience, you can either...
-- Use the built-in test controller for SAML logins at http://localhost:3000/test/saml/login
+- Use the built-in test controller for SAML logins at http://localhost:3000/test/saml/login or OIDC logins at http://localhost:3000/test/oidc/login
+
+ Note: to update service provider configurations, run the command `rake db:seed` or `make setup`.
- Or, run a sample partner application, which is configured by default to run with your local IdP instance:
- OIDC: https://github.com/18F/identity-oidc-sinatra
- Runs at http://localhost:9292/
diff --git a/lib/idp/constants.rb b/lib/idp/constants.rb
index 03b45648ce5..b12c884dbcd 100644
--- a/lib/idp/constants.rb
+++ b/lib/idp/constants.rb
@@ -165,5 +165,6 @@ module Vendors
MOCK_IDV_APPLICANT_FULL_STATE_ID_JURISDICTION = 'North Dakota'
MOCK_IDV_APPLICANT_FULL_STATE = 'Montana'
MOCK_IDV_APPLICANT_FULL_IDENTITY_DOC_ADDRESS_STATE = 'Virginia'
+ MOCK_IDV_APPLICANT_STATE = 'MT'
end
end
diff --git a/lib/tasks/disposable_domains.rake b/lib/tasks/disposable_email_domains.rake
similarity index 60%
rename from lib/tasks/disposable_domains.rake
rename to lib/tasks/disposable_email_domains.rake
index 4310e90746d..9ecf648f51e 100644
--- a/lib/tasks/disposable_domains.rake
+++ b/lib/tasks/disposable_email_domains.rake
@@ -1,13 +1,13 @@
# rubocop:disable Rails/SkipsModelValidations
require 'csv'
-namespace :disposable_domains do
- task :load, %i[s3_url] => [:environment] do |_task, args|
+namespace :disposable_email_domains do
+ task :load, %i[s3_secrets_path] => [:environment] do |_task, args|
# Need to increase statement timeout since command takes a long time.
ActiveRecord::Base.connection.execute 'SET statement_timeout = 200000'
- file = Identity::Hostdata.secrets_s3.read_file(args[:s3_url])
+ file = Identity::Hostdata.secrets_s3.read_file(args[:s3_secrets_path])
names = file.split("\n")
DisposableEmailDomain.insert_all(names.map { |name| { name: } })
end
end
-# rake "disposable_domains:load['URL_HERE']"
+# rake "disposable_email_domains:load[S3_SECRETS_PATH]"
# rubocop:enable Rails/SkipsModelValidations
diff --git a/scripts/create-deploy-pr b/scripts/create-deploy-pr
index a8623193067..310d4103beb 100755
--- a/scripts/create-deploy-pr
+++ b/scripts/create-deploy-pr
@@ -2,20 +2,43 @@
set -euo pipefail
-ORIGIN=${ORIGIN:-origin}
+GIT_REMOTE=${GIT_REMOTE:-origin}
SOURCE=${SOURCE:-}
DEPLOY_BRANCH=stages/prod
PATCH=${PATCH:-}
DRY_RUN=${DRY_RUN:-0}
-CHANGELOG_FILE=${CHANGELOG_FILE:-.rc-changelog.md}
+CHANGELOG_FILE=${CHANGELOG_FILE:-tmp/.rc-changelog.md}
+FORMAT_CHANGELOG=${FORMAT_CHANGELOG:-}
+GH_REPO=${GH_REPO:-18f/identity-idp}
+STATUS_PROMOTION_LABEL='status - promotion'
+
+function check_gh_configuration {
+ if ! which gh > /dev/null 2>&1; then
+ echo "Github CLI (gh) is not installed. You can install it with: brew install gh"
+ exit 1
+ fi
-function get_last_rc {
- GH_OUTPUT=$(gh release list --exclude-drafts --exclude-pre-releases --limit 1 || true)
- if [ -z "$GH_OUTPUT" ]; then
- echo "Failed to get latest released" >&2
+ if [ "${CI:-}" == "1" ] && [ -z "${GH_TOKEN:-}" ]; then
+ # gh will not work in CI unless GH_TOKEN is explicitly set.
+ echo "You must set the GH_TOKEN environment variable."
+ exit 1
+ fi
+
+ # Verify our git remote aligns with GH configuration
+ GIT_REMOTE_REPO=$(
+ git remote get-url "$GIT_REMOTE" \
+ | sed -E 's#(^https?://github.com/|^git@github.com:|\.git$)##g' \
+ | tr '[:upper:]' '[:lower:]' \
+ )
+
+ if [ "$GIT_REMOTE_REPO" != "$GH_REPO" ]; then
+ echo "\$GH_REPO is set to a different value ($GH_REPO) than the git remote ($GIT_REMOTE - $GIT_REMOTE_REPO) in use."
exit 1
fi
+}
+function get_last_rc {
+ GH_OUTPUT=$(gh release list --exclude-drafts --exclude-pre-releases --limit 1 || true)
LAST_RC=$(echo "$GH_OUTPUT" | grep -E --only-matching 'RC [0-9]+(\.[0-9]+)?' | sed 's/RC //')
if [ -z "$LAST_RC" ]; then
echo 0
@@ -53,18 +76,10 @@ function get_staging_sha {
curl --silent https://idp.staging.login.gov/api/deploy.json | jq -r .git_sha
}
-if [ -z "${CI:-}" ]; then
- echo "This script is meant to be run in a continuous integration environment."
- exit 1
-fi
-
-if [ -z "${GH_TOKEN:-}" ] && [ "$DRY_RUN" == "0" ]; then
- echo "You must set the GH_TOKEN environment variable."
- exit 1
-fi
+check_gh_configuration
RC_BRANCH=stages/rc-$(date +'%Y-%m-%d')
-if git rev-parse "$ORIGIN/$RC_BRANCH" > /dev/null 2>&1; then
+if git rev-parse "$GIT_REMOTE/$RC_BRANCH" > /dev/null 2>&1; then
echo "RC branch $RC_BRANCH already exists. Delete that branch and re-run this workflow to create a PR." >&2
exit 1
fi
@@ -78,7 +93,7 @@ if [ -z "$SOURCE" ]; then
echo "Staging currently running ${SHA}"
else
SHA=$(git rev-parse "$SOURCE" || true)
- if [ -z $SHA ]; then
+ if [ -z "$SHA" ]; then
echo "Invalid source: '$SOURCE'"
exit 17
elif [ "$SOURCE" == "$SHA" ]; then
@@ -88,18 +103,32 @@ else
fi
fi
+mkdir -p "$(dirname "$CHANGELOG_FILE")" || true
+
echo "Building changelog..."
-scripts/changelog_check.rb -s "$SHA" -b "${ORIGIN}/${DEPLOY_BRANCH}" > "$CHANGELOG_FILE"
+scripts/changelog_check.rb -s "$SHA" -b "${GIT_REMOTE}/${DEPLOY_BRANCH}" > "$CHANGELOG_FILE"
+
+if [ "$FORMAT_CHANGELOG" != "" ]; then
+ # Pipe the changelog in as stdin to the hook
+ echo "Executing changelog formatting hook '${FORMAT_CHANGELOG}'..."
+ ORIGINAL_CHANGELOG_FILE="${CHANGELOG_FILE}.orig"
+ mv "$CHANGELOG_FILE" "$ORIGINAL_CHANGELOG_FILE"
+ cat "$ORIGINAL_CHANGELOG_FILE" | sh -c "$FORMAT_CHANGELOG" > "$CHANGELOG_FILE"
+ echo "Diff:"
+ diff --color=auto "$ORIGINAL_CHANGELOG_FILE" "$CHANGELOG_FILE"
+fi
if [[ $DRY_RUN -eq 0 ]]; then
- echo "Pushing $RC_BRANCH to origin..."
- git push $ORIGIN "$SHA:refs/heads/$RC_BRANCH"
+ echo "Pushing $RC_BRANCH to $GIT_REMOTE..."
+ git push "$GIT_REMOTE" "$SHA:refs/heads/$RC_BRANCH"
+
+ gh label create "$STATUS_PROMOTION_LABEL" 2>/dev/null || true
# Create PR
echo "Creating PR..."
gh pr create \
--title "Deploy RC ${NEXT_RC} to Production" \
- --label 'status - promotion' \
+ --label "$STATUS_PROMOTION_LABEL" \
--base "$DEPLOY_BRANCH" \
--head "$RC_BRANCH" \
--body-file "$CHANGELOG_FILE"
@@ -107,6 +136,4 @@ else
echo "Dry run. Not creating PR."
fi
-echo "# Changelog"
-cat "$CHANGELOG_FILE" && rm "$CHANGELOG_FILE"
diff --git a/scripts/create-release b/scripts/create-release
index 92ef2a73e42..f37f33a12d6 100755
--- a/scripts/create-release
+++ b/scripts/create-release
@@ -3,8 +3,10 @@
set -euo pipefail
DEPLOY_BRANCH=stages/prod
-PR_JSON_FILE=${PR_JSON_FILE:-.pr.json}
-CHANGELOG_FILE=${CHANGELOG_FILE:-.changelog.md}
+DRY_RUN=${DRY_RUN:-0}
+PR_JSON_FILE=${PR_JSON_FILE:-tmp/.pr.json}
+CHANGELOG_FILE=${CHANGELOG_FILE:-tmp/.changelog.md}
+GH_REPO=${GH_REPO:-18f/identity-idp}
USAGE="
${0} [PULL_REQUEST_NUMBER]
@@ -13,35 +15,54 @@ Creates a new release based on the given PR having been merged.
"
if [ $# -eq 0 ]; then
- echo $USAGE
+ echo "$USAGE"
exit 1
fi
PR="$1"; shift
-if [ -z "${CI:-}" ]; then
- echo "This script is meant to be run in a continuous integration environment."
+if ! which gh > /dev/null 2>&1; then
+ echo "Github CLI (gh) is not installed. You can install it with: brew install gh"
exit 1
fi
-if [ -z "${GH_TOKEN:-}" ]; then
- echo "You must set the GH_TOKEN environment variable."
- exit 1
+if [ "${CI:-}" == "1" ]; then
+ # gh will not work in CI unless GH_TOKEN is explicitly set.
+ if [ -z "${GH_TOKEN:-}" ]; then
+ echo "You must set the GH_TOKEN environment variable."
+ exit 1
+ fi
+
+ # GITHUB_SHA is provided by Github Actions, but if for some reason
+ # you are running locally with CI=1 trying to simulate a CI run,
+ # make it clear that we want it set.
+ if [ -z "${GITHUB_SHA:-}" ]; then
+ echo "\$GITHUB_SHA must be set."
+ exit 1
+ fi
fi
+mkdir -p "$(dirname "$PR_JSON_FILE")" || true
+mkdir -p "$(dirname "$CHANGELOG_FILE")" || true
echo "Getting PR ${PR} data..."
gh pr list \
- --json number,title,body \
+ --json number,mergeCommit,title,body \
--base "$DEPLOY_BRANCH" \
--state merged \
- jq ".[] | select(.number == ${PR})" > "$PR_JSON_FILE"
+ | jq ".[] | select(.number == ${PR})" > "$PR_JSON_FILE"
if [ ! -s "$PR_JSON_FILE" ]; then
- echo "PR $PR not found."
+ echo "Merged PR $PR not found."
exit 9
fi
+if [ -z "${GITHUB_SHA:-}" ]; then
+ # $GITHUB_SHA is set by Github Actions. But when running locally we can
+ # pull it out of the PR's JSON blob.
+ GITHUB_SHA=$(jq --raw-output '.mergeCommit.oid' < "$PR_JSON_FILE")
+fi
+
RC=$(jq --raw-output '.title' < "$PR_JSON_FILE" | sed -E 's/Deploy RC (.+) to .*/\1/')
jq --raw-output '.body' < "$PR_JSON_FILE" > "$CHANGELOG_FILE"
TITLE="RC $RC"
@@ -49,7 +70,7 @@ TITLE="RC $RC"
echo "Checking for existing release '$TITLE'..."
EXISTING_RELEASE=$(gh release list --exclude-drafts | (grep "$TITLE" || true))
-if [ ! -z "$EXISTING_RELEASE" ]; then
+if [ -n "$EXISTING_RELEASE" ]; then
echo "❌ Release already exists: $TITLE" >&2
exit 10
else
@@ -58,10 +79,14 @@ fi
TAG=$(date -u +'%Y-%m-%dT%H%M%S')
-echo "Creating release $TITLE with tag $TAG..."
-gh release create \
- "$TAG" \
- --latest \
- --target "$GITHUB_SHA" \
- --title "$TITLE" \
- --notes-file "$CHANGELOG_FILE"
+if [ "${DRY_RUN:-}" == "1" ]; then
+ echo "Dry run. Not creating release $TITLE with tag $TAG ($GITHUB_SHA)..."
+else
+ echo "Creating release $TITLE with tag $TAG..."
+ gh release create \
+ "$TAG" \
+ --latest \
+ --target "$GITHUB_SHA" \
+ --title "$TITLE" \
+ --notes-file "$CHANGELOG_FILE"
+fi
\ No newline at end of file
diff --git a/spec/controllers/api/internal/two_factor_authentication/auth_app_controller_spec.rb b/spec/controllers/api/internal/two_factor_authentication/auth_app_controller_spec.rb
new file mode 100644
index 00000000000..199a0e82a53
--- /dev/null
+++ b/spec/controllers/api/internal/two_factor_authentication/auth_app_controller_spec.rb
@@ -0,0 +1,217 @@
+require 'rails_helper'
+
+RSpec.describe Api::Internal::TwoFactorAuthentication::AuthAppController do
+ let(:user) { create(:user, :with_phone) }
+ let(:configuration) { create(:auth_app_configuration, user:) }
+
+ before do
+ stub_analytics
+ stub_sign_in(user) if user
+ end
+
+ describe '#update' do
+ let(:name) { 'example' }
+ let(:params) { { id: configuration.id, name: } }
+ let(:response) { put :update, params: params }
+ subject(:response_body) { JSON.parse(response.body, symbolize_names: true) }
+
+ it 'responds with successful result' do
+ expect(response_body).to eq(success: true)
+ expect(response.status).to eq(200)
+ end
+
+ it 'logs the submission attempt' do
+ response
+
+ expect(@analytics).to have_logged_event(
+ :auth_app_update_name_submitted,
+ success: true,
+ error_details: nil,
+ configuration_id: configuration.id.to_s,
+ )
+ end
+
+ it 'includes csrf token in the response headers' do
+ expect(response.headers['X-CSRF-Token']).to be_kind_of(String)
+ end
+
+ context 'signed out' do
+ let(:user) { nil }
+ let(:configuration) { create(:auth_app_configuration) }
+
+ it 'responds with unauthorized response' do
+ expect(response_body).to eq(error: 'Unauthorized')
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context 'with invalid submission' do
+ let(:name) { '' }
+
+ it 'responds with unsuccessful result' do
+ expect(response_body).to eq(success: false, error: t('errors.messages.blank'))
+ expect(response.status).to eq(400)
+ end
+
+ it 'logs the submission attempt' do
+ response
+
+ expect(@analytics).to have_logged_event(
+ :auth_app_update_name_submitted,
+ success: false,
+ configuration_id: configuration.id.to_s,
+ error_details: { name: { blank: true } },
+ )
+ end
+ end
+
+ context 'not recently authenticated' do
+ before do
+ allow(controller).to receive(:recently_authenticated_2fa?).and_return(false)
+ end
+
+ it 'responds with unauthorized response' do
+ expect(response_body).to eq(error: 'Unauthorized')
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context 'with a configuration that does not exist' do
+ let(:params) { { id: 0 } }
+
+ it 'responds with unsuccessful result' do
+ expect(response_body).to eq(
+ success: false,
+ error: t('errors.manage_authenticator.internal_error'),
+ )
+ expect(response.status).to eq(400)
+ end
+ end
+
+ context 'with a configuration that does not belong to the user' do
+ let(:configuration) { create(:auth_app_configuration) }
+
+ it 'responds with unsuccessful result' do
+ expect(response_body).to eq(
+ success: false,
+ error: t('errors.manage_authenticator.internal_error'),
+ )
+ expect(response.status).to eq(400)
+ end
+ end
+ end
+
+ describe '#destroy' do
+ let(:params) { { id: configuration.id } }
+ let(:response) { delete :destroy, params: params }
+ subject(:response_body) { JSON.parse(response.body, symbolize_names: true) }
+
+ it 'responds with successful result' do
+ expect(response_body).to eq(success: true)
+ expect(response.status).to eq(200)
+ end
+
+ it 'logs the submission attempt' do
+ response
+
+ expect(@analytics).to have_logged_event(
+ :auth_app_delete_submitted,
+ success: true,
+ configuration_id: configuration.id.to_s,
+ error_details: nil,
+ )
+ end
+
+ it 'includes csrf token in the response headers' do
+ expect(response.headers['X-CSRF-Token']).to be_kind_of(String)
+ end
+
+ it 'sends a recovery information changed event' do
+ expect(PushNotification::HttpPush).to receive(:deliver).
+ with(PushNotification::RecoveryInformationChangedEvent.new(user: user))
+
+ response
+ end
+
+ it 'revokes remembered device' do
+ expect(user.remember_device_revoked_at).to eq nil
+
+ freeze_time do
+ response
+ expect(user.reload.remember_device_revoked_at).to eq Time.zone.now
+ end
+ end
+
+ it 'logs a user event for the removed credential' do
+ expect { response }.to change { user.events.authenticator_disabled.size }.by 1
+ end
+
+ context 'signed out' do
+ let(:user) { nil }
+ let(:configuration) { create(:auth_app_configuration) }
+
+ it 'responds with unauthorized response' do
+ expect(response_body).to eq(error: 'Unauthorized')
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context 'with invalid submission' do
+ let(:user) { create(:user) }
+
+ it 'responds with unsuccessful result' do
+ expect(response_body).to eq(
+ success: false,
+ error: t('errors.manage_authenticator.remove_only_method_error'),
+ )
+ expect(response.status).to eq(400)
+ end
+
+ it 'logs the submission attempt' do
+ response
+
+ expect(@analytics).to have_logged_event(
+ :auth_app_delete_submitted,
+ success: false,
+ configuration_id: configuration.id.to_s,
+ error_details: { configuration_id: { only_method: true } },
+ )
+ end
+ end
+
+ context 'not recently authenticated' do
+ before do
+ allow(controller).to receive(:recently_authenticated_2fa?).and_return(false)
+ end
+
+ it 'responds with unauthorized response' do
+ expect(response_body).to eq(error: 'Unauthorized')
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context 'with a configuration that does not exist' do
+ let(:params) { { id: 0 } }
+
+ it 'responds with unsuccessful result' do
+ expect(response_body).to eq(
+ success: false,
+ error: t('errors.manage_authenticator.internal_error'),
+ )
+ expect(response.status).to eq(400)
+ end
+ end
+
+ context 'with a configuration that does not belong to the user' do
+ let(:configuration) { create(:auth_app_configuration) }
+
+ it 'responds with unsuccessful result' do
+ expect(response_body).to eq(
+ success: false,
+ error: t('errors.manage_authenticator.internal_error'),
+ )
+ expect(response.status).to eq(400)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb
index 21f325de2bf..fcee63bdc1b 100644
--- a/spec/controllers/idv/verify_info_controller_spec.rb
+++ b/spec/controllers/idv/verify_info_controller_spec.rb
@@ -286,7 +286,7 @@
verified_attributes: [],
),
device_profiling_result: Proofing::DdpResult.new(success: true),
- ipp_enrollment_in_progress: false,
+ ipp_enrollment_in_progress: true,
residential_resolution_result: Proofing::Resolution::Result.new(success: true),
resolution_result: Proofing::Resolution::Result.new(success: true),
same_address_as_id: true,
diff --git a/spec/controllers/sign_up/completions_controller_spec.rb b/spec/controllers/sign_up/completions_controller_spec.rb
index fb149479dd5..102d3d4bc1e 100644
--- a/spec/controllers/sign_up/completions_controller_spec.rb
+++ b/spec/controllers/sign_up/completions_controller_spec.rb
@@ -263,6 +263,7 @@
sp_request_requested_attributes: nil,
sp_session_requested_attributes: nil,
in_account_creation_flow: true,
+ disposable_email_domain: nil,
)
end
diff --git a/spec/controllers/users/two_factor_authentication_controller_spec.rb b/spec/controllers/users/two_factor_authentication_controller_spec.rb
index 622e104260f..e5e924b1183 100644
--- a/spec/controllers/users/two_factor_authentication_controller_spec.rb
+++ b/spec/controllers/users/two_factor_authentication_controller_spec.rb
@@ -184,12 +184,17 @@ def index
expect(response).to redirect_to login_two_factor_webauthn_path(platform: false)
end
- it 'passes the platform parameter if the user has a platform autheticator' do
- controller.current_user.webauthn_configurations.first.update!(platform_authenticator: true)
+ context 'when platform_authenticator' do
+ before do
+ controller.current_user.webauthn_configurations.
+ first.update!(platform_authenticator: true)
+ end
- get :show
+ it 'passes the platform parameter if the user has a platform autheticator' do
+ get :show
- expect(response).to redirect_to login_two_factor_webauthn_path(platform: true)
+ expect(response).to redirect_to login_two_factor_webauthn_path(platform: true)
+ end
end
end
diff --git a/spec/controllers/users/webauthn_setup_controller_spec.rb b/spec/controllers/users/webauthn_setup_controller_spec.rb
index afdd6dcdc48..efa3a03fbe9 100644
--- a/spec/controllers/users/webauthn_setup_controller_spec.rb
+++ b/spec/controllers/users/webauthn_setup_controller_spec.rb
@@ -400,10 +400,7 @@
'Multi-Factor Authentication Setup',
{
enabled_mfa_methods_count: 0,
- errors: { name: [I18n.t(
- 'errors.webauthn_platform_setup.attestation_error',
- link: MarketingSite.contact_url,
- )] },
+ errors: { name: [I18n.t('errors.webauthn_platform_setup.general_error')] },
error_details: { name: { attestation_error: true } },
in_account_creation_flow: false,
mfa_method_counts: {},
diff --git a/spec/features/idv/doc_auth/welcome_spec.rb b/spec/features/idv/doc_auth/welcome_spec.rb
index 23ca9a7629b..dd94e83f215 100644
--- a/spec/features/idv/doc_auth/welcome_spec.rb
+++ b/spec/features/idv/doc_auth/welcome_spec.rb
@@ -16,63 +16,17 @@
complete_doc_auth_steps_before_welcome_step
end
- it 'logs return to sp link click' do
- click_on t('idv.troubleshooting.options.get_help_at_sp', sp_name: sp_name)
-
- expect(fake_analytics).to have_logged_event(
- 'Return to SP: Failed to proof',
- flow: nil,
- location: 'missing_items',
- redirect_url: instance_of(String),
- step: 'welcome',
- )
- end
-
- it 'logs supported documents troubleshooting link click' do
- click_on t('idv.troubleshooting.options.supported_documents')
-
- expect(fake_analytics).to have_logged_event(
- 'External Redirect',
- step: 'welcome',
- location: 'missing_items',
- flow: 'idv',
- redirect_url: MarketingSite.help_center_article_url(
- category: 'verify-your-identity',
- article: 'accepted-state-issued-identification',
- ),
- )
- end
-
- it 'logs missing items troubleshooting link click' do
- within '.troubleshooting-options' do
- click_on t('idv.troubleshooting.options.learn_more_address_verification_options')
- end
-
- expect(fake_analytics).to have_logged_event(
- 'External Redirect',
- step: 'welcome',
- location: 'missing_items',
- flow: 'idv',
- redirect_url: MarketingSite.help_center_article_url(
- category: 'verify-your-identity',
- article: 'phone-number',
- ),
- )
- end
-
- it 'logs "you will need" learn more link click' do
- within '.usa-process-list' do
- click_on t('idv.troubleshooting.options.learn_more_address_verification_options')
- end
+ it 'logs "intro_paragraph" learn more link click' do
+ click_on t('doc_auth.info.getting_started_learn_more')
expect(fake_analytics).to have_logged_event(
'External Redirect',
step: 'welcome',
- location: 'you_will_need',
+ location: 'intro_paragraph',
flow: 'idv',
redirect_url: MarketingSite.help_center_article_url(
category: 'verify-your-identity',
- article: 'phone-number',
+ article: 'how-to-verify-your-identity',
),
)
end
diff --git a/spec/features/idv/end_to_end_idv_spec.rb b/spec/features/idv/end_to_end_idv_spec.rb
index ee0bfdc6667..778fc1d7462 100644
--- a/spec/features/idv/end_to_end_idv_spec.rb
+++ b/spec/features/idv/end_to_end_idv_spec.rb
@@ -5,6 +5,7 @@
include InPersonHelper
let(:sp) { :oidc }
+ let(:sp_name) { 'Test SP' }
scenario 'Unsupervised proofing happy path desktop' do
try_to_skip_ahead_before_signing_in
@@ -148,8 +149,7 @@
def validate_welcome_page
expect(page).to have_current_path(idv_welcome_path)
- # Check for expected content
- expect_step_indicator_current_step(t('step_indicator.flows.idv.getting_started'))
+ expect(page).to have_content t('doc_auth.headings.welcome', sp_name: sp_name)
end
def validate_agreement_page
@@ -215,12 +215,7 @@ def validate_ssn_page
expect(page.find_field(t('idv.form.ssn_label'))['aria-invalid']).to eq('false')
expect(page).to have_content(t('doc_auth.info.no_ssn'))
- click_link(
- t(
- 'doc_auth.info.exit.with_sp', app_name: APP_NAME,
- sp_name: 'Test SP'
- ),
- )
+ click_link(t('doc_auth.info.exit.with_sp', app_name: APP_NAME, sp_name: sp_name))
expect(page).to have_current_path(idv_cancel_path(step: 'ssn_offramp'))
click_on t('idv.cancel.actions.keep_going')
diff --git a/spec/features/idv/in_person_spec.rb b/spec/features/idv/in_person_spec.rb
index a1b8568f59e..d520a987931 100644
--- a/spec/features/idv/in_person_spec.rb
+++ b/spec/features/idv/in_person_spec.rb
@@ -420,155 +420,6 @@
end
end
- context 'transliteration' do
- before(:each) do
- allow(IdentityConfig.store).to receive(:usps_ipp_transliteration_enabled).
- and_return(true)
- end
-
- let(:user) { user_with_2fa }
- let(:enrollment) { InPersonEnrollment.new }
-
- before do
- allow(user).to receive(:establishing_in_person_enrollment).
- and_return(enrollment)
- end
-
- it 'shows validation errors',
- allow_browser_log: true do
- sign_in_and_2fa_user
- begin_in_person_proofing
- complete_prepare_step
- complete_location_step
- expect(page).to have_current_path(idv_in_person_step_path(step: :state_id), wait: 10)
-
- fill_out_state_id_form_ok
- fill_in t('in_person_proofing.form.state_id.first_name'), with: 'T0mmy "Lee"'
- fill_in t('in_person_proofing.form.state_id.last_name'), with: 'Джейкоб'
- fill_in t('in_person_proofing.form.state_id.address1'), with: '#1 $treet'
- fill_in t('in_person_proofing.form.state_id.address2'), with: 'Gr@nd Lañe^'
- fill_in t('in_person_proofing.form.state_id.city'), with: 'B3st C!ty'
- click_idv_continue
-
- expect(page).to have_content(
- I18n.t(
- 'in_person_proofing.form.state_id.errors.unsupported_chars',
- char_list: '", 0',
- ),
- )
-
- expect(page).to have_content(
- I18n.t(
- 'in_person_proofing.form.state_id.errors.unsupported_chars',
- char_list: 'Д, б, е, ж, й, к, о',
- ),
- )
-
- expect(page).to have_content(
- I18n.t(
- 'in_person_proofing.form.state_id.errors.unsupported_chars',
- char_list: '$',
- ),
- )
-
- expect(page).to have_content(
- I18n.t(
- 'in_person_proofing.form.state_id.errors.unsupported_chars',
- char_list: '@, ^',
- ),
- )
-
- expect(page).to have_content(
- I18n.t(
- 'in_person_proofing.form.state_id.errors.unsupported_chars',
- char_list: '!, 3',
- ),
- )
-
- # re-fill state id form with good inputs
- fill_in t('in_person_proofing.form.state_id.first_name'),
- with: InPersonHelper::GOOD_FIRST_NAME
- fill_in t('in_person_proofing.form.state_id.last_name'),
- with: InPersonHelper::GOOD_LAST_NAME
- fill_in t('in_person_proofing.form.state_id.address1'),
- with: InPersonHelper::GOOD_IDENTITY_DOC_ADDRESS1
- fill_in t('in_person_proofing.form.state_id.address2'),
- with: InPersonHelper::GOOD_IDENTITY_DOC_ADDRESS2
- fill_in t('in_person_proofing.form.state_id.city'),
- with: InPersonHelper::GOOD_IDENTITY_DOC_CITY
- click_idv_continue
-
- expect(page).to have_current_path(idv_in_person_step_path(step: :address), wait: 10)
- end
-
- it 'shows hints when user selects Puerto Rico as state',
- allow_browser_log: true do
- sign_in_and_2fa_user
- begin_in_person_proofing
- complete_prepare_step
- complete_location_step
- expect(page).to have_current_path(idv_in_person_step_path(step: :state_id), wait: 10)
-
- # state id page
- select 'Puerto Rico',
- from: t('in_person_proofing.form.state_id.identity_doc_address_state')
-
- expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address1_hint'))
- expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address2_hint'))
-
- # change state selection
- fill_out_state_id_form_ok
- expect(page).not_to have_content(I18n.t('in_person_proofing.form.state_id.address1_hint'))
- expect(page).not_to have_content(I18n.t('in_person_proofing.form.state_id.address2_hint'))
-
- # re-select puerto rico
- select 'Puerto Rico',
- from: t('in_person_proofing.form.state_id.identity_doc_address_state')
- click_idv_continue
-
- expect(page).to have_current_path(idv_in_person_step_path(step: :address))
-
- # address form
- select 'Puerto Rico',
- from: t('idv.form.state')
- expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address1_hint'))
- expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address2_hint'))
-
- # change selection
- fill_out_address_form_ok
- expect(page).not_to have_content(I18n.t('in_person_proofing.form.state_id.address1_hint'))
- expect(page).not_to have_content(I18n.t('in_person_proofing.form.state_id.address2_hint'))
-
- # re-select puerto rico
- select 'Puerto Rico',
- from: t('idv.form.state')
- click_idv_continue
-
- # ssn page
- expect(page).to have_current_path(idv_in_person_ssn_url)
- complete_ssn_step
-
- # verify page
- expect(page).to have_current_path(idv_in_person_verify_info_path)
- expect(page).to have_text('PR').twice
-
- # update state ID
- click_button t('idv.buttons.change_state_id_label')
-
- expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
- expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address1_hint'))
- expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address2_hint'))
- click_button t('forms.buttons.submit.update')
-
- # update address
- click_button t('idv.buttons.change_address_label')
-
- expect(page).to have_content(t('in_person_proofing.headings.update_address'))
- expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address1_hint'))
- expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address2_hint'))
- end
- end
-
context 'same address as id is false',
allow_browser_log: true do
let(:user) { user_with_2fa }
@@ -607,7 +458,7 @@
end
end
- context 'same address as id is true then update is selected on verify info pg',
+ context 'same address as id is true',
allow_browser_log: true do
let(:user) { user_with_2fa }
@@ -618,32 +469,26 @@
complete_location_step(user)
end
- it 'can redo the address page form after it is skipped' do
- complete_state_id_step(user, same_address_as_id: true)
- # skip address step
- complete_ssn_step(user)
- # click update address button on the verify page
- click_button t('idv.buttons.change_address_label')
- expect(page).to have_content(t('in_person_proofing.headings.update_address'))
- fill_out_address_form_ok(same_address_as_id: true)
- click_button t('forms.buttons.submit.update')
- expect(page).to have_content(t('headings.verify'))
- expect(page).to have_current_path(idv_in_person_verify_info_path)
- end
-
it 'allows user to update their residential address as different from their state id' do
complete_state_id_step(user, same_address_as_id: true)
+ # skip address step b/c residential address is same as state id address
complete_ssn_step(user)
- # click "update residential address"
+ # click update residential address
click_button t('idv.buttons.change_address_label')
expect(page).to have_content(t('in_person_proofing.headings.update_address'))
- # change something in the address
+ # expect address page to have fields populated with address from state id
+ expect(page).to have_field(
+ t('idv.form.address1'),
+ with: InPersonHelper::GOOD_IDENTITY_DOC_ADDRESS1,
+ )
+
+ # change part of the address
fill_in t('idv.form.address1'), with: 'new address different from state address1'
# click update
click_button t('forms.buttons.submit.update')
- # back to verify page
+ # verify page
expect(page).to have_current_path(idv_in_person_verify_info_path)
expect(page).to have_content(t('headings.verify'))
expect(page).to have_text('new address different from state address1').once
@@ -659,156 +504,38 @@
end
end
- context 'Updates are made on state ID page starting from Verify Your Information',
- allow_browser_log: true do
+ context 'Outage alert enabled' do
let(:user) { user_with_2fa }
- before(:each) do
- sign_in_and_2fa_user(user)
- begin_in_person_proofing(user)
- complete_prepare_step(user)
- complete_location_step(user)
- end
-
- it 'does not update their previous selection of "Yes,
- I live at the address on my state-issued ID"' do
- complete_state_id_step(user, same_address_as_id: true)
- # skip address step
- complete_ssn_step(user)
- # expect to be on verify page
- expect(page).to have_content(t('headings.verify'))
- expect(page).to have_current_path(idv_in_person_verify_info_path)
- # click update state ID button on the verify page
- click_button t('idv.buttons.change_state_id_label')
- # expect to be on the state ID page
- expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
- # change address
- fill_in t('in_person_proofing.form.state_id.address1'), with: ''
- fill_in t('in_person_proofing.form.state_id.address1'), with: 'test update address'
- click_button t('forms.buttons.submit.update')
- # expect to be back on verify page
- expect(page).to have_content(t('headings.verify'))
- expect(page).to have_current_path(idv_in_person_verify_info_path)
- expect(page).to have_content(t('headings.verify'))
- # expect to see state ID address update on verify twice
- expect(page).to have_text('test update address').twice # for state id addr and addr update
- # click update state id address
- click_button t('idv.buttons.change_state_id_label')
- # expect to be on the state ID page
- expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
- # expect "Yes, I live at a different address" is checked"
- expect(page).to have_checked_field(
- t('in_person_proofing.form.state_id.same_address_as_id_yes'),
- visible: false,
- )
+ before do
+ allow(IdentityConfig.store).to receive(:in_person_outage_message_enabled).and_return(true)
end
- it 'does not update their previous selection of "No, I live at a different address"' do
- complete_state_id_step(user, same_address_as_id: false)
- # expect to be on address page
- expect(page).to have_content(t('in_person_proofing.headings.address'))
- # complete address step
- complete_address_step(user)
- complete_ssn_step(user)
- # expect to be back on verify page
- expect(page).to have_content(t('headings.verify'))
- expect(page).to have_current_path(idv_in_person_verify_info_path)
- # click update state ID button on the verify page
- click_button t('idv.buttons.change_state_id_label')
- # expect to be on the state ID page
- expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
- # change address
- fill_in t('in_person_proofing.form.state_id.address1'), with: ''
- fill_in t('in_person_proofing.form.state_id.address1'), with: 'test update address'
- click_button t('forms.buttons.submit.update')
- # expect to be back on verify page
- expect(page).to have_content(t('headings.verify'))
- expect(page).to have_current_path(idv_in_person_verify_info_path)
- expect(page).to have_content(t('headings.verify'))
- # expect to see state ID address update on verify
- expect(page).to have_text('test update address').once # only state id address update
- # click update state id address
- click_button t('idv.buttons.change_state_id_label')
- # expect to be on the state ID page
- expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
- expect(page).to have_checked_field(
- t('in_person_proofing.form.state_id.same_address_as_id_no'),
- visible: false,
- )
- end
+ it 'allows the user to generate a barcode despite outage', allow_browser_log: true do
+ sign_in_and_2fa_user(user)
+ begin_in_person_proofing(user)
- it 'updates their previous selection from "Yes" TO "No, I live at a different address"' do
- complete_state_id_step(user, same_address_as_id: true)
- # skip address step
- complete_ssn_step(user)
- # click update state ID button on the verify page
- click_button t('idv.buttons.change_state_id_label')
- # expect to be on the state ID page
- expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
- # change address
- fill_in t('in_person_proofing.form.state_id.address1'), with: ''
- fill_in t('in_person_proofing.form.state_id.address1'), with: 'test update address'
- # change response to No
- choose t('in_person_proofing.form.state_id.same_address_as_id_no')
- click_button t('forms.buttons.submit.update')
- # expect to be on address page
- expect(page).to have_content(t('in_person_proofing.headings.address'))
- # complete address step
- complete_address_step(user)
- # expect to be on verify page
- expect(page).to have_content(t('headings.verify'))
- expect(page).to have_current_path(idv_in_person_verify_info_path)
- # expect to see state ID address update on verify
- expect(page).to have_text('test update address').once # only state id address update
- # click update state id address
- click_button t('idv.buttons.change_state_id_label')
- # expect to be on the state ID page
- expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
- # check that the "No, I live at a different address" is checked"
- expect(page).to have_checked_field(
- t('in_person_proofing.form.state_id.same_address_as_id_no'),
- visible: false,
+ # alert is visible on prepare page
+ expect(page).to have_content(
+ t(
+ 'idv.failure.exceptions.in_person_outage_error_message.post_cta.body',
+ app_name: APP_NAME,
+ ),
)
- end
+ complete_all_in_person_proofing_steps
+ complete_phone_step(user)
+ complete_enter_password_step(user)
+ acknowledge_and_confirm_personal_key
- it 'updates their previous selection from "No" TO "Yes,
- I live at the address on my state-issued ID"' do
- complete_state_id_step(user, same_address_as_id: false)
- # expect to be on address page
- expect(page).to have_content(t('in_person_proofing.headings.address'))
- # complete address step
- complete_address_step(user)
- complete_ssn_step(user)
- # expect to be on verify page
- expect(page).to have_content(t('headings.verify'))
- expect(page).to have_current_path(idv_in_person_verify_info_path)
- # click update state ID button on the verify page
- click_button t('idv.buttons.change_state_id_label')
- # expect to be on the state ID page
- expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
- # change address
- fill_in t('in_person_proofing.form.state_id.address1'), with: ''
- fill_in t('in_person_proofing.form.state_id.address1'), with: 'test update address'
- # change response to Yes
- choose t('in_person_proofing.form.state_id.same_address_as_id_yes')
- click_button t('forms.buttons.submit.update')
- # expect to be back on verify page
- expect(page).to have_content(t('headings.verify'))
- expect(page).to have_current_path(idv_in_person_verify_info_path)
- # expect to see state ID address update on verify twice
- expect(page).to have_text('test update address').twice # for state id addr and addr update
- # click update state ID button on the verify page
- click_button t('idv.buttons.change_state_id_label')
- # expect to be on the state ID page
- expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
- expect(page).to have_checked_field(
- t('in_person_proofing.form.state_id.same_address_as_id_yes'),
- visible: false,
+ # alert is visible on ready to verify page
+ expect(page).to have_content(
+ t('idv.failure.exceptions.in_person_outage_error_message.ready_to_verify.body'),
)
+ expect(page).to have_current_path(idv_in_person_ready_to_verify_path, wait: 10)
end
end
- context 'when manual address entry is enabled for post office search' do
+ context 'when full form address entry is enabled for post office search' do
let(:user) { user_with_2fa }
before do
diff --git a/spec/features/idv/pending_profile_password_reset_spec.rb b/spec/features/idv/pending_profile_password_reset_spec.rb
index dd2421c7cda..bc42be34212 100644
--- a/spec/features/idv/pending_profile_password_reset_spec.rb
+++ b/spec/features/idv/pending_profile_password_reset_spec.rb
@@ -3,6 +3,12 @@
RSpec.describe 'Resetting password with a pending profile' do
include OidcAuthHelper
+ let(:sp_name) { 'Test SP' }
+ before do
+ allow_any_instance_of(ServiceProviderSession).to receive(:sp_name).
+ and_return(sp_name)
+ end
+
scenario 'while GPO pending requires the user to reproof' do
user = create(:user, :with_phone, :with_pending_gpo_profile)
@@ -19,7 +25,7 @@
user.password = new_password
sign_in_live_with_2fa(user)
- expect(page).to have_content(t('doc_auth.headings.welcome'))
+ expect(page).to have_content t('doc_auth.headings.welcome', sp_name: sp_name)
expect(current_path).to eq(idv_welcome_path)
expect(user.reload.active_or_pending_profile).to be_nil
@@ -43,7 +49,7 @@
user.password = new_password
sign_in_live_with_2fa(user)
- expect(page).to have_content(t('doc_auth.headings.welcome'))
+ expect(page).to have_content(t('doc_auth.headings.welcome', sp_name: sp_name))
expect(current_path).to eq(idv_welcome_path)
expect(user.reload.active_or_pending_profile).to be_nil
@@ -65,7 +71,7 @@
user.password = new_password
sign_in_live_with_2fa(user)
- expect(page).to have_content(t('doc_auth.headings.welcome'))
+ expect(page).to have_content(t('doc_auth.headings.welcome', sp_name: sp_name))
expect(current_path).to eq(idv_welcome_path)
expect(user.reload.active_or_pending_profile).to be_nil
diff --git a/spec/features/idv/steps/in_person/address_spec.rb b/spec/features/idv/steps/in_person/address_spec.rb
index b6055711e5e..c98fa252f59 100644
--- a/spec/features/idv/steps/in_person/address_spec.rb
+++ b/spec/features/idv/steps/in_person/address_spec.rb
@@ -23,6 +23,7 @@
it 'allows the user to cancel and start over', allow_browser_log: true do
complete_idv_steps_before_address
+ expect(page).to have_current_path(idv_in_person_proofing_address_url, wait: 10)
expect(page).not_to have_content('forms.buttons.back')
click_link t('links.cancel')
@@ -55,6 +56,143 @@
expect(page).to have_text(InPersonHelper::GOOD_ADDRESS2)
expect(page).to have_text(InPersonHelper::GOOD_CITY)
expect(page).to have_text(InPersonHelper::GOOD_ZIPCODE)
+ expect(page).to have_text(Idp::Constants::MOCK_IDV_APPLICANT_STATE)
+ end
+ end
+
+ context 'updating address page' do
+ it 'has form fields that are pre-populated', allow_browser_log: true do
+ user = user_with_2fa
+ complete_idv_steps_before_address(user)
+ fill_out_address_form_ok
+ click_idv_continue
+ complete_ssn_step(user)
+
+ expect(page).to have_current_path(idv_in_person_verify_info_url, wait: 10)
+ click_link t('idv.buttons.change_address_label')
+
+ # address page has fields that are pre-populated
+ expect(page).to have_content(t('in_person_proofing.headings.update_address'))
+ expect(page).to have_field(t('idv.form.address1'), with: InPersonHelper::GOOD_ADDRESS1)
+ expect(page).to have_field(t('idv.form.address2'), with: InPersonHelper::GOOD_ADDRESS2)
+ expect(page).to have_field(t('idv.form.city'), with: InPersonHelper::GOOD_CITY)
+ expect(page).to have_field(t('idv.form.zipcode'), with: InPersonHelper::GOOD_ZIPCODE)
+ expect(page).to have_field(
+ t('idv.form.state'),
+ with: Idp::Constants::MOCK_IDV_APPLICANT_STATE,
+ )
+ end
+ end
+
+ context 'Transliterable Validation' do
+ before(:each) do
+ allow(IdentityConfig.store).to receive(:usps_ipp_transliteration_enabled).
+ and_return(true)
+ end
+
+ it 'shows validation errors',
+ allow_browser_log: true do
+ complete_idv_steps_before_address
+
+ fill_out_address_form_ok(same_address_as_id: false)
+ fill_in t('idv.form.address1'), with: '#1 $treet'
+ fill_in t('idv.form.address2'), with: 'Gr@nd Lañe^'
+ fill_in t('idv.form.city'), with: 'N3w C!ty'
+ click_idv_continue
+
+ expect(page).to have_content(
+ I18n.t(
+ 'in_person_proofing.form.address.errors.unsupported_chars',
+ char_list: '$',
+ ),
+ )
+
+ expect(page).to have_content(
+ I18n.t(
+ 'in_person_proofing.form.address.errors.unsupported_chars',
+ char_list: '@, ^',
+ ),
+ )
+
+ expect(page).to have_content(
+ I18n.t(
+ 'in_person_proofing.form.address.errors.unsupported_chars',
+ char_list: '!, 3',
+ ),
+ )
+
+ select InPersonHelper::GOOD_STATE, from: t('idv.form.state')
+ fill_in t('idv.form.address1'),
+ with: InPersonHelper::GOOD_ADDRESS1
+ fill_in t('idv.form.address2'),
+ with: InPersonHelper::GOOD_ADDRESS2
+ fill_in t('idv.form.city'),
+ with: InPersonHelper::GOOD_CITY
+ fill_in t('idv.form.zipcode'),
+ with: InPersonHelper::GOOD_ZIPCODE
+ click_idv_continue
+
+ expect(page).to have_current_path(idv_in_person_ssn_url, wait: 10)
+ end
+ end
+
+ context 'Validation' do
+ it 'validates zip code input', allow_browser_log: true do
+ complete_idv_steps_before_address
+
+ fill_out_address_form_ok
+ # blank out the zip code field
+ fill_in t('idv.form.zipcode'), with: ''
+ # try to enter invalid input into the zip code field
+ fill_in t('idv.form.zipcode'), with: 'invalid input'
+ expect(page).to have_field(t('idv.form.zipcode'), with: '')
+ # enter valid characters, but invalid length
+ fill_in t('idv.form.zipcode'), with: '123'
+ click_idv_continue
+ expect(page).to have_css('.usa-error-message', text: t('idv.errors.pattern_mismatch.zipcode'))
+ # enter a valid zip and make sure we can continue
+ fill_in t('idv.form.zipcode'), with: '123456789'
+ expect(page).to have_field(t('idv.form.zipcode'), with: '12345-6789')
+ click_idv_continue
+ expect(page).to have_current_path(idv_in_person_ssn_url)
+ end
+ end
+
+ context 'State selection' do
+ it 'shows address hint when user selects state that has a specific hint',
+ allow_browser_log: true do
+ complete_idv_steps_before_address
+
+ # address form
+ select 'Puerto Rico',
+ from: t('idv.form.state')
+ expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address1_hint'))
+ expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address2_hint'))
+
+ # change selection
+ fill_out_address_form_ok(same_address_as_id: false)
+ expect(page).not_to have_content(I18n.t('in_person_proofing.form.state_id.address1_hint'))
+ expect(page).not_to have_content(I18n.t('in_person_proofing.form.state_id.address2_hint'))
+
+ # re-select puerto rico
+ select 'Puerto Rico',
+ from: t('idv.form.state')
+ click_idv_continue
+
+ # ssn page
+ expect(page).to have_current_path(idv_in_person_ssn_url)
+ complete_ssn_step
+
+ # verify page
+ expect(page).to have_current_path(idv_in_person_verify_info_path, wait: 10)
+ expect(page).to have_text('PR')
+
+ # update address
+ click_link t('idv.buttons.change_address_label')
+
+ expect(page).to have_content(t('in_person_proofing.headings.update_address'))
+ expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address1_hint'))
+ expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address2_hint'))
end
end
end
diff --git a/spec/features/idv/steps/in_person/state_id_step_spec.rb b/spec/features/idv/steps/in_person/state_id_step_spec.rb
index aa774a0477a..2b761fba616 100644
--- a/spec/features/idv/steps/in_person/state_id_step_spec.rb
+++ b/spec/features/idv/steps/in_person/state_id_step_spec.rb
@@ -8,28 +8,454 @@
allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true)
end
- it 'validates zip code input', allow_browser_log: true do
- user = user_with_2fa
-
- sign_in_and_2fa_user(user)
- begin_in_person_proofing(user)
- complete_prepare_step(user)
- complete_location_step(user)
- expect(page).to have_current_path(idv_in_person_step_path(step: :state_id), wait: 10)
- fill_out_state_id_form_ok(same_address_as_id: true)
- # blank out the zip code field
- fill_in t('in_person_proofing.form.state_id.zipcode'), with: ''
- # try to enter invalid input into the zip code field
- fill_in t('in_person_proofing.form.state_id.zipcode'), with: 'invalid input'
- expect(page).to have_field(t('in_person_proofing.form.state_id.zipcode'), with: '')
- # enter valid characters, but invalid length
- fill_in t('in_person_proofing.form.state_id.zipcode'), with: '123'
- click_idv_continue
- expect(page).to have_css('.usa-error-message', text: t('idv.errors.pattern_mismatch.zipcode'))
- # enter a valid zip and make sure we can continue
- fill_in t('in_person_proofing.form.state_id.zipcode'), with: '123456789'
- expect(page).to have_field(t('in_person_proofing.form.state_id.zipcode'), with: '12345-6789')
- click_idv_continue
- expect(page).to have_current_path(idv_in_person_ssn_url)
+ context 'when visiting state id for the first time' do
+ it 'displays correct heading and button text', allow_browser_log: true do
+ complete_steps_before_state_id_step
+
+ expect(page).to have_content(t('forms.buttons.continue'))
+ expect(page).to have_content(
+ t(
+ 'in_person_proofing.headings.state_id_milestone_2',
+ ).tr(' ', ' '),
+ )
+ end
+
+ it 'allows the user to cancel and start over', allow_browser_log: true do
+ complete_steps_before_state_id_step
+
+ expect(page).not_to have_content('forms.buttons.back')
+
+ click_link t('links.cancel')
+ click_on t('idv.cancel.actions.start_over')
+ expect(page).to have_current_path(idv_welcome_path)
+ end
+
+ it 'allows the user to cancel and return', allow_browser_log: true do
+ complete_steps_before_state_id_step
+
+ expect(page).not_to have_content('forms.buttons.back')
+
+ click_link t('links.cancel')
+ click_on t('idv.cancel.actions.keep_going')
+ expect(page).to have_current_path(idv_in_person_step_path(step: :state_id), wait: 10)
+ end
+
+ it 'allows user to submit valid inputs on form', allow_browser_log: true do
+ complete_steps_before_state_id_step
+ fill_out_state_id_form_ok(same_address_as_id: true)
+ click_idv_continue
+
+ expect(page).to have_current_path(idv_in_person_ssn_url, wait: 10)
+ complete_ssn_step
+
+ expect_in_person_step_indicator_current_step(t('step_indicator.flows.idv.verify_info'))
+ expect(page).to have_current_path(idv_in_person_verify_info_url)
+ expect(page).to have_text(InPersonHelper::GOOD_FIRST_NAME)
+ expect(page).to have_text(InPersonHelper::GOOD_LAST_NAME)
+ expect(page).to have_text(InPersonHelper::GOOD_DOB_FORMATTED_EVENT)
+ expect(page).to have_text(InPersonHelper::GOOD_STATE_ID_NUMBER)
+ expect(page).to have_text(InPersonHelper::GOOD_IDENTITY_DOC_ADDRESS1)
+ expect(page).to have_text(InPersonHelper::GOOD_IDENTITY_DOC_ADDRESS2)
+ expect(page).to have_text(InPersonHelper::GOOD_IDENTITY_DOC_CITY)
+ expect(page).to have_text(InPersonHelper::GOOD_IDENTITY_DOC_ZIPCODE)
+ end
+ end
+
+ context 'updating state id page' do
+ it 'has form fields that are pre-populated', allow_browser_log: true do
+ complete_steps_before_state_id_step
+
+ fill_out_state_id_form_ok(same_address_as_id: true)
+ click_idv_continue
+ expect(page).to have_current_path(idv_in_person_ssn_url, wait: 10)
+ complete_ssn_step
+ expect(page).to have_current_path(idv_in_person_verify_info_url, wait: 10)
+ click_button t('idv.buttons.change_state_id_label')
+
+ # state id page has fields that are pre-populated
+ expect(page).to have_current_path(idv_in_person_step_path(step: :state_id), wait: 10)
+ expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
+ expect(page).to have_field(
+ t('in_person_proofing.form.state_id.first_name'),
+ with: InPersonHelper::GOOD_FIRST_NAME,
+ )
+ expect(page).to have_field(
+ t('in_person_proofing.form.state_id.last_name'),
+ with: InPersonHelper::GOOD_LAST_NAME,
+ )
+ expect(page).to have_field(t('components.memorable_date.month'), with: '10')
+ expect(page).to have_field(t('components.memorable_date.day'), with: '6')
+ expect(page).to have_field(t('components.memorable_date.year'), with: '1938')
+ expect(page).to have_field(
+ t('in_person_proofing.form.state_id.state_id_jurisdiction'),
+ with: Idp::Constants::MOCK_IDV_APPLICANT[:state_id_jurisdiction],
+ )
+ expect(page).to have_field(
+ t('in_person_proofing.form.state_id.state_id_number'),
+ with: InPersonHelper::GOOD_STATE_ID_NUMBER,
+ )
+ expect(page).to have_field(
+ t('in_person_proofing.form.state_id.address1'),
+ with: InPersonHelper::GOOD_IDENTITY_DOC_ADDRESS1,
+ )
+ expect(page).to have_field(
+ t('in_person_proofing.form.state_id.address2'),
+ with: InPersonHelper::GOOD_IDENTITY_DOC_ADDRESS2,
+ )
+ expect(page).to have_field(
+ t('in_person_proofing.form.state_id.city'),
+ with: InPersonHelper::GOOD_IDENTITY_DOC_CITY,
+ )
+ expect(page).to have_field(
+ t('in_person_proofing.form.state_id.zipcode'),
+ with: InPersonHelper::GOOD_IDENTITY_DOC_ZIPCODE,
+ )
+ expect(page).to have_field(
+ t('in_person_proofing.form.state_id.identity_doc_address_state'),
+ with: Idp::Constants::MOCK_IDV_APPLICANT[:state_id_jurisdiction],
+ )
+ expect(page).to have_checked_field(
+ t('in_person_proofing.form.state_id.same_address_as_id_yes'),
+ visible: false,
+ )
+ end
+
+ context 'same address as id',
+ allow_browser_log: true do
+ let(:user) { user_with_2fa }
+
+ before(:each) do
+ sign_in_and_2fa_user(user)
+ begin_in_person_proofing(user)
+ complete_prepare_step(user)
+ complete_location_step(user)
+ end
+
+ it 'does not update their previous selection of "Yes,
+ I live at the address on my state-issued ID"' do
+ complete_state_id_step(user, same_address_as_id: true)
+ # skip address step
+ complete_ssn_step(user)
+ # expect to be on verify page
+ expect(page).to have_content(t('headings.verify'))
+ expect(page).to have_current_path(idv_in_person_verify_info_path)
+ # click update state ID button on the verify page
+ click_button t('idv.buttons.change_state_id_label')
+ # expect to be on the state ID page
+ expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
+ # change address
+ fill_in t('in_person_proofing.form.state_id.address1'), with: ''
+ fill_in t('in_person_proofing.form.state_id.address1'), with: 'test update address'
+ click_button t('forms.buttons.submit.update')
+ # expect to be back on verify page
+ expect(page).to have_content(t('headings.verify'))
+ expect(page).to have_current_path(idv_in_person_verify_info_path)
+ expect(page).to have_content(t('headings.verify'))
+ # expect to see state ID address update on verify twice
+ expect(page).to have_text('test update address').twice # for state id addr and addr update
+ # click update state id address
+ click_button t('idv.buttons.change_state_id_label')
+ # expect to be on the state ID page
+ expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
+ # expect "Yes, I live at a different address" is checked"
+ expect(page).to have_checked_field(
+ t('in_person_proofing.form.state_id.same_address_as_id_yes'),
+ visible: false,
+ )
+ end
+
+ it 'does not update their previous selection of "No, I live at a different address"' do
+ complete_state_id_step(user, same_address_as_id: false)
+ # expect to be on address page
+ expect(page).to have_content(t('in_person_proofing.headings.address'))
+ # complete address step
+ complete_address_step(user)
+ complete_ssn_step(user)
+ # expect to be back on verify page
+ expect(page).to have_content(t('headings.verify'))
+ expect(page).to have_current_path(idv_in_person_verify_info_path)
+ # click update state ID button on the verify page
+ click_button t('idv.buttons.change_state_id_label')
+ # expect to be on the state ID page
+ expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
+ # change address
+ fill_in t('in_person_proofing.form.state_id.address1'), with: ''
+ fill_in t('in_person_proofing.form.state_id.address1'), with: 'test update address'
+ click_button t('forms.buttons.submit.update')
+ # expect to be back on verify page
+ expect(page).to have_content(t('headings.verify'))
+ expect(page).to have_current_path(idv_in_person_verify_info_path)
+ expect(page).to have_content(t('headings.verify'))
+ # expect to see state ID address update on verify
+ expect(page).to have_text('test update address').once # only state id address update
+ # click update state id address
+ click_button t('idv.buttons.change_state_id_label')
+ # expect to be on the state ID page
+ expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
+ expect(page).to have_checked_field(
+ t('in_person_proofing.form.state_id.same_address_as_id_no'),
+ visible: false,
+ )
+ end
+
+ it 'updates their previous selection from "Yes" TO "No, I live at a different address"' do
+ complete_state_id_step(user, same_address_as_id: true)
+ # skip address step
+ complete_ssn_step(user)
+ # click update state ID button on the verify page
+ click_button t('idv.buttons.change_state_id_label')
+ # expect to be on the state ID page
+ expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
+ # change address
+ fill_in t('in_person_proofing.form.state_id.address1'), with: ''
+ fill_in t('in_person_proofing.form.state_id.address1'), with: 'test update address'
+ # change response to No
+ choose t('in_person_proofing.form.state_id.same_address_as_id_no')
+ click_button t('forms.buttons.submit.update')
+ # expect to be on address page
+ expect(page).to have_content(t('in_person_proofing.headings.address'))
+ # complete address step
+ complete_address_step(user)
+ # expect to be on verify page
+ expect(page).to have_content(t('headings.verify'))
+ expect(page).to have_current_path(idv_in_person_verify_info_path)
+ # expect to see state ID address update on verify
+ expect(page).to have_text('test update address').once # only state id address update
+ # click update state id address
+ click_button t('idv.buttons.change_state_id_label')
+ # expect to be on the state ID page
+ expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
+ # check that the "No, I live at a different address" is checked"
+ expect(page).to have_checked_field(
+ t('in_person_proofing.form.state_id.same_address_as_id_no'),
+ visible: false,
+ )
+ end
+
+ it 'updates their previous selection from "No" TO "Yes,
+ I live at the address on my state-issued ID"' do
+ complete_state_id_step(user, same_address_as_id: false)
+ # expect to be on address page
+ expect(page).to have_content(t('in_person_proofing.headings.address'))
+ # complete address step
+ complete_address_step(user)
+ complete_ssn_step(user)
+ # expect to be on verify page
+ expect(page).to have_content(t('headings.verify'))
+ expect(page).to have_current_path(idv_in_person_verify_info_path)
+ # click update state ID button on the verify page
+ click_button t('idv.buttons.change_state_id_label')
+ # expect to be on the state ID page
+ expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
+ # change address
+ fill_in t('in_person_proofing.form.state_id.address1'), with: ''
+ fill_in t('in_person_proofing.form.state_id.address1'), with: 'test update address'
+ # change response to Yes
+ choose t('in_person_proofing.form.state_id.same_address_as_id_yes')
+ click_button t('forms.buttons.submit.update')
+ # expect to be back on verify page
+ expect(page).to have_content(t('headings.verify'))
+ expect(page).to have_current_path(idv_in_person_verify_info_path)
+ # expect to see state ID address update on verify twice
+ expect(page).to have_text('test update address').twice # for state id addr and addr update
+ # click update state ID button on the verify page
+ click_button t('idv.buttons.change_state_id_label')
+ # expect to be on the state ID page
+ expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
+ expect(page).to have_checked_field(
+ t('in_person_proofing.form.state_id.same_address_as_id_yes'),
+ visible: false,
+ )
+ end
+ end
+ end
+
+ context 'Validation' do
+ it 'validates zip code input', allow_browser_log: true do
+ complete_steps_before_state_id_step
+
+ fill_out_state_id_form_ok(same_address_as_id: true)
+ fill_in t('in_person_proofing.form.state_id.zipcode'), with: ''
+ fill_in t('in_person_proofing.form.state_id.zipcode'), with: 'invalid input'
+ expect(page).to have_field(t('in_person_proofing.form.state_id.zipcode'), with: '')
+
+ # enter valid characters, but invalid length
+ fill_in t('in_person_proofing.form.state_id.zipcode'), with: '123'
+ click_idv_continue
+ expect(page).to have_css('.usa-error-message', text: t('idv.errors.pattern_mismatch.zipcode'))
+
+ # enter a valid zip and make sure we can continue
+ fill_in t('in_person_proofing.form.state_id.zipcode'), with: '123456789'
+ expect(page).to have_field(t('in_person_proofing.form.state_id.zipcode'), with: '12345-6789')
+ click_idv_continue
+ expect(page).to have_current_path(idv_in_person_ssn_url)
+ end
+
+ it 'shows error for dob under minimum age', allow_browser_log: true do
+ complete_steps_before_state_id_step
+
+ fill_in t('components.memorable_date.month'), with: '1'
+ fill_in t('components.memorable_date.day'), with: '1'
+ fill_in t('components.memorable_date.year'), with: Time.zone.now.strftime('%Y')
+ click_idv_continue
+ expect(page).to have_content(
+ t(
+ 'in_person_proofing.form.state_id.memorable_date.errors.date_of_birth.range_min_age',
+ app_name: APP_NAME,
+ ),
+ )
+
+ year = (Time.zone.now - 13.years).strftime('%Y')
+ fill_in t('components.memorable_date.year'), with: year
+ click_idv_continue
+ expect(page).not_to have_content(
+ t(
+ 'in_person_proofing.form.state_id.memorable_date.errors.date_of_birth.range_min_age',
+ app_name: APP_NAME,
+ ),
+ )
+ end
+ end
+
+ context 'Transliterable Validation' do
+ before(:each) do
+ allow(IdentityConfig.store).to receive(:usps_ipp_transliteration_enabled).
+ and_return(true)
+ end
+
+ it 'shows validation errors',
+ allow_browser_log: true do
+ complete_steps_before_state_id_step
+
+ fill_out_state_id_form_ok
+ fill_in t('in_person_proofing.form.state_id.first_name'), with: 'T0mmy "Lee"'
+ fill_in t('in_person_proofing.form.state_id.last_name'), with: 'Джейкоб'
+ fill_in t('in_person_proofing.form.state_id.address1'), with: '#1 $treet'
+ fill_in t('in_person_proofing.form.state_id.address2'), with: 'Gr@nd Lañe^'
+ fill_in t('in_person_proofing.form.state_id.city'), with: 'N3w C!ty'
+ click_idv_continue
+
+ expect(page).to have_content(
+ I18n.t(
+ 'in_person_proofing.form.state_id.errors.unsupported_chars',
+ char_list: '", 0',
+ ),
+ )
+
+ expect(page).to have_content(
+ I18n.t(
+ 'in_person_proofing.form.state_id.errors.unsupported_chars',
+ char_list: 'Д, б, е, ж, й, к, о',
+ ),
+ )
+
+ expect(page).to have_content(
+ I18n.t(
+ 'in_person_proofing.form.state_id.errors.unsupported_chars',
+ char_list: '$',
+ ),
+ )
+
+ expect(page).to have_content(
+ I18n.t(
+ 'in_person_proofing.form.state_id.errors.unsupported_chars',
+ char_list: '@, ^',
+ ),
+ )
+
+ expect(page).to have_content(
+ I18n.t(
+ 'in_person_proofing.form.state_id.errors.unsupported_chars',
+ char_list: '!, 3',
+ ),
+ )
+
+ fill_in t('in_person_proofing.form.state_id.first_name'),
+ with: InPersonHelper::GOOD_FIRST_NAME
+ fill_in t('in_person_proofing.form.state_id.last_name'),
+ with: InPersonHelper::GOOD_LAST_NAME
+ fill_in t('in_person_proofing.form.state_id.address1'),
+ with: InPersonHelper::GOOD_IDENTITY_DOC_ADDRESS1
+ fill_in t('in_person_proofing.form.state_id.address2'),
+ with: InPersonHelper::GOOD_IDENTITY_DOC_ADDRESS2
+ fill_in t('in_person_proofing.form.state_id.city'),
+ with: InPersonHelper::GOOD_IDENTITY_DOC_CITY
+ click_idv_continue
+
+ expect(page).to have_current_path(idv_in_person_step_path(step: :address), wait: 10)
+ end
+ end
+
+ context 'State selection' do
+ it 'shows address hint when user selects state that has a specific hint',
+ allow_browser_log: true do
+ complete_steps_before_state_id_step
+
+ # state id page
+ select 'Puerto Rico',
+ from: t('in_person_proofing.form.state_id.identity_doc_address_state')
+
+ expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address1_hint'))
+ expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address2_hint'))
+
+ # change state selection
+ fill_out_state_id_form_ok(same_address_as_id: true)
+ expect(page).not_to have_content(I18n.t('in_person_proofing.form.state_id.address1_hint'))
+ expect(page).not_to have_content(I18n.t('in_person_proofing.form.state_id.address2_hint'))
+
+ # re-select puerto rico
+ select 'Puerto Rico',
+ from: t('in_person_proofing.form.state_id.identity_doc_address_state')
+ click_idv_continue
+
+ # ssn page
+ expect(page).to have_current_path(idv_in_person_ssn_url)
+ complete_ssn_step
+
+ # verify page
+ expect(page).to have_current_path(idv_in_person_verify_info_path)
+ expect(page).to have_text('PR')
+
+ # update state ID
+ click_button t('idv.buttons.change_state_id_label')
+
+ expect(page).to have_content(t('in_person_proofing.headings.update_state_id'))
+ expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address1_hint'))
+ expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address2_hint'))
+ end
+
+ it 'shows id number hint when user selects issuing state that has a specific hint',
+ allow_browser_log: true do
+ complete_steps_before_state_id_step
+
+ # expect default hint to be present
+ expect(page).to have_content(t('in_person_proofing.form.state_id.state_id_number_hint'))
+
+ select 'Texas',
+ from: t('in_person_proofing.form.state_id.state_id_jurisdiction')
+ expect(page).to have_content(t('in_person_proofing.form.state_id.state_id_number_texas_hint'))
+ expect(page).not_to have_content(t('in_person_proofing.form.state_id.state_id_number_hint'))
+
+ select 'Florida',
+ from: t('in_person_proofing.form.state_id.state_id_jurisdiction')
+ expect(page).not_to have_content(
+ t('in_person_proofing.form.state_id.state_id_number_texas_hint'),
+ )
+ expect(page).not_to have_content(t('in_person_proofing.form.state_id.state_id_number_hint'))
+ expect(page).to have_content(
+ t('in_person_proofing.form.state_id.state_id_number_florida_hint'),
+ )
+
+ # select a state without a state specific hint
+ select 'Ohio',
+ from: t('in_person_proofing.form.state_id.state_id_jurisdiction')
+ expect(page).to have_content(t('in_person_proofing.form.state_id.state_id_number_hint'))
+ expect(page).not_to have_content(
+ t('in_person_proofing.form.state_id.state_id_number_texas_hint'),
+ )
+ expect(page).not_to have_content(
+ t('in_person_proofing.form.state_id.state_id_number_florida_hint'),
+ )
+ end
end
end
diff --git a/spec/features/remember_device/totp_spec.rb b/spec/features/remember_device/totp_spec.rb
index 207b6fd6d62..f3de6d554bc 100644
--- a/spec/features/remember_device/totp_spec.rb
+++ b/spec/features/remember_device/totp_spec.rb
@@ -41,17 +41,29 @@ def remember_device_and_sign_out_user
context 'update totp' do
def remember_device_and_sign_out_user
+ auth_app_config = create(:auth_app_configuration, user:)
+ name = auth_app_config.name
+
sign_in_and_2fa_user(user)
visit account_two_factor_authentication_path
- page.find('.remove-auth-app').click # Delete
- click_on t('account.index.totp_confirm_delete')
+
+ click_link(
+ format(
+ '%s: %s',
+ t('two_factor_authentication.auth_app.manage_accessible_label'),
+ name,
+ ),
+ )
+
+ click_button t('two_factor_authentication.auth_app.delete')
+
travel_to(10.seconds.from_now) # Travel past the revoked at date from disabling the device
click_link t('account.index.auth_app_add'), href: authenticator_setup_url
fill_in_totp_name
fill_in :code, with: totp_secret_from_page
check t('forms.messages.remember_device')
click_submit_default
- expect(page).to have_current_path(account_two_factor_authentication_path)
+ expect(page).to have_current_path(account_path)
first(:button, t('links.sign_out')).click
user
end
diff --git a/spec/features/two_factor_authentication/sign_in_spec.rb b/spec/features/two_factor_authentication/sign_in_spec.rb
index 4f297b4941a..56a86cf4b21 100644
--- a/spec/features/two_factor_authentication/sign_in_spec.rb
+++ b/spec/features/two_factor_authentication/sign_in_spec.rb
@@ -587,7 +587,6 @@ def attempt_to_bypass_2fa
context 'sign in' do
it 'allows user to be signed in without issue' do
mock_webauthn_verification_challenge
-
sign_in_user(webauthn_configuration.user)
mock_successful_webauthn_authentication { click_webauthn_authenticate_button }
diff --git a/spec/features/users/totp_management_spec.rb b/spec/features/users/totp_management_spec.rb
index de496c4e0c5..49b43395fb2 100644
--- a/spec/features/users/totp_management_spec.rb
+++ b/spec/features/users/totp_management_spec.rb
@@ -4,18 +4,95 @@
context 'when the user has totp enabled' do
let(:user) { create(:user, :fully_registered, :with_authentication_app) }
- it 'allows the user to disable their totp app' do
+ it 'allows user to delete a platform authenticator when another 2FA option is set up' do
+ auth_app_config = create(:auth_app_configuration, user:)
+ name = auth_app_config.name
+
+ sign_in_and_2fa_user(user)
+ visit account_two_factor_authentication_path
+
+ expect(user.reload.auth_app_configurations.count).to eq(2)
+ expect(page).to have_content(name)
+
+ click_link(
+ format(
+ '%s: %s',
+ t('two_factor_authentication.auth_app.manage_accessible_label'),
+ name,
+ ),
+ )
+
+ expect(current_path).to eq(edit_auth_app_path(id: auth_app_config.id))
+
+ click_button t('two_factor_authentication.auth_app.delete')
+
+ expect(page).to have_content(t('two_factor_authentication.auth_app.deleted'))
+ expect(user.reload.auth_app_configurations.count).to eq(1)
+ end
+
+ it 'allows user to rename an authentication app app' do
+ auth_app_configuration = create(:auth_app_configuration, user:)
+ name = auth_app_configuration.name
+
sign_in_and_2fa_user(user)
visit account_two_factor_authentication_path
- expect(page).to have_content(t('two_factor_authentication.login_options.auth_app'))
- expect(page.find('.remove-auth-app')).to_not be_nil
- page.find('.remove-auth-app').click
+ expect(page).to have_content(name)
+
+ click_link(
+ format(
+ '%s: %s',
+ t('two_factor_authentication.auth_app.manage_accessible_label'),
+ name,
+ ),
+ )
+
+ expect(current_path).to eq(edit_auth_app_path(id: auth_app_configuration.id))
+ expect(page).to have_field(
+ t('two_factor_authentication.auth_app.nickname'),
+ with: name,
+ )
+
+ fill_in t('two_factor_authentication.auth_app.nickname'), with: 'new name'
+
+ click_button t('two_factor_authentication.auth_app.change_nickname')
+
+ expect(page).to have_content('new name')
+ expect(page).to have_content(t('two_factor_authentication.auth_app.renamed'))
+ end
+
+ it 'requires a user to use a unique name when renaming' do
+ existing_auth_app_configuration = create(:auth_app_configuration, user:, name: 'existing')
+ new_app_auth_configuration = create(:auth_app_configuration, user:, name: 'new existing')
+ name = existing_auth_app_configuration.name
+
+ sign_in_and_2fa_user(user)
+ visit account_two_factor_authentication_path
+
+ expect(page).to have_content(name)
+
+ click_link(
+ format(
+ '%s: %s',
+ t('two_factor_authentication.auth_app.manage_accessible_label'),
+ name,
+ ),
+ )
+
+ expect(current_path).to eq(edit_auth_app_path(id: existing_auth_app_configuration.id))
+ expect(page).to have_field(
+ t('two_factor_authentication.auth_app.nickname'),
+ with: name,
+ )
+
+ fill_in t('two_factor_authentication.auth_app.nickname'),
+ with: new_app_auth_configuration.name
+
+ click_button t('two_factor_authentication.auth_app.change_nickname')
- expect(current_path).to eq auth_app_delete_path
- click_on t('account.index.totp_confirm_delete')
+ expect(current_path).to eq(edit_auth_app_path(id: existing_auth_app_configuration.id))
- expect(current_path).to eq account_two_factor_authentication_path
+ expect(page).to have_content(t('errors.manage_authenticator.unique_name_error'))
end
end
diff --git a/spec/features/webauthn/hidden_spec.rb b/spec/features/webauthn/hidden_spec.rb
index 7567ee11224..7b1f67bae47 100644
--- a/spec/features/webauthn/hidden_spec.rb
+++ b/spec/features/webauthn/hidden_spec.rb
@@ -83,11 +83,27 @@
let(:user) { create(:user, :fully_registered, :with_webauthn_platform) }
context 'with javascript enabled', :js do
- it 'displays the authenticator option' do
- sign_in_user(user)
- click_on t('two_factor_authentication.login_options_link_text')
+ context ' with device that supports authenticator' do
+ it 'displays the authenticator option' do
+ sign_in_user(user)
+ click_on t('two_factor_authentication.login_options_link_text')
- expect(webauthn_option_hidden?).to eq(false)
+ expect(webauthn_option_hidden?).to eq(false)
+ end
+ end
+
+ context 'with device that doesnt support authenticator' do
+ it 'redirects to options page on sign in and shows the option' do
+ email ||= user.email_addresses.first.email
+ password = user.password
+ allow(UserMailer).to receive(:new_device_sign_in).and_call_original
+ visit new_user_session_path
+ set_hidden_field('platform_authenticator_available', 'false')
+ fill_in_credentials_and_submit(email, password)
+ continue_as(email, password)
+ expect(current_path).to eq(login_two_factor_options_path)
+ expect(webauthn_option_hidden?).to eq(false)
+ end
end
end
diff --git a/spec/features/webauthn/sign_in_spec.rb b/spec/features/webauthn/sign_in_spec.rb
index bd9440c192e..69f86e4c0fc 100644
--- a/spec/features/webauthn/sign_in_spec.rb
+++ b/spec/features/webauthn/sign_in_spec.rb
@@ -75,9 +75,33 @@
mock_webauthn_verification_challenge
sign_in_user(user)
+ expect(current_url).to eq(login_two_factor_webauthn_url(platform: true))
mock_cancelled_webauthn_authentication { click_webauthn_authenticate_button }
expect(page).to have_content(t('two_factor_authentication.webauthn_platform_header_text'))
end
+
+ context 'with device that doesnt support authenticator' do
+ before do
+ email ||= user.email_addresses.first.email
+ password = user.password
+ allow(UserMailer).to receive(:new_device_sign_in).and_call_original
+ visit new_user_session_path
+ set_hidden_field('platform_authenticator_available', 'false')
+ fill_in_credentials_and_submit(email, password)
+ continue_as(email, password)
+ end
+
+ it 'redirects to options page on sign in' do
+ expect(current_path).to eq(login_two_factor_options_path)
+ end
+
+ it 'allows user to go to options page and still select webauthn as their option' do
+ expect(current_path).to eq(login_two_factor_options_path)
+ select_2fa_option('webauthn_platform', visible: :all)
+ click_continue
+ expect(current_url).to eq(login_two_factor_webauthn_url(platform: true))
+ end
+ end
end
end
diff --git a/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_with_face_match_fail.json b/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_with_face_match_fail.json
new file mode 100644
index 00000000000..bba5ab5d30c
--- /dev/null
+++ b/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_with_face_match_fail.json
@@ -0,0 +1,1100 @@
+{
+ "Status": {
+ "ConversationId": "70000300394121",
+ "RequestId": "614507871",
+ "TransactionStatus": "passed",
+ "TransactionReasonCode": {
+ "Code": "trueid_pass",
+ "Description": "TRUEID PASS"
+ },
+ "Reference": "ca6e36c4-8a55-4831-aa8a-38d78b7c80e3"
+ },
+ "Products": [
+ {
+ "ProductType": "TrueID",
+ "ExecutedStepName": "True_ID_Step",
+ "ProductConfigurationName": "GSA2.V3.TrueID.CROP.PT.test",
+ "ProductStatus": "pass",
+ "ParameterDetails": [
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocumentName",
+ "Values": [{ "Value": "Maryland (MD) Driver's License - STAR" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocAuthResult",
+ "Values": [{ "Value": "Passed" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocAuthTamperResult",
+ "Values": [{ "Value": "Passed" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocAuthTamperSensitivity",
+ "Values": [{ "Value": "Normal" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocIssuerCode",
+ "Values": [{ "Value": "MD" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocIssuerName",
+ "Values": [{ "Value": "Maryland" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocIssuerType",
+ "Values": [{ "Value": "StateProvince" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocClassCode",
+ "Values": [{ "Value": "DriversLicense" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocClass",
+ "Values": [{ "Value": "DriversLicense" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocClassName",
+ "Values": [{ "Value": "Drivers License" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocIsGeneric",
+ "Values": [{ "Value": "false" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocIssue",
+ "Values": [{ "Value": "2016" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocIssueType",
+ "Values": [{ "Value": "Driver's License - STAR" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocSize",
+ "Values": [{ "Value": "ID1" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "ClassificationMode",
+ "Values": [{ "Value": "Automatic" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "OrientationChanged",
+ "Values": [{ "Value": "true" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "PresentationChanged",
+ "Values": [{ "Value": "false" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "Side",
+ "Values": [{ "Value": "Front" }, { "Value": "Back" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "GlareMetric",
+ "Values": [{ "Value": "100" }, { "Value": "100" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "SharpnessMetric",
+ "Values": [{ "Value": "65" }, { "Value": "65" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "IsTampered",
+ "Values": [{ "Value": "0" }, { "Value": "0" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "IsCropped",
+ "Values": [{ "Value": "1" }, { "Value": "1" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "HorizontalResolution",
+ "Values": [{ "Value": "600" }, { "Value": "600" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "VerticalResolution",
+ "Values": [{ "Value": "600" }, { "Value": "600" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "Light",
+ "Values": [{ "Value": "White" }, { "Value": "White" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "MimeType",
+ "Values": [
+ { "Value": "image/vnd.ms-photo" },
+ { "Value": "image/vnd.ms-photo" }
+ ]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "ImageMetrics_Id",
+ "Values": [
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "637aa4c6-eeb3-453f-899e-a56effcf3747" }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "FullName",
+ "Values": [{ "Value": "DAVID LICENSE SAMPLE" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Sex",
+ "Values": [{ "Value": "Male" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Age",
+ "Values": [{ "Value": "33" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DOB_Year",
+ "Values": [{ "Value": "1985" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DOB_Month",
+ "Values": [{ "Value": "7" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DOB_Day",
+ "Values": [{ "Value": "1" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "ExpirationDate_Year",
+ "Values": [{ "Value": "2099" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "ExpirationDate_Month",
+ "Values": [{ "Value": "10" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "ExpirationDate_Day",
+ "Values": [{ "Value": "15" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_1_AlertName",
+ "Values": [{ "Value": "Document Expired" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_2_AlertName",
+ "Values": [{ "Value": "Visible Pattern" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_3_AlertName",
+ "Values": [{ "Value": "Document Tampering Detection" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_4_AlertName",
+ "Values": [{ "Value": "2D Barcode Content" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_5_AlertName",
+ "Values": [{ "Value": "2D Barcode Read" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_6_AlertName",
+ "Values": [{ "Value": "Barcode Encoding" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_7_AlertName",
+ "Values": [{ "Value": "Birth Date Crosscheck" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_8_AlertName",
+ "Values": [{ "Value": "Birth Date Valid" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_9_AlertName",
+ "Values": [{ "Value": "Document Classification" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_10_AlertName",
+ "Values": [{ "Value": "Document Crosscheck Aggregation" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_11_AlertName",
+ "Values": [{ "Value": "Document Number Crosscheck" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_12_AlertName",
+ "Values": [{ "Value": "Document Tampering Detection" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_13_AlertName",
+ "Values": [{ "Value": "Document Tampering Detection" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_14_AlertName",
+ "Values": [{ "Value": "Document Tampering Detection" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_15_AlertName",
+ "Values": [{ "Value": "Expiration Date Crosscheck" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_16_AlertName",
+ "Values": [{ "Value": "Expiration Date Valid" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_17_AlertName",
+ "Values": [{ "Value": "Full Name Crosscheck" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_18_AlertName",
+ "Values": [{ "Value": "Issue Date Crosscheck" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_19_AlertName",
+ "Values": [{ "Value": "Issue Date Valid" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_20_AlertName",
+ "Values": [{ "Value": "Series Expired" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_21_AlertName",
+ "Values": [{ "Value": "Sex Crosscheck" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_22_AlertName",
+ "Values": [{ "Value": "Visible Pattern" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_23_AlertName",
+ "Values": [{ "Value": "Visible Pattern" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_24_AlertName",
+ "Values": [{ "Value": "Visible Pattern" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_3_Model",
+ "Values": [{ "Value": "Text Tampering Detection V1.3.1 (Beta)" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_12_Model",
+ "Values": [{ "Value": "Photo Tampering Detection V2.4" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_13_Model",
+ "Values": [{ "Value": "Text Tampering Detection V1.2.1" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_14_Model",
+ "Values": [{ "Value": "Physical Document Presence V2.5" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_1_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Checked if the document is expired."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_2_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified the presence of a pattern on the visible image."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_3_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Examines a document for evidence of tampering"
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_4_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Checked the contents of the two-dimensional barcode on the document."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_5_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Attention",
+ "Detail": "Verified that the two-dimensional barcode on the document was read successfully."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_6_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified the format of the barcode."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_7_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Compare the machine-readable birth date field to the human-readable birth date field."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_8_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified that the birth date is valid."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_9_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified that the type of document is supported and is able to be fully authenticated."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_10_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Compared the machine-readable fields to the human-readable fields."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_11_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Compare the machine-readable document number field to the human-readable document number field."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_12_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Examines a document for evidence of tampering"
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_13_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Examines a document for evidence of tampering"
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_14_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Examines a document for evidence of tampering"
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_15_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Compare the machine-readable expiration date field to the human-readable expiration date field."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_16_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified that the expiration date is valid."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_17_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Compare the machine-readable full name field to the human-readable full name field."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_18_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Compare the machine-readable issue date field to the human-readable issue date field."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_19_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified that the issue date is valid."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_20_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified whether the document type is still in circulation."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_21_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Compare the machine-readable sex field to the human-readable sex field."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_22_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified the presence of a pattern on the visible image."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_23_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified the presence of a pattern on the visible image."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_24_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified the presence of a pattern on the visible image."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_1_Disposition",
+ "Values": [{ "Value": "The document has expired" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_2_Disposition",
+ "Values": [{ "Value": "A visible pattern was not found" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_3_Disposition",
+ "Values": [
+ {
+ "Value": "Evidence suggests that the document may have been tampered with."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_4_Disposition",
+ "Values": [{ "Value": "The 2D barcode is formatted correctly" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_5_Disposition",
+ "Values": [{ "Value": "The 2D barcode was read successfully" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_6_Disposition",
+ "Values": [
+ {
+ "Value": "The barcode encoding is consistent with the expected encoding for the type"
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_7_Disposition",
+ "Values": [{ "Value": "The birth dates match" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_8_Disposition",
+ "Values": [{ "Value": "The birth date is valid" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_9_Disposition",
+ "Values": [{ "Value": "The document type is supported" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_10_Disposition",
+ "Values": [
+ {
+ "Value": "There are not a large number of differences between electronic and human-readable data sources"
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_11_Disposition",
+ "Values": [{ "Value": "The document numbers match" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_12_Disposition",
+ "Values": [
+ { "Value": "No evidence of document tampering was detected." }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_13_Disposition",
+ "Values": [
+ { "Value": "No evidence of document tampering was detected." }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_14_Disposition",
+ "Values": [
+ { "Value": "No evidence of document tampering was detected." }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_15_Disposition",
+ "Values": [{ "Value": "The expiration dates match" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_16_Disposition",
+ "Values": [{ "Value": "The expiration date is valid" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_17_Disposition",
+ "Values": [{ "Value": "The full names match" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_18_Disposition",
+ "Values": [{ "Value": "The issue dates match" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_19_Disposition",
+ "Values": [{ "Value": "The issue date is valid" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_20_Disposition",
+ "Values": [{ "Value": "The series has not expired" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_21_Disposition",
+ "Values": [{ "Value": "The sexes match" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_22_Disposition",
+ "Values": [{ "Value": "A visible pattern was found" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_23_Disposition",
+ "Values": [{ "Value": "A visible pattern was found" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_24_Disposition",
+ "Values": [{ "Value": "A visible pattern was found" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_2_Regions",
+ "Values": [{ "Value": "Background" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_3_Regions",
+ "Values": [
+ { "Value": "Address" },
+ { "Value": "Birth Date" },
+ { "Value": "Document Number" },
+ { "Value": "Expiration Date" },
+ { "Value": "Full Name" },
+ { "Value": "Issue Date" }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_12_Regions",
+ "Values": [{ "Value": "Photo" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_13_Regions",
+ "Values": [
+ { "Value": "Address" },
+ { "Value": "Birth Date" },
+ { "Value": "Document Number" },
+ { "Value": "Expiration Date" },
+ { "Value": "Full Name" },
+ { "Value": "Issue Date" }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_22_Regions",
+ "Values": [{ "Value": "Expires Label" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_23_Regions",
+ "Values": [{ "Value": "USA" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_24_Regions",
+ "Values": [{ "Value": "Background Upper" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_2_Regions_Reference",
+ "Values": [{ "Value": "faacfb79-d0a1-4a8e-b868-20c604988e84" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_3_Regions_Reference",
+ "Values": [
+ { "Value": "c8be94b6-78ac-4e85-88cb-e17880371e4a" },
+ { "Value": "a8226d92-e62c-42a3-a206-ab7c3e3d9796" },
+ { "Value": "c2e18c41-e3de-46a8-abc7-3412015a6cef" },
+ { "Value": "80f8f290-daa0-47e2-828e-52106bb26f31" },
+ { "Value": "2c74b850-dd89-41bb-a21c-70ae0563ef77" },
+ { "Value": "63bf5053-f81f-493f-aff0-33c07d07a894" }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_12_Regions_Reference",
+ "Values": [{ "Value": "f29b1fe5-6482-4b39-8b4a-d91caf4ecb57" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_13_Regions_Reference",
+ "Values": [
+ { "Value": "c8be94b6-78ac-4e85-88cb-e17880371e4a" },
+ { "Value": "a8226d92-e62c-42a3-a206-ab7c3e3d9796" },
+ { "Value": "c2e18c41-e3de-46a8-abc7-3412015a6cef" },
+ { "Value": "80f8f290-daa0-47e2-828e-52106bb26f31" },
+ { "Value": "2c74b850-dd89-41bb-a21c-70ae0563ef77" },
+ { "Value": "63bf5053-f81f-493f-aff0-33c07d07a894" }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_22_Regions_Reference",
+ "Values": [{ "Value": "d55f2c66-f84f-4213-a660-4ff9e5d0fde5" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_23_Regions_Reference",
+ "Values": [{ "Value": "bbf6ba02-ee3f-4b5c-a5d0-2fdb39ac79f7" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_24_Regions_Reference",
+ "Values": [{ "Value": "20203cb8-f8a4-4a5b-999f-3700f73fe4fe" }]
+ },
+ {
+ "Group": "PORTRAIT_MATCH_RESULT",
+ "Name": "FaceMatchResult",
+ "Values": [{"Value": "Fail"}]
+ },
+ {
+ "Group": "PORTRAIT_MATCH_RESULT",
+ "Name": "FaceMatchScore",
+ "Values": [{"Value": "50"}]
+ },
+ {
+ "Group": "PORTRAIT_MATCH_RESULT",
+ "Name": "FaceStatusCode",
+ "Values": [{"Value": "1"}]
+ },
+ {
+ "Group": "PORTRAIT_MATCH_RESULT",
+ "Name": "FaceErrorMessage",
+ "Values": [{"Value": "Successful. Liveness: Live"}]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_FullName",
+ "Values": [{ "Value": "DAVID LICENSE SAMPLE" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_Surname",
+ "Values": [{ "Value": "SAMPLE" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_GivenName",
+ "Values": [{ "Value": "DAVID LICENSE" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_FirstName",
+ "Values": [{ "Value": "DAVID" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_MiddleName",
+ "Values": [{ "Value": "LICENSE" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_DOB_Year",
+ "Values": [{ "Value": "1986" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_DOB_Month",
+ "Values": [{ "Value": "7" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_DOB_Day",
+ "Values": [{ "Value": "1" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_DocumentClassName",
+ "Values": [{ "Value": "Drivers License" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_DocumentNumber",
+ "Values": [{ "Value": "M555555555555" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_ExpirationDate_Year",
+ "Values": [{ "Value": "2099" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_ExpirationDate_Month",
+ "Values": [{ "Value": "10" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_xpirationDate_Day",
+ "Values": [{ "Value": "15" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_IssuingStateCode",
+ "Values": [{ "Value": "MD" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_IssuingStateName",
+ "Values": [{ "Value": "Maryland" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_CountryCode",
+ "Values": [{ "Value": "USA" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_Address",
+ "Values": [
+ {
+ "Value": "123 ABC AVExE2x80xA8ANYTOWN, MD 12345"
+ }
+ ]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_AddressLine1",
+ "Values": [{ "Value": "123 ABC AVE" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_AddressLine2",
+ "Values": [{ "Value": "APT 3E" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_City",
+ "Values": [{ "Value": "ANYTOWN" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_State",
+ "Values": [{ "Value": "MD" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_PostalCode",
+ "Values": [{ "Value": "12345" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_Height",
+ "Values": [{ "Value": "5' 9\"" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_IssueDate_Year",
+ "Values": [{ "Value": "2016" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_IssueDate_Month",
+ "Values": [{ "Value": "10" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_IssueDate_Day",
+ "Values": [{ "Value": "15" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_LicenseClass",
+ "Values": [{ "Value": "C" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_LicenseRestrictions",
+ "Values": [{ "Value": "B" }]
+ },
+ {
+ "Group": "DOCUMENT_REGION",
+ "Name": "DocumentRegion_Id",
+ "Values": [
+ { "Value": "ce2cf0e2-5373-4ec2-84e8-7fe44a01642b" },
+ { "Value": "0b4f4f2b-cbd6-43e9-ac67-55bf2bdd9df5" },
+ { "Value": "c8be94b6-78ac-4e85-88cb-e17880371e4a" },
+ { "Value": "a0fdf00c-071c-4d8e-81af-8af0fc7688b8" },
+ { "Value": "faacfb79-d0a1-4a8e-b868-20c604988e84" },
+ { "Value": "3362ad4b-a36b-487e-826c-c748c7b04e8d" },
+ { "Value": "20203cb8-f8a4-4a5b-999f-3700f73fe4fe" },
+ { "Value": "a8226d92-e62c-42a3-a206-ab7c3e3d9796" },
+ { "Value": "a3e3a625-8b0e-4deb-a91e-86afc55d036b" },
+ { "Value": "cda4092d-9208-4871-bbc1-1b732c299d26" },
+ { "Value": "42770baf-3a4a-4477-9e5f-4400237273fe" },
+ { "Value": "c2e18c41-e3de-46a8-abc7-3412015a6cef" },
+ { "Value": "b36594f2-d19c-48aa-98c2-2b4f8429744f" },
+ { "Value": "80f8f290-daa0-47e2-828e-52106bb26f31" },
+ { "Value": "d55f2c66-f84f-4213-a660-4ff9e5d0fde5" },
+ { "Value": "26ca0c85-01ab-4311-bfd5-d27d2ee975eb" },
+ { "Value": "0af79f76-1542-4391-ad17-a5d6169be57f" },
+ { "Value": "2c74b850-dd89-41bb-a21c-70ae0563ef77" },
+ { "Value": "64e8ee97-2b24-452d-a5af-1627951aa737" },
+ { "Value": "63bf5053-f81f-493f-aff0-33c07d07a894" },
+ { "Value": "f29b1fe5-6482-4b39-8b4a-d91caf4ecb57" },
+ { "Value": "35442fb1-c3bb-4f2f-ad9e-537a8f0e7e0f" },
+ { "Value": "29964031-e072-4204-a9ae-b2b7122bfdc1" },
+ { "Value": "a3bbdf8b-62f5-438f-bf72-091bb2f6f0ff" },
+ { "Value": "42d0c49e-fc7a-45da-8f6d-8896c4b5267f" },
+ { "Value": "5430efcb-523b-4c62-a190-e9aa7eea4ebd" },
+ { "Value": "bbf6ba02-ee3f-4b5c-a5d0-2fdb39ac79f7" },
+ { "Value": "0686341c-0b3f-4544-840e-58822120ef06" }
+ ]
+ },
+ {
+ "Group": "DOCUMENT_REGION",
+ "Name": "DocumentRegion_Key",
+ "Values": [
+ { "Value": "1D Barcode" },
+ { "Value": "2D Barcode" },
+ { "Value": "Address" },
+ { "Value": "Alaska Validator" },
+ { "Value": "Background" },
+ { "Value": "Background Lower" },
+ { "Value": "Background Upper" },
+ { "Value": "Birth Date" },
+ { "Value": "Birth Date" },
+ { "Value": "DOB Label" },
+ { "Value": "DOB Label Text" },
+ { "Value": "Document Number" },
+ { "Value": "Document Type" },
+ { "Value": "Expiration Date" },
+ { "Value": "Expires Label" },
+ { "Value": "Expires Label Position" },
+ { "Value": "Eye Color" },
+ { "Value": "Full Name" },
+ { "Value": "Height" },
+ { "Value": "Issue Date" },
+ { "Value": "Photo" },
+ { "Value": "Photo Printing" },
+ { "Value": "Secondary Photo" },
+ { "Value": "Sex" },
+ { "Value": "Sex Height Labels" },
+ { "Value": "Signature" },
+ { "Value": "USA" },
+ { "Value": "Weight" }
+ ]
+ },
+ {
+ "Group": "DOCUMENT_REGION",
+ "Name": "DocumentRegion_ImageReference",
+ "Values": [
+ { "Value": "637aa4c6-eeb3-453f-899e-a56effcf3747" },
+ { "Value": "637aa4c6-eeb3-453f-899e-a56effcf3747" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "637aa4c6-eeb3-453f-899e-a56effcf3747" },
+ { "Value": "637aa4c6-eeb3-453f-899e-a56effcf3747" },
+ { "Value": "637aa4c6-eeb3-453f-899e-a56effcf3747" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" }
+ ]
+ }
+ ]
+ },
+ {
+ "ProductType": "TrueID_Decision",
+ "ExecutedStepName": "Decision",
+ "ProductConfigurationName": "TRUEID_PASS",
+ "ProductStatus": "pass",
+ "ProductReason": {
+ "Code": "trueid_pass",
+ "Description": "TRUEID PASS"
+ }
+ }
+ ]
+}
diff --git a/spec/forms/two_factor_authentication/auth_app_delete_form_spec.rb b/spec/forms/two_factor_authentication/auth_app_delete_form_spec.rb
new file mode 100644
index 00000000000..3e8c3f15dcb
--- /dev/null
+++ b/spec/forms/two_factor_authentication/auth_app_delete_form_spec.rb
@@ -0,0 +1,89 @@
+require 'rails_helper'
+
+RSpec.describe TwoFactorAuthentication::AuthAppDeleteForm do
+ let(:user) { create(:user) }
+ let(:configuration) { create(:auth_app_configuration, user:) }
+ let(:configuration_id) { configuration&.id }
+ let(:form) { described_class.new(user:, configuration_id:) }
+
+ describe '#submit' do
+ let(:result) { form.submit }
+
+ context 'with having another mfa enabled' do
+ let(:user) { create(:user, :with_phone) }
+
+ it 'returns a successful result' do
+ expect(result.success?).to eq(true)
+ expect(result.to_h).to eq(success: true, configuration_id:)
+ end
+
+ context 'with blank configuration' do
+ let(:configuration) { nil }
+
+ it 'returns an unsuccessful result' do
+ expect(result.success?).to eq(false)
+ expect(result.to_h).to eq(
+ success: false,
+ error_details: {
+ configuration_id: { configuration_not_found: true },
+ },
+ configuration_id:,
+ )
+ end
+ end
+
+ context 'with configuration that does not exist' do
+ let(:configuration_id) { 'does-not-exist' }
+
+ it 'returns an unsuccessful result' do
+ expect(result.success?).to eq(false)
+ expect(result.to_h).to eq(
+ success: false,
+ error_details: {
+ configuration_id: { configuration_not_found: true },
+ },
+ configuration_id:,
+ )
+ end
+ end
+
+ context 'with configuration not belonging to the user' do
+ let(:configuration) { create(:auth_app_configuration) }
+
+ it 'returns an unsuccessful result' do
+ expect(result.success?).to eq(false)
+ expect(result.to_h).to eq(
+ success: false,
+ error_details: {
+ configuration_id: { configuration_not_found: true },
+ },
+ configuration_id:,
+ )
+ end
+ end
+ end
+
+ context 'with user not having another mfa enabled' do
+ let(:user) { create(:user) }
+
+ it 'returns an unsuccessful result' do
+ expect(result.success?).to eq(false)
+ expect(result.to_h).to eq(
+ success: false,
+ error_details: {
+ configuration_id: { only_method: true },
+ },
+ configuration_id:,
+ )
+ end
+ end
+ end
+
+ describe '#configuration' do
+ subject(:form_configuration) { form.configuration }
+
+ it 'returns configuration' do
+ expect(form_configuration).to eq(configuration)
+ end
+ end
+end
diff --git a/spec/forms/two_factor_authentication/auth_app_update_form_spec.rb b/spec/forms/two_factor_authentication/auth_app_update_form_spec.rb
new file mode 100644
index 00000000000..87d1dd46948
--- /dev/null
+++ b/spec/forms/two_factor_authentication/auth_app_update_form_spec.rb
@@ -0,0 +1,142 @@
+require 'rails_helper'
+
+RSpec.describe TwoFactorAuthentication::AuthAppUpdateForm do
+ let(:user) { create(:user) }
+ let(:original_name) { 'original-name' }
+ let(:configuration) { create(:auth_app_configuration, user:, name: original_name) }
+ let(:configuration_id) { configuration&.id }
+ let(:form) { described_class.new(user:, configuration_id:) }
+
+ describe '#submit' do
+ let(:name) { 'new-namae' }
+ let(:result) { form.submit(name:) }
+
+ it 'returns a successful result' do
+ expect(result.success?).to eq(true)
+ expect(result.to_h).to eq(success: true, configuration_id:)
+ end
+
+ it 'saves the new name' do
+ result
+
+ expect(configuration.reload.name).to eq(name)
+ end
+
+ context 'with blank configuration' do
+ let(:configuration) { nil }
+
+ it 'returns an unsuccessful result' do
+ expect(result.success?).to eq(false)
+ expect(result.to_h).to eq(
+ success: false,
+ error_details: {
+ configuration_id: { configuration_not_found: true },
+ },
+ configuration_id:,
+ )
+ end
+ end
+
+ context 'with configuration that does not exist' do
+ let(:configuration_id) { 'does-not-exist' }
+
+ it 'returns an unsuccessful result' do
+ expect(result.success?).to eq(false)
+ expect(result.to_h).to eq(
+ success: false,
+ error_details: {
+ configuration_id: { configuration_not_found: true },
+ },
+ configuration_id:,
+ )
+ end
+ end
+
+ context 'with configuration not belonging to the user' do
+ let(:configuration) { create(:auth_app_configuration, name: original_name) }
+
+ it 'returns an unsuccessful result' do
+ expect(result.success?).to eq(false)
+ expect(result.to_h).to eq(
+ success: false,
+ error_details: {
+ configuration_id: { configuration_not_found: true },
+ },
+ configuration_id:,
+ )
+ end
+
+ it 'does not save the new name' do
+ expect(configuration).not_to receive(:save)
+
+ result
+
+ expect(configuration.reload.name).to eq(original_name)
+ end
+ end
+
+ context 'with blank name' do
+ let(:name) { '' }
+
+ it 'returns an unsuccessful result' do
+ expect(result.success?).to eq(false)
+ expect(result.to_h).to eq(
+ success: false,
+ error_details: {
+ name: { blank: true },
+ },
+ configuration_id:,
+ )
+ end
+
+ it 'does not save the new name' do
+ expect(configuration).not_to receive(:save)
+
+ result
+
+ expect(configuration.reload.name).to eq(original_name)
+ end
+ end
+
+ context 'with duplicate name' do
+ before do
+ create(:auth_app_configuration, user:, name:)
+ end
+
+ it 'returns an unsuccessful result' do
+ expect(result.success?).to eq(false)
+ expect(result.to_h).to eq(
+ success: false,
+ error_details: {
+ name: { duplicate: true },
+ },
+ configuration_id:,
+ )
+ end
+
+ it 'does not save the new name' do
+ expect(configuration).not_to receive(:save)
+
+ result
+
+ expect(configuration.reload.name).to eq(original_name)
+ end
+ end
+ end
+
+ describe '#name' do
+ subject(:name) { form.name }
+
+ it 'returns configuration name' do
+ expect(name).to eq(configuration.name)
+ end
+ end
+
+ describe '#configuration' do
+ subject(:form_configuration) { form.configuration }
+
+ it 'returns configuration' do
+ expect(form_configuration).to eq(configuration)
+ end
+ end
+end
diff --git a/spec/forms/webauthn_setup_form_spec.rb b/spec/forms/webauthn_setup_form_spec.rb
index e4680ec6795..e467c404ec0 100644
--- a/spec/forms/webauthn_setup_form_spec.rb
+++ b/spec/forms/webauthn_setup_form_spec.rb
@@ -171,7 +171,11 @@
expect(subject.submit(protocol, params).to_h).to eq(
success: false,
- errors: {},
+ errors: { name: [I18n.t(
+ 'errors.webauthn_setup.general_error_html',
+ link_html: I18n.t('errors.webauthn_setup.additional_methods_link'),
+ )] },
+ error_details: { name: { attestation_error: true } },
**extra_attributes,
)
end
@@ -213,8 +217,8 @@
expect(subject.submit(protocol, params).to_h).to eq(
success: false,
errors: { name: [I18n.t(
- 'errors.webauthn_setup.attestation_error',
- link: MarketingSite.contact_url,
+ 'errors.webauthn_setup.general_error_html',
+ link_html: I18n.t('errors.webauthn_setup.additional_methods_link'),
)] },
error_details: { name: { attestation_error: true } },
**extra_attributes,
diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb
index 7c9e0965fbf..ca5ac68f823 100644
--- a/spec/jobs/resolution_proofing_job_spec.rb
+++ b/spec/jobs/resolution_proofing_job_spec.rb
@@ -17,6 +17,7 @@
let(:threatmetrix_session_id) { SecureRandom.uuid }
let(:proofing_device_profiling) { :enabled }
let(:lexisnexis_threatmetrix_mock_enabled) { false }
+ let(:ipp_enrollment_in_progress) { false }
before do
allow(IdentityConfig.store).to receive(:proofing_device_profiling).
@@ -40,6 +41,7 @@
user_id: user.id,
threatmetrix_session_id: threatmetrix_session_id,
request_ip: request_ip,
+ ipp_enrollment_in_progress: ipp_enrollment_in_progress,
)
end
@@ -118,6 +120,7 @@
user_id: user.id,
threatmetrix_session_id: threatmetrix_session_id,
request_ip: request_ip,
+ ipp_enrollment_in_progress: ipp_enrollment_in_progress,
)
end
it 'stores a successful result' do
@@ -378,6 +381,7 @@
context "when the user's state ID address does not match their residential address" do
let(:pii) { Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS }
+ let(:ipp_enrollment_in_progress) { true }
let(:residential_address) do
{
@@ -411,7 +415,7 @@
user_id: user.id,
threatmetrix_session_id: threatmetrix_session_id,
request_ip: request_ip,
- ipp_enrollment_in_progress: true,
+ ipp_enrollment_in_progress: ipp_enrollment_in_progress,
)
end
@@ -510,6 +514,7 @@
context 'without a threatmetrix session ID' do
let(:threatmetrix_session_id) { nil }
+ let(:ipp_enrollment_in_progress) { false }
it 'does not make a request to threatmetrix' do
stub_vendor_requests
diff --git a/spec/models/disposable_domain_spec.rb b/spec/models/disposable_domain_spec.rb
deleted file mode 100644
index 3572d1b0859..00000000000
--- a/spec/models/disposable_domain_spec.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe DisposableDomain do
- let(:domain) { 'temporary.com' }
-
- describe '.disposable?' do
- before do
- DisposableDomain.create(name: domain)
- end
-
- context 'when the domain exists' do
- it 'returns true' do
- expect(DisposableDomain.disposable?(domain)).to eq true
- end
- end
-
- context 'when the domain does not exist' do
- it 'returns false' do
- expect(DisposableDomain.disposable?('example.com')).to eq false
- end
- end
-
- context 'with bad data' do
- it 'returns false' do
- expect(DisposableDomain.disposable?('')).to eq false
- expect(DisposableDomain.disposable?(nil)).to eq false
- expect(DisposableDomain.disposable?({})).to eq false
- end
- end
- end
-end
diff --git a/spec/services/doc_auth/lexis_nexis/requests/true_id_request_spec.rb b/spec/services/doc_auth/lexis_nexis/requests/true_id_request_spec.rb
index 86da30ce531..b16b387df7d 100644
--- a/spec/services/doc_auth/lexis_nexis/requests/true_id_request_spec.rb
+++ b/spec/services/doc_auth/lexis_nexis/requests/true_id_request_spec.rb
@@ -228,7 +228,7 @@ def response_body(include_liveness)
[
Group: 'PORTRAIT_MATCH_RESULT',
Name: 'FaceMatchResult',
- Values: [{ Value: 'Success' }],
+ Values: [{ Value: 'Pass' }],
]
end
),
@@ -262,7 +262,7 @@ def response_body_with_doc_auth_errors(include_liveness)
[
Group: 'PORTRAIT_MATCH_RESULT',
Name: 'FaceMatchResult',
- Values: [{ Value: 'Success' }],
+ Values: [{ Value: 'Pass' }],
]
end
),
diff --git a/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb b/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb
index 838d010d97b..6e95a753411 100644
--- a/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb
+++ b/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb
@@ -2,40 +2,27 @@
RSpec.describe DocAuth::LexisNexis::Responses::TrueIdResponse do
let(:success_response_body) { LexisNexisFixtures.true_id_response_success_3 }
- let(:success_with_liveness_response_body) do
- LexisNexisFixtures.true_id_response_success_with_liveness
- end
let(:success_response) do
instance_double(Faraday::Response, status: 200, body: success_response_body)
end
+ # rubocop:disable Layout/LineLength
let(:success_with_liveness_response) do
- instance_double(Faraday::Response, status: 200, body: success_with_liveness_response_body)
- end
- let(:failure_body_no_liveness) { LexisNexisFixtures.true_id_response_failure_no_liveness }
- let(:failure_body_with_liveness) { LexisNexisFixtures.true_id_response_failure_with_liveness }
- let(:failure_body_with_all_failures) do
- LexisNexisFixtures.true_id_response_failure_with_all_failures
+ instance_double(Faraday::Response, status: 200, body: LexisNexisFixtures.true_id_response_success_with_liveness)
end
- let(:failure_body_no_liveness_low_dpi) do
- LexisNexisFixtures.true_id_response_failure_no_liveness_low_dpi
+ let(:failure_response_face_match_fail) do
+ instance_double(Faraday::Response, status: 200, body: LexisNexisFixtures.true_id_response_with_face_match_fail)
end
-
- let(:failure_body_tampering) do
- LexisNexisFixtures.true_id_response_failure_tampering
- end
-
- # rubocop:disable Layout/LineLength
let(:failure_response_no_liveness) do
- instance_double(Faraday::Response, status: 200, body: failure_body_no_liveness)
+ instance_double(Faraday::Response, status: 200, body: LexisNexisFixtures.true_id_response_failure_no_liveness)
end
let(:failure_response_with_liveness) do
- instance_double(Faraday::Response, status: 200, body: failure_body_with_liveness)
+ instance_double(Faraday::Response, status: 200, body: LexisNexisFixtures.true_id_response_failure_with_liveness)
end
let(:failure_response_tampering) do
- instance_double(Faraday::Response, status: 200, body: failure_body_tampering)
+ instance_double(Faraday::Response, status: 200, body: LexisNexisFixtures.true_id_response_failure_tampering)
end
let(:failure_response_with_all_failures) do
- instance_double(Faraday::Response, status: 200, body: failure_body_with_all_failures)
+ instance_double(Faraday::Response, status: 200, body: LexisNexisFixtures.true_id_response_failure_with_all_failures)
end
let(:communications_error_response) do
instance_double(Faraday::Response, status: 200, body: LexisNexisFixtures.communications_error)
@@ -53,7 +40,7 @@
instance_double(Faraday::Response, status: 200, body: LexisNexisFixtures.true_id_barcode_read_attention)
end
let(:failure_response_no_liveness_low_dpi) do
- instance_double(Faraday::Response, status: 200, body: failure_body_no_liveness_low_dpi)
+ instance_double(Faraday::Response, status: 200, body: LexisNexisFixtures.true_id_response_failure_no_liveness_low_dpi)
end
# rubocop:enable Layout/LineLength
@@ -692,4 +679,40 @@ def get_decision_product(resp)
end
end
end
+
+ describe '#successful_result?' do
+ context 'when all checks other than selfie pass' do
+ context 'and selfie check is enabled' do
+ liveness_checking_enabled = true
+
+ it 'returns true with a passing selfie' do
+ response = described_class.new(
+ success_with_liveness_response, config, liveness_checking_enabled
+ )
+
+ expect(response.successful_result?).to eq(true)
+ end
+
+ it 'returns false with a failing selfie' do
+ response = described_class.new(
+ failure_response_face_match_fail, config, liveness_checking_enabled
+ )
+
+ expect(response.successful_result?).to eq(false)
+ end
+ end
+
+ context 'and selfie check is disabled' do
+ liveness_checking_enabled = false
+
+ it 'returns true no matter what the value of selfie is' do
+ response = described_class.new(
+ failure_response_face_match_fail, config, liveness_checking_enabled
+ )
+
+ expect(response.successful_result?).to eq(true)
+ end
+ end
+ end
+ end
end
diff --git a/spec/services/idv/agent_spec.rb b/spec/services/idv/agent_spec.rb
index 43de10130a4..86c46ab809f 100644
--- a/spec/services/idv/agent_spec.rb
+++ b/spec/services/idv/agent_spec.rb
@@ -15,6 +15,7 @@
let(:issuer) { 'fake-issuer' }
let(:friendly_name) { 'fake-name' }
let(:app_id) { 'fake-app-id' }
+ let(:ipp_enrollment_in_progress) { false }
let(:agent) { Idv::Agent.new(applicant) }
@@ -41,6 +42,7 @@
user_id: user.id,
threatmetrix_session_id: nil,
request_ip: request_ip,
+ ipp_enrollment_in_progress: ipp_enrollment_in_progress,
)
result = document_capture_session.load_proofing_result.result
@@ -57,6 +59,7 @@
user_id: user.id,
threatmetrix_session_id: nil,
request_ip: request_ip,
+ ipp_enrollment_in_progress: ipp_enrollment_in_progress,
)
result = document_capture_session.load_proofing_result.result
expect(result[:context][:stages][:state_id]).to include(
@@ -82,6 +85,7 @@
user_id: user.id,
threatmetrix_session_id: nil,
request_ip: request_ip,
+ ipp_enrollment_in_progress: ipp_enrollment_in_progress,
)
result = document_capture_session.load_proofing_result.result
expect(result[:errors][:ssn]).to eq ['Unverified SSN.']
@@ -97,6 +101,7 @@
user_id: user.id,
threatmetrix_session_id: nil,
request_ip: request_ip,
+ ipp_enrollment_in_progress: ipp_enrollment_in_progress,
)
result = document_capture_session.load_proofing_result.result
@@ -118,6 +123,7 @@
user_id: user.id,
threatmetrix_session_id: nil,
request_ip: request_ip,
+ ipp_enrollment_in_progress: ipp_enrollment_in_progress,
)
result = document_capture_session.load_proofing_result.result
@@ -142,6 +148,7 @@
user_id: user.id,
threatmetrix_session_id: nil,
request_ip: request_ip,
+ ipp_enrollment_in_progress: ipp_enrollment_in_progress,
)
result = document_capture_session.load_proofing_result.result
@@ -151,6 +158,32 @@
timed_out: true,
)
end
+
+ context 'successfully proofs in IPP flow' do
+ let(:ipp_enrollment_in_progress) { true }
+
+ it 'returns a successful result' do
+ addr = Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS
+ agent = Idv::Agent.new(addr.merge(uuid: user.uuid))
+ agent.proof_resolution(
+ document_capture_session,
+ should_proof_state_id: true,
+ trace_id: trace_id,
+ user_id: user.id,
+ threatmetrix_session_id: nil,
+ request_ip: request_ip,
+ ipp_enrollment_in_progress: ipp_enrollment_in_progress,
+ )
+ result = document_capture_session.load_proofing_result.result
+ expect(result[:context][:stages][:state_id]).to include(
+ transaction_id: Proofing::Mock::StateIdMockClient::TRANSACTION_ID,
+ errors: {},
+ exception: nil,
+ success: true,
+ timed_out: false,
+ )
+ end
+ end
end
describe '#proof_address' do
diff --git a/spec/services/proofing/resolution/progressive_proofer_spec.rb b/spec/services/proofing/resolution/progressive_proofer_spec.rb
index 337528cfff9..8fb731b6759 100644
--- a/spec/services/proofing/resolution/progressive_proofer_spec.rb
+++ b/spec/services/proofing/resolution/progressive_proofer_spec.rb
@@ -1,10 +1,9 @@
require 'rails_helper'
RSpec.describe Proofing::Resolution::ProgressiveProofer do
- let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS }
+ let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN }
let(:should_proof_state_id) { true }
- let(:ipp_enrollment_in_progress) { true }
- let(:double_address_verification) { true }
+ let(:ipp_enrollment_in_progress) { false }
let(:request_ip) { Faker::Internet.ip_v4_address }
let(:threatmetrix_session_id) { SecureRandom.uuid }
let(:timer) { JobHelpers::Timer.new }
@@ -32,6 +31,21 @@
zipcode: applicant_pii[:zipcode],
}
end
+ let(:transformed_pii) do
+ {
+ first_name: 'FAKEY',
+ last_name: 'MCFAKERSON',
+ dob: '1938-10-06',
+ address1: '123 Way St',
+ address2: '2nd Address Line',
+ city: 'Best City',
+ zipcode: '12345',
+ state_id_jurisdiction: 'Virginia',
+ address_state: 'VA',
+ state_id_number: '1111111111111',
+ same_address_as_id: 'true',
+ }
+ end
describe '#proof' do
before do
@@ -42,7 +56,6 @@
instance.proof(
applicant_pii: applicant_pii,
ipp_enrollment_in_progress: ipp_enrollment_in_progress,
- double_address_verification: double_address_verification,
request_ip: request_ip,
should_proof_state_id: should_proof_state_id,
threatmetrix_session_id: threatmetrix_session_id,
@@ -51,40 +64,63 @@
)
end
- it 'returns a ResultAdjudicator' do
- proofing_result = proof
+ context 'remote proofing' do
+ it 'returns a ResultAdjudicator' do
+ proofing_result = proof
- expect(proofing_result).to be_an_instance_of(Proofing::Resolution::ResultAdjudicator)
- expect(proofing_result.same_address_as_id).to eq(applicant_pii[:same_address_as_id])
- end
+ expect(proofing_result).to be_an_instance_of(Proofing::Resolution::ResultAdjudicator)
+ expect(proofing_result.same_address_as_id).to eq(nil)
+ end
- let(:resolution_result) do
- instance_double(Proofing::Resolution::Result)
- end
- context 'ThreatMetrix is enabled' do
- let(:threatmetrix_proofer) { instance_double(Proofing::LexisNexis::Ddp::Proofer) }
-
- before do
- allow(FeatureManagement).to receive(:proofing_device_profiling_collecting_enabled?).
- and_return(true)
- allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_mock_enabled).
- and_return(false)
- allow(instance).to receive(:lexisnexis_ddp_proofer).and_return(threatmetrix_proofer)
-
- allow(instance).to receive(:proof_id_address_with_lexis_nexis_if_needed).
- and_return(resolution_result)
- allow(resolution_result).to receive(:success?).and_return(true)
- allow(instant_verify_proofer).to receive(:proof)
+ let(:resolution_result) do
+ instance_double(Proofing::Resolution::Result)
end
+ context 'ThreatMetrix is enabled' do
+ let(:threatmetrix_proofer) { instance_double(Proofing::LexisNexis::Ddp::Proofer) }
+
+ before do
+ allow(FeatureManagement).to receive(:proofing_device_profiling_collecting_enabled?).
+ and_return(true)
+ allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_mock_enabled).
+ and_return(false)
+ allow(instance).to receive(:lexisnexis_ddp_proofer).and_return(threatmetrix_proofer)
- it 'makes a request to the ThreatMetrix proofer' do
- expect(threatmetrix_proofer).to receive(:proof)
+ allow(instance).to receive(:proof_id_address_with_lexis_nexis_if_needed).
+ and_return(resolution_result)
+ allow(resolution_result).to receive(:success?).and_return(true)
+ allow(instant_verify_proofer).to receive(:proof)
+ end
+
+ it 'makes a request to the ThreatMetrix proofer' do
+ expect(threatmetrix_proofer).to receive(:proof)
+
+ subject
+ end
- subject
+ context 'it lacks a session id' do
+ let(:threatmetrix_session_id) { nil }
+ it 'returns a disabled result' do
+ result = subject
+
+ device_profiling_result = result.device_profiling_result
+
+ expect(device_profiling_result.success).to be(true)
+ expect(device_profiling_result.client).to eq('tmx_disabled')
+ expect(device_profiling_result.review_status).to eq('pass')
+ end
+ end
end
- context 'it lacks a session id' do
- let(:threatmetrix_session_id) { nil }
+ context 'ThreatMetrix is disabled' do
+ before do
+ allow(FeatureManagement).to receive(:proofing_device_profiling_collecting_enabled?).
+ and_return(false)
+
+ allow(instance).to receive(:proof_id_address_with_lexis_nexis_if_needed).
+ and_return(resolution_result)
+ allow(resolution_result).to receive(:success?).and_return(true)
+ allow(instant_verify_proofer).to receive(:proof)
+ end
it 'returns a disabled result' do
result = subject
@@ -95,363 +131,358 @@
expect(device_profiling_result.review_status).to eq('pass')
end
end
- end
- context 'ThreatMetrix is disabled' do
- before do
- allow(FeatureManagement).to receive(:proofing_device_profiling_collecting_enabled?).
- and_return(false)
+ context 'LexisNexis Instant Verify A/B test enabled' do
+ let(:residential_instant_verify_proof) do
+ instance_double(Proofing::Resolution::Result)
+ end
+ let(:instant_verify_workflow) { 'equitable_workflow' }
+ let(:ab_test_variables) do
+ {
+ ab_testing_enabled: true,
+ use_alternate_workflow: true,
+ instant_verify_workflow: instant_verify_workflow,
+ }
+ end
+
+ before do
+ allow(instant_verify_proofer).to receive(:proof).
+ and_return(residential_instant_verify_proof)
+ allow(residential_instant_verify_proof).to receive(:success?).and_return(true)
+ end
- allow(instance).to receive(:proof_id_address_with_lexis_nexis_if_needed).
- and_return(resolution_result)
- allow(resolution_result).to receive(:success?).and_return(true)
- allow(instant_verify_proofer).to receive(:proof)
+ it 'uses the selected workflow' do
+ lniv = Idv::LexisNexisInstantVerify.new(dcs_uuid)
+ expect(lniv).to receive(:workflow_ab_testing_variables).
+ and_return(ab_test_variables)
+ expect(Idv::LexisNexisInstantVerify).to receive(:new).
+ and_return(lniv)
+ expect(Proofing::LexisNexis::InstantVerify::Proofer).to receive(:new).
+ with(hash_including(instant_verify_workflow: instant_verify_workflow)).
+ and_return(instant_verify_proofer)
+
+ proof
+ end
end
- it 'returns a disabled result' do
- result = subject
- device_profiling_result = result.device_profiling_result
+ context 'remote flow does not augment pii' do
+ let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) }
+ let(:id_address_instant_verify_proof) do
+ instance_double(Proofing::Resolution::Result)
+ end
- expect(device_profiling_result.success).to be(true)
- expect(device_profiling_result.client).to eq('tmx_disabled')
- expect(device_profiling_result.review_status).to eq('pass')
- end
- end
+ before do
+ allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer)
+ allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer)
+ allow(instant_verify_proofer).to receive(:proof).
+ and_return(id_address_instant_verify_proof)
+ allow(id_address_instant_verify_proof).to receive(:success?).and_return(true)
+ end
- context 'LexisNexis Instant Verify A/B test enabled' do
- let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID }
- let(:residential_instant_verify_proof) do
- instance_double(Proofing::Resolution::Result)
- end
- let(:instant_verify_workflow) { 'equitable_workflow' }
- let(:ab_test_variables) do
- {
- ab_testing_enabled: true,
- use_alternate_workflow: true,
- instant_verify_workflow: instant_verify_workflow,
- }
- end
+ it 'proofs with untransformed pii' do
+ expect(aamva_proofer).to receive(:proof).with(applicant_pii)
- before do
- allow(instant_verify_proofer).to receive(:proof).
- and_return(residential_instant_verify_proof)
- allow(residential_instant_verify_proof).to receive(:success?).and_return(true)
- end
+ result = subject
- it 'uses the selected workflow' do
- lniv = Idv::LexisNexisInstantVerify.new(dcs_uuid)
- expect(lniv).to receive(:workflow_ab_testing_variables).
- and_return(ab_test_variables)
- expect(Idv::LexisNexisInstantVerify).to receive(:new).
- and_return(lniv)
- expect(Proofing::LexisNexis::InstantVerify::Proofer).to receive(:new).
- with(hash_including(instant_verify_workflow: instant_verify_workflow)).
- and_return(instant_verify_proofer)
-
- proof
+ expect(result.same_address_as_id).to eq(nil)
+ expect(result.ipp_enrollment_in_progress).to eq(false)
+ # rubocop:disable Layout/LineLength
+ expect(result.residential_resolution_result.vendor_name).to eq('ResidentialAddressNotRequired')
+ # rubocop:enable Layout/LineLength
+ end
end
end
- context 'residential address and id address are the same' do
+ context 'ipp flow' do
+ let(:ipp_enrollment_in_progress) { true }
let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID }
- let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) }
- let(:residential_instant_verify_proof) do
- instance_double(Proofing::Resolution::Result)
- end
- before do
- allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer)
- allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer)
- allow(instant_verify_proofer).to receive(:proof).
- and_return(residential_instant_verify_proof)
- allow(residential_instant_verify_proof).to receive(:success?).and_return(true)
- end
- it 'only makes one request to LexisNexis InstantVerify' do
- expect(instant_verify_proofer).to receive(:proof).exactly(:once)
- expect(aamva_proofer).to receive(:proof)
+ it 'returns a ResultAdjudicator' do
+ proofing_result = proof
- subject
+ expect(proofing_result).to be_an_instance_of(Proofing::Resolution::ResultAdjudicator)
+ expect(proofing_result.same_address_as_id).to eq(applicant_pii[:same_address_as_id])
end
- it 'produces a result adjudicator with correct information' do
- expect(aamva_proofer).to receive(:proof)
-
- result = subject
-
- expect(result.same_address_as_id).to eq('true')
- expect(result.ipp_enrollment_in_progress).to eq(true)
- expect(result.double_address_verification).to eq(true)
- expect(result.resolution_result).to eq(result.residential_resolution_result)
- end
-
- context 'LexisNexis InstantVerify fails' do
- let(:result_that_failed_instant_verify) do
+ context 'residential address and id address are the same' do
+ let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) }
+ let(:residential_instant_verify_proof) do
instance_double(Proofing::Resolution::Result)
end
before do
- allow(instance).to receive(:proof_id_address_with_lexis_nexis_if_needed).
- and_return(result_that_failed_instant_verify)
- allow(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)).
- and_return(result_that_failed_instant_verify)
- allow(instance).to receive(:user_can_pass_after_state_id_check?).
- with(result_that_failed_instant_verify).
- and_return(true)
- allow(result_that_failed_instant_verify).to receive(:success?).
- and_return(false)
+ allow(instance).to receive(:with_state_id_address).and_return(transformed_pii)
+ allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer)
+ allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer)
+ allow(instant_verify_proofer).to receive(:proof).
+ and_return(residential_instant_verify_proof)
+ allow(residential_instant_verify_proof).to receive(:success?).and_return(true)
end
- context 'the failure can be covered by AAMVA' do
- before do
- allow(result_that_failed_instant_verify).
- to receive(:attributes_requiring_additional_verification).
- and_return([:address])
- end
+ it 'only makes one request to LexisNexis InstantVerify' do
+ expect(instant_verify_proofer).to receive(:proof).exactly(:once)
+ expect(aamva_proofer).to receive(:proof)
- context 'it is not covered by AAMVA' do
- let(:failed_aamva_proof) { instance_double(Proofing::StateIdResult) }
- before do
- allow(aamva_proofer).to receive(:proof).and_return(failed_aamva_proof)
- allow(failed_aamva_proof).to receive(:verified_attributes).and_return([])
- allow(failed_aamva_proof).to receive(:success?).and_return(false)
- end
- it 'indicates the aamva check did not pass' do
- result = subject
+ subject
+ end
- expect(result.state_id_result.success?).to eq(false)
- end
- end
+ it 'produces a result adjudicator with correct information' do
+ expect(aamva_proofer).to receive(:proof)
- context 'it is covered by AAMVA' do
- let(:successful_aamva_proof) { instance_double(Proofing::StateIdResult) }
- before do
- allow(aamva_proofer).to receive(:proof).and_return(successful_aamva_proof)
- allow(successful_aamva_proof).to receive(:verified_attributes).
- and_return([:address])
- allow(successful_aamva_proof).to receive(:success?).and_return(true)
- end
- it 'indicates aamva did pass' do
- result = subject
+ result = subject
+ expect(result.same_address_as_id).to eq('true')
+ expect(result.ipp_enrollment_in_progress).to eq(true)
+ expect(result.resolution_result).to eq(result.residential_resolution_result)
+ end
- expect(result.state_id_result.success?).to eq(true)
- end
- end
+ it 'transforms PII correctly' do
+ expect(aamva_proofer).to receive(:proof).with(transformed_pii)
+
+ result = subject
+ expect(result.same_address_as_id).to eq('true')
+ expect(result.ipp_enrollment_in_progress).to eq(true)
+ expect(result.resolution_result).to eq(result.residential_resolution_result)
+ expect(result.resolution_result.success?).to eq(true)
end
- end
- context 'LexisNexis InstantVerify passes for residential address and id address' do
- context 'should proof with AAMVA' do
- let(:id_resolution_that_passed_instant_verify) do
+ context 'LexisNexis InstantVerify fails' do
+ let(:result_that_failed_instant_verify) do
instance_double(Proofing::Resolution::Result)
end
- let(:residential_resolution_that_passed_instant_verify) do
- instance_double(Proofing::Resolution::Result)
- end
-
before do
- allow(instance).to receive(:proof_residential_address_if_needed).
- and_return(residential_resolution_that_passed_instant_verify)
allow(instance).to receive(:proof_id_address_with_lexis_nexis_if_needed).
- and_return(id_resolution_that_passed_instant_verify)
- allow(instant_verify_proofer).to receive(:proof).
- with(hash_including(state_id_address)).
- and_return(id_resolution_that_passed_instant_verify)
+ and_return(result_that_failed_instant_verify)
+ allow(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)).
+ and_return(result_that_failed_instant_verify)
allow(instance).to receive(:user_can_pass_after_state_id_check?).
- with(id_resolution_that_passed_instant_verify).
- and_return(true)
- allow(id_resolution_that_passed_instant_verify).to receive(:success?).
- and_return(true)
- allow(residential_resolution_that_passed_instant_verify).to receive(:success?).
+ with(result_that_failed_instant_verify).
and_return(true)
+ allow(result_that_failed_instant_verify).to receive(:success?).
+ and_return(false)
end
- it 'makes a request to the AAMVA proofer' do
- expect(aamva_proofer).to receive(:proof)
+ context 'the failure can be covered by AAMVA' do
+ before do
+ allow(result_that_failed_instant_verify).
+ to receive(:attributes_requiring_additional_verification).
+ and_return([:address])
+ end
- subject
+ context 'it is not covered by AAMVA' do
+ let(:failed_aamva_proof) { instance_double(Proofing::StateIdResult) }
+ before do
+ allow(aamva_proofer).to receive(:proof).and_return(failed_aamva_proof)
+ allow(failed_aamva_proof).to receive(:verified_attributes).and_return([])
+ allow(failed_aamva_proof).to receive(:success?).and_return(false)
+ end
+ it 'indicates the aamva check did not pass' do
+ result = subject
+
+ expect(result.state_id_result.success?).to eq(false)
+ end
+ end
+
+ context 'it is covered by AAMVA' do
+ let(:successful_aamva_proof) { instance_double(Proofing::StateIdResult) }
+ before do
+ allow(aamva_proofer).to receive(:proof).and_return(successful_aamva_proof)
+ allow(successful_aamva_proof).to receive(:verified_attributes).
+ and_return([:address])
+ allow(successful_aamva_proof).to receive(:success?).and_return(true)
+ end
+ it 'indicates aamva did pass' do
+ result = subject
+
+ expect(result.state_id_result.success?).to eq(true)
+ end
+ end
end
+ end
- context 'AAMVA proofing fails' do
- let(:aamva_client) { instance_double(Proofing::Aamva::VerificationClient) }
- let(:failed_aamva_proof) do
- instance_double(Proofing::StateIdResult)
+ context 'LexisNexis InstantVerify passes for residential address and id address' do
+ context 'should proof with AAMVA' do
+ let(:id_resolution_that_passed_instant_verify) do
+ instance_double(Proofing::Resolution::Result)
+ end
+ let(:residential_resolution_that_passed_instant_verify) do
+ instance_double(Proofing::Resolution::Result)
end
+
before do
- allow(Proofing::Aamva::VerificationClient).to receive(:new).and_return(aamva_client)
- allow(failed_aamva_proof).to receive(:success?).and_return(false)
+ allow(instance).to receive(:proof_residential_address_if_needed).
+ and_return(residential_resolution_that_passed_instant_verify)
+ allow(instance).to receive(:proof_id_address_with_lexis_nexis_if_needed).
+ and_return(id_resolution_that_passed_instant_verify)
+ allow(instant_verify_proofer).to receive(:proof).
+ with(hash_including(state_id_address)).
+ and_return(id_resolution_that_passed_instant_verify)
+ allow(instance).to receive(:user_can_pass_after_state_id_check?).
+ with(id_resolution_that_passed_instant_verify).
+ and_return(true)
+ allow(id_resolution_that_passed_instant_verify).to receive(:success?).
+ and_return(true)
+ allow(residential_resolution_that_passed_instant_verify).to receive(:success?).
+ and_return(true)
end
- it 'returns a result adjudicator that indicates the aamva proofing failed' do
- allow(aamva_proofer).to receive(:proof).and_return(failed_aamva_proof)
- result = subject
+ it 'makes a request to the AAMVA proofer' do
+ expect(aamva_proofer).to receive(:proof)
- expect(result.state_id_result.success?).to eq(false)
+ subject
+ end
+
+ context 'AAMVA proofing fails' do
+ let(:aamva_client) { instance_double(Proofing::Aamva::VerificationClient) }
+ let(:failed_aamva_proof) do
+ instance_double(Proofing::StateIdResult)
+ end
+ before do
+ allow(Proofing::Aamva::VerificationClient).to receive(:new).and_return(aamva_client)
+ allow(failed_aamva_proof).to receive(:success?).and_return(false)
+ end
+ it 'returns a result adjudicator that indicates the aamva proofing failed' do
+ allow(aamva_proofer).to receive(:proof).and_return(failed_aamva_proof)
+
+ result = subject
+
+ expect(result.state_id_result.success?).to eq(false)
+ end
end
end
end
end
- end
-
- context 'residential address and id address are different' do
- let(:residential_address_proof) do
- instance_double(Proofing::Resolution::Result)
- end
- let(:resolution_result) do
- instance_double(Proofing::Resolution::Result)
- end
- let(:ipp_enrollment_in_progress) { true }
- let(:double_address_verification) { true }
- let(:applicant_pii) do
- JSON.parse(<<-STR, symbolize_names: true)
- {
- "uuid": "3e8db152-4d35-4207-b828-3eee8c52c50f",
- "middle_name": "",
- "phone": "",
- "state_id_expiration": "2029-01-01",
- "state_id_issued": "MI",
- "first_name": "Imaginary",
- "last_name": "Person",
- "dob": "1999-09-00",
- "identity_doc_address1": "1 Seaview",
- "identity_doc_address2": "",
- "identity_doc_city": "Sant Cruz",
- "identity_doc_zipcode": "91000",
- "state_id_jurisdiction": "AZ",
- "identity_doc_address_state": "CA",
- "state_id_number": "AZ333222111",
- "same_address_as_id": "false",
- "state": "MI",
- "zipcode": "48880",
- "city": "Pontiac",
- "address1": "1 Mobile Dr",
- "address2": "",
- "ssn": "900-32-1898",
- "state_id_type": "drivers_license",
- "uuid_prefix": null
- }
- STR
- end
- let(:residential_address) do
- {
- address1: applicant_pii[:address1],
- address2: applicant_pii[:address2],
- city: applicant_pii[:city],
- state: applicant_pii[:state],
- state_id_jurisdiction: applicant_pii[:state_id_jurisdiction],
- zipcode: applicant_pii[:zipcode],
- }
- end
- let(:state_id_address) do
- {
- address1: applicant_pii[:identity_doc_address1],
- address2: applicant_pii[:identity_doc_address2],
- city: applicant_pii[:identity_doc_city],
- state: applicant_pii[:identity_doc_address_state],
- state_id_jurisdiction: applicant_pii[:state_id_jurisdiction],
- zipcode: applicant_pii[:identity_doc_zipcode],
- }
- end
- context 'LexisNexis InstantVerify passes for residential address' do
- before do
- allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer)
- allow(instant_verify_proofer).to receive(:proof).and_return(residential_address_proof)
- allow(residential_address_proof).to receive(:success?).and_return(true)
+ context 'residential address and id address are different' do
+ let(:residential_address_proof) do
+ instance_double(Proofing::Resolution::Result)
+ end
+ let(:resolution_result) do
+ instance_double(Proofing::Resolution::Result)
+ end
+ let(:ipp_enrollment_in_progress) { true }
+ let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS }
+ let(:residential_address) do
+ {
+ address1: applicant_pii[:address1],
+ address2: applicant_pii[:address2],
+ city: applicant_pii[:city],
+ state: applicant_pii[:state],
+ state_id_jurisdiction: applicant_pii[:state_id_jurisdiction],
+ zipcode: applicant_pii[:zipcode],
+ }
+ end
+ let(:state_id_address) do
+ {
+ address1: applicant_pii[:identity_doc_address1],
+ address2: applicant_pii[:identity_doc_address2],
+ city: applicant_pii[:identity_doc_city],
+ state: applicant_pii[:identity_doc_address_state],
+ state_id_jurisdiction: applicant_pii[:state_id_jurisdiction],
+ zipcode: applicant_pii[:identity_doc_zipcode],
+ }
end
- context 'LexisNexis InstantVerify passes for id address' do
- it 'makes two requests to the InstantVerify Proofer' do
- expect(instant_verify_proofer).to receive(:proof).
- with(hash_including(residential_address)).
- ordered
- expect(instant_verify_proofer).to receive(:proof).
- with(hash_including(state_id_address)).
- ordered
-
- subject
+ context 'LexisNexis InstantVerify passes for residential address' do
+ before do
+ allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer)
+ allow(instant_verify_proofer).to receive(:proof).and_return(residential_address_proof)
+ allow(residential_address_proof).to receive(:success?).and_return(true)
end
- context 'AAMVA fails' do
- let(:failed_aamva_proof) { instance_double(Proofing::StateIdResult) }
- let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) }
- before do
- allow(instance).to receive(:proof_id_with_aamva_if_needed).
- and_return(failed_aamva_proof)
- allow(aamva_proofer).to receive(:proof).and_return(failed_aamva_proof)
- allow(failed_aamva_proof).to receive(:success?).and_return(false)
- allow(resolution_result).to receive(:errors)
- end
+ context 'LexisNexis InstantVerify passes for id address' do
+ it 'makes two requests to the InstantVerify Proofer' do
+ expect(instant_verify_proofer).to receive(:proof).
+ with(hash_including(residential_address)).
+ ordered
+ expect(instant_verify_proofer).to receive(:proof).
+ with(hash_including(state_id_address)).
+ ordered
- it 'returns the correct resolution results' do
- result_adjudicator = subject
+ subject
+ end
- expect(result_adjudicator.residential_resolution_result.success?).to be(true)
- expect(result_adjudicator.resolution_result.success?).to be(true)
- expect(result_adjudicator.state_id_result.success?).to be(false)
+ context 'AAMVA fails' do
+ let(:failed_aamva_proof) { instance_double(Proofing::StateIdResult) }
+ let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) }
+ before do
+ allow(instance).to receive(:proof_id_with_aamva_if_needed).
+ and_return(failed_aamva_proof)
+ allow(aamva_proofer).to receive(:proof).and_return(failed_aamva_proof)
+ allow(failed_aamva_proof).to receive(:success?).and_return(false)
+ allow(resolution_result).to receive(:errors)
+ end
+
+ it 'returns the correct resolution results' do
+ result_adjudicator = subject
+
+ expect(result_adjudicator.residential_resolution_result.success?).to be(true)
+ expect(result_adjudicator.resolution_result.success?).to be(true)
+ expect(result_adjudicator.state_id_result.success?).to be(false)
+ end
end
end
end
- end
-
- context 'LexisNexis InstantVerify fails for residential address' do
- let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) }
-
- before do
- allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer)
- allow(instance).to receive(:proof_residential_address_if_needed).
- and_return(residential_address_proof)
- allow(instant_verify_proofer).to receive(:proof).
- with(hash_including(residential_address)).
- and_return(residential_address_proof)
- allow(instance).to receive(:user_can_pass_after_state_id_check?).
- with(residential_address_proof).
- and_return(false)
- allow(residential_address_proof).to receive(:success?).
- and_return(false)
- end
- it 'does not make unnecessary calls' do
- expect(aamva_proofer).to_not receive(:proof)
- expect(instant_verify_proofer).to_not receive(:proof).
- with(hash_including(state_id_address))
+ context 'LexisNexis InstantVerify fails for residential address' do
+ let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) }
- subject
- end
- end
+ before do
+ allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer)
+ allow(instance).to receive(:proof_residential_address_if_needed).
+ and_return(residential_address_proof)
+ allow(instant_verify_proofer).to receive(:proof).
+ with(hash_including(residential_address)).
+ and_return(residential_address_proof)
+ allow(instance).to receive(:user_can_pass_after_state_id_check?).
+ with(residential_address_proof).
+ and_return(false)
+ allow(residential_address_proof).to receive(:success?).
+ and_return(false)
+ end
- context 'LexisNexis InstantVerify fails for id address & passes for residential address' do
- let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) }
- let(:result_that_failed_instant_verify) do
- instance_double(Proofing::Resolution::Result)
- end
+ it 'does not make unnecessary calls' do
+ expect(aamva_proofer).to_not receive(:proof)
+ expect(instant_verify_proofer).to_not receive(:proof).
+ with(hash_including(state_id_address))
- before do
- allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer)
- allow(instance).to receive(:proof_id_address_with_lexis_nexis_if_needed).
- and_return(result_that_failed_instant_verify)
- allow(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)).
- and_return(result_that_failed_instant_verify)
+ subject
+ end
end
- context 'the failure can be covered by AAMVA' do
- let(:failed_aamva_proof) { instance_double(Proofing::StateIdResult) }
+ context 'LexisNexis InstantVerify fails for id address & passes for residential address' do
let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) }
- before do
- allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer)
- allow(instant_verify_proofer).to receive(:proof).and_return(residential_address_proof)
- allow(residential_address_proof).to receive(:success?).and_return(true)
+ let(:result_that_failed_instant_verify) do
+ instance_double(Proofing::Resolution::Result)
+ end
- allow(instance).to receive(:user_can_pass_after_state_id_check?).
- with(result_that_failed_instant_verify).
- and_return(true)
- allow(result_that_failed_instant_verify).
- to receive(:attributes_requiring_additional_verification).
- and_return([:address])
+ before do
allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer)
+ allow(instance).to receive(:proof_id_address_with_lexis_nexis_if_needed).
+ and_return(result_that_failed_instant_verify)
+ allow(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)).
+ and_return(result_that_failed_instant_verify)
end
- it 'calls AAMVA' do
- expect(aamva_proofer).to receive(:proof)
- subject
+ context 'the failure can be covered by AAMVA' do
+ let(:failed_aamva_proof) { instance_double(Proofing::StateIdResult) }
+ let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) }
+ before do
+ allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer)
+ allow(instant_verify_proofer).to receive(:proof).and_return(residential_address_proof)
+ allow(residential_address_proof).to receive(:success?).and_return(true)
+
+ allow(instance).to receive(:user_can_pass_after_state_id_check?).
+ with(result_that_failed_instant_verify).
+ and_return(true)
+ allow(result_that_failed_instant_verify).
+ to receive(:attributes_requiring_additional_verification).
+ and_return([:address])
+ allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer)
+ end
+ it 'calls AAMVA' do
+ expect(aamva_proofer).to receive(:proof)
+
+ subject
+ end
end
end
end
diff --git a/spec/services/proofing/resolution/result_adjudicator_spec.rb b/spec/services/proofing/resolution/result_adjudicator_spec.rb
index 603226a7ca6..7da807de820 100644
--- a/spec/services/proofing/resolution/result_adjudicator_spec.rb
+++ b/spec/services/proofing/resolution/result_adjudicator_spec.rb
@@ -29,7 +29,6 @@
end
let(:should_proof_state_id) { true }
- let(:double_address_verification) { true }
let(:ipp_enrollment_in_progress) { true }
let(:same_address_as_id) { 'false' }
@@ -52,7 +51,6 @@
state_id_result: state_id_result,
should_proof_state_id: should_proof_state_id,
ipp_enrollment_in_progress: ipp_enrollment_in_progress,
- double_address_verification: double_address_verification,
device_profiling_result: device_profiling_result,
same_address_as_id: same_address_as_id,
)
@@ -93,33 +91,6 @@
expect(resolution_adjudication_reason).to eq(:fail_state_id)
end
end
-
- # rubocop:disable Layout/LineLength
- context 'Confirm adjudication works for either double_address_verification or ipp_enrollment_in_progress' do
- context 'Adjudication passes if double_address_verification is false and ipp_enrollment_in_progress is true' do
- # rubocop:enable Layout/LineLength
- let(:double_address_verification) { false }
- let(:ipp_enrollment_in_progress) { true }
-
- it 'returns a successful response' do
- result = subject.adjudicated_result
-
- expect(result.success?).to eq(true)
- end
- end
- # rubocop:disable Layout/LineLength
- context 'Adjudication passes if ipp_enrollment_in_progress is false and double_address_verification is true' do
- # rubocop:enable Layout/LineLength
- let(:double_address_verification) { true }
- let(:ipp_enrollment_in_progress) { false }
-
- it 'returns a successful response' do
- result = subject.adjudicated_result
-
- expect(result.success?).to eq(true)
- end
- end
- end
end
end
end
diff --git a/spec/support/features/in_person_helper.rb b/spec/support/features/in_person_helper.rb
index 367a0ec9839..f8967ef86a5 100644
--- a/spec/support/features/in_person_helper.rb
+++ b/spec/support/features/in_person_helper.rb
@@ -147,6 +147,15 @@ def complete_verify_step(_user = nil)
click_idv_submit_default
end
+ def complete_steps_before_state_id_step
+ sign_in_and_2fa_user
+ begin_in_person_proofing
+ complete_prepare_step
+ complete_location_step
+
+ expect(page).to have_current_path(idv_in_person_step_path(step: :state_id), wait: 10)
+ end
+
def complete_all_in_person_proofing_steps(user = user_with_2fa, same_address_as_id: true)
complete_prepare_step(user)
complete_location_step(user)
diff --git a/spec/support/features/session_helper.rb b/spec/support/features/session_helper.rb
index 2c43879c70e..ed1e55d8d72 100644
--- a/spec/support/features/session_helper.rb
+++ b/spec/support/features/session_helper.rb
@@ -2,6 +2,7 @@
module Features
module SessionHelper
+ include JavascriptDriverHelper
include PersonalKeyHelper
VALID_PASSWORD = 'Val!d Pass w0rd'.freeze
@@ -50,6 +51,7 @@ def sign_up_and_2fa_ial1_user
def signin(email, password)
allow(UserMailer).to receive(:new_device_sign_in).and_call_original
visit new_user_session_path
+ set_hidden_field('platform_authenticator_available', 'true')
fill_in_credentials_and_submit(email, password)
continue_as(email, password)
end
@@ -729,5 +731,14 @@ def expect_branded_experience
def acknowledge_backup_code_confirmation
click_on t('two_factor_authentication.backup_codes.saved_backup_codes')
end
+
+ def set_hidden_field(id, value)
+ input = first("input##{id}", visible: false)
+ if javascript_enabled?
+ input.execute_script("this.value = #{value.to_json}")
+ else
+ input.set(value)
+ end
+ end
end
end
diff --git a/spec/support/lexis_nexis_fixtures.rb b/spec/support/lexis_nexis_fixtures.rb
index 750c9a51baf..d1929aed7ce 100644
--- a/spec/support/lexis_nexis_fixtures.rb
+++ b/spec/support/lexis_nexis_fixtures.rb
@@ -164,6 +164,10 @@ def true_id_response_success_with_liveness
read_fixture_file_at_path('true_id/true_id_response_success_with_liveness.json')
end
+ def true_id_response_with_face_match_fail
+ read_fixture_file_at_path('true_id/true_id_response_with_face_match_fail.json')
+ end
+
def true_id_response_failure_no_liveness
read_fixture_file_at_path('true_id/true_id_response_failure_no_liveness.json')
end
diff --git a/spec/views/accounts/_auth_apps.html.erb_spec.rb b/spec/views/accounts/_auth_apps.html.erb_spec.rb
new file mode 100644
index 00000000000..3160151842f
--- /dev/null
+++ b/spec/views/accounts/_auth_apps.html.erb_spec.rb
@@ -0,0 +1,22 @@
+require 'rails_helper'
+
+RSpec.describe 'accounts/_auth_apps.html.erb' do
+ let(:user) do
+ create(
+ :user,
+ auth_app_configurations: create_list(:auth_app_configuration, 2),
+ )
+ end
+ let(:user_session) { { auth_events: [] } }
+
+ subject(:rendered) { render partial: 'accounts/auth_apps' }
+
+ before do
+ allow(view).to receive(:current_user).and_return(user)
+ allow(view).to receive(:user_session).and_return(user_session)
+ end
+
+ it 'renders a list of auth apps' do
+ expect(rendered).to have_selector('[role="list"] [role="list-item"]', count: 2)
+ end
+end
diff --git a/spec/views/accounts/show.html.erb_spec.rb b/spec/views/accounts/show.html.erb_spec.rb
index 23cf81fb0fb..22e8364a511 100644
--- a/spec/views/accounts/show.html.erb_spec.rb
+++ b/spec/views/accounts/show.html.erb_spec.rb
@@ -109,25 +109,6 @@
end
end
- context 'auth app listing and adding' do
- context 'user has no auth app' do
- let(:user) { create(:user, :fully_registered, :with_piv_or_cac) }
-
- it 'does not render auth app' do
- expect(view).to_not render_template(partial: '_auth_apps')
- end
- end
-
- context 'user has an auth app' do
- let(:user) { create(:user, :fully_registered, :with_authentication_app) }
- it 'renders the auth app section' do
- render
-
- expect(view).to render_template(partial: '_auth_apps')
- end
- end
- end
-
context 'PIV/CAC listing and adding' do
context 'user has no piv/cac' do
let(:user) { create(:user, :fully_registered, :with_authentication_app) }
diff --git a/spec/views/accounts/two_factor_authentication/show.html.erb_spec.rb b/spec/views/accounts/two_factor_authentication/show.html.erb_spec.rb
index 66441dfbd63..66baa50c0e0 100644
--- a/spec/views/accounts/two_factor_authentication/show.html.erb_spec.rb
+++ b/spec/views/accounts/two_factor_authentication/show.html.erb_spec.rb
@@ -37,15 +37,6 @@
),
)
end
-
- it 'contains link to disable TOTP' do
- render
-
- expect(rendered).to have_link(
- t('forms.buttons.disable'),
- href: auth_app_delete_path(id: user.auth_app_configurations.first.id),
- )
- end
end
context 'when the user does not have password_reset_profile' do
diff --git a/spec/views/idv/welcome/show.html.erb_spec.rb b/spec/views/idv/welcome/show.html.erb_spec.rb
index c974f132235..12cd6916e6c 100644
--- a/spec/views/idv/welcome/show.html.erb_spec.rb
+++ b/spec/views/idv/welcome/show.html.erb_spec.rb
@@ -3,11 +3,15 @@
RSpec.describe 'idv/welcome/show.html.erb' do
let(:user_fully_authenticated) { true }
let(:sp_name) { nil }
+ let(:selfie_required) { false }
let(:user) { create(:user) }
before do
@decorated_sp_session = instance_double(ServiceProviderSession)
allow(@decorated_sp_session).to receive(:sp_name).and_return(sp_name)
+ allow(@decorated_sp_session).to receive(:selfie_required?).and_return(selfie_required)
+ @sp_name = @decorated_sp_session.sp_name || APP_NAME
+ @title = t('doc_auth.headings.welcome', sp_name: @sp_name)
allow(view).to receive(:decorated_sp_session).and_return(@decorated_sp_session)
allow(view).to receive(:user_fully_authenticated?).and_return(user_fully_authenticated)
allow(view).to receive(:user_signing_up?).and_return(false)
@@ -27,45 +31,29 @@
it 'renders a link to return to the SP' do
expect(rendered).to have_link(t('links.cancel'))
end
- end
-
- context 'without service provider' do
- it 'renders troubleshooting options' do
- render
- expect(rendered).to have_link(t('idv.troubleshooting.options.supported_documents'))
+ it 'renders the welcome template' do
+ expect(rendered).to have_content(@title)
+ expect(rendered).to have_content(t('doc_auth.instructions.getting_started'))
+ expect(rendered).to have_content(t('doc_auth.instructions.bullet1'))
expect(rendered).to have_link(
- t('idv.troubleshooting.options.learn_more_address_verification_options'),
- )
- expect(rendered).not_to have_link(
- nil,
- href: return_to_sp_failure_to_proof_url(step: 'welcome', location: 'missing_items'),
+ t('doc_auth.info.getting_started_learn_more'),
+ href: help_center_redirect_path(
+ category: 'verify-your-identity',
+ article: 'how-to-verify-your-identity',
+ flow: :idv,
+ step: :welcome,
+ location: 'intro_paragraph',
+ ),
)
end
- end
-
- context 'with service provider' do
- let(:sp_name) { 'Example App' }
- it 'renders troubleshooting options' do
- render
+ context 'when the SP requests IAL2 verification' do
+ let(:selfie_required) { true }
- expect(rendered).to have_link(t('idv.troubleshooting.options.supported_documents'))
- expect(rendered).to have_link(
- t('idv.troubleshooting.options.learn_more_address_verification_options'),
- )
- expect(rendered).to have_link(
- t('idv.troubleshooting.options.get_help_at_sp', sp_name: sp_name),
- href: return_to_sp_failure_to_proof_url(step: 'welcome', location: 'missing_items'),
- )
+ it 'renders a modified welcome template' do
+ expect(rendered).to have_content(t('doc_auth.instructions.bullet1_with_selfie'))
+ end
end
end
-
- it 'renders a link to the privacy & security page' do
- render
- expect(rendered).to have_link(
- t('doc_auth.instructions.learn_more'),
- href: policy_redirect_url(flow: :idv, step: :welcome, location: :footer),
- )
- end
end
diff --git a/spec/views/users/auth_app/edit.html.erb_spec.rb b/spec/views/users/auth_app/edit.html.erb_spec.rb
new file mode 100644
index 00000000000..55e90c0393f
--- /dev/null
+++ b/spec/views/users/auth_app/edit.html.erb_spec.rb
@@ -0,0 +1,42 @@
+require 'rails_helper'
+
+RSpec.describe 'users/auth_app/edit.html.erb' do
+ include Devise::Test::ControllerHelpers
+
+ let(:nickname) { 'Example' }
+ let(:configuration) { create(:auth_app_configuration, name: nickname) }
+ let(:user) { create(:user, auth_app_configurations: [configuration]) }
+ let(:form) do
+ TwoFactorAuthentication::AuthAppUpdateForm.new(
+ user:,
+ configuration_id: configuration.id,
+ )
+ end
+
+ subject(:rendered) { render }
+
+ before do
+ @form = form
+ end
+
+ it 'renders form to update configuration' do
+ expect(rendered).to have_selector(
+ "form[action='#{auth_app_path(id: configuration.id)}'] input[name='_method'][value='put']",
+ visible: false,
+ )
+ end
+
+ it 'initializes form with configuration values' do
+ expect(rendered).to have_field(
+ t('two_factor_authentication.auth_app.nickname'),
+ with: nickname,
+ )
+ end
+
+ it 'has labelled form with button to delete configuration' do
+ expect(rendered).to have_button_to_with_accessibility(
+ t('two_factor_authentication.auth_app.delete'),
+ auth_app_path(id: configuration.id),
+ )
+ end
+end