diff --git a/app/controllers/concerns/idv/verify_info_concern.rb b/app/controllers/concerns/idv/verify_info_concern.rb
index 783d8f67c62..71c3a320cc9 100644
--- a/app/controllers/concerns/idv/verify_info_concern.rb
+++ b/app/controllers/concerns/idv/verify_info_concern.rb
@@ -31,7 +31,10 @@ def shared_update
idv_session.verify_info_step_document_capture_session_uuid = document_capture_session.uuid
- Idv::Agent.new(pii).proof_resolution(
+ user_pii = pii
+ user_pii[:best_effort_phone_number_for_socure] = best_effort_phone
+
+ Idv::Agent.new(user_pii).proof_resolution(
document_capture_session,
trace_id: amzn_trace_id,
user_id: current_user.id,
@@ -48,6 +51,14 @@ def log_event_for_missing_threatmetrix_session_id
analytics.idv_verify_info_missing_threatmetrix_session_id if idv_session.ssn_step_complete?
end
+ def best_effort_phone
+ if idv_session.phone_for_mobile_flow
+ { source: :hybrid_handoff, phone: idv_session.phone_for_mobile_flow }
+ elsif current_user.default_phone_configuration
+ { source: :mfa, phone: current_user.default_phone_configuration.formatted_phone }
+ end
+ end
+
private
def ipp_enrollment_in_progress?
@@ -192,6 +203,15 @@ def async_state_done(current_async_state)
},
)
+ threatmetrix_reponse_body = form_response.extra.dig(
+ :proofing_results, :context, :stages, :threatmetrix, :response_body
+ )
+ if threatmetrix_reponse_body.present?
+ analytics.idv_threatmetrix_response_body(
+ response_body: threatmetrix_reponse_body,
+ )
+ end
+
summarize_result_and_rate_limit(form_response)
delete_async
diff --git a/app/forms/register_user_email_form.rb b/app/forms/register_user_email_form.rb
index 6aa1cb2046a..07afaf21bc2 100644
--- a/app/forms/register_user_email_form.rb
+++ b/app/forms/register_user_email_form.rb
@@ -170,7 +170,7 @@ def send_sign_up_confirmed_email
)
else
UserMailer.with(user: existing_user, email_address: email_address_record).
- signup_with_your_email.deliver_now_or_later
+ signup_with_your_email(request_id: request_id).deliver_now_or_later
end
end
diff --git a/app/jobs/socure_shadow_mode_proofing_job.rb b/app/jobs/socure_shadow_mode_proofing_job.rb
index 83b8aa15bf1..d9bbe33faae 100644
--- a/app/jobs/socure_shadow_mode_proofing_job.rb
+++ b/app/jobs/socure_shadow_mode_proofing_job.rb
@@ -42,6 +42,7 @@ def perform(
analytics.idv_socure_shadow_mode_proofing_result(
resolution_result: format_proofing_result_for_logs(proofing_result),
socure_result: socure_result.to_h,
+ phone_source: applicant[:phone_source],
user_id: user.uuid,
pii_like_keypaths: [
[:errors, :ssn],
@@ -91,6 +92,10 @@ def build_applicant(
)
applicant_pii = decrypted_arguments[:applicant_pii]
+ if applicant_pii[:phone].nil? && applicant_pii[:best_effort_phone_number_for_socure]
+ applicant_pii[:phone] = applicant_pii[:best_effort_phone_number_for_socure][:phone]
+ applicant_pii[:phone_source] = applicant_pii[:best_effort_phone_number_for_socure][:source]
+ end
{
**applicant_pii.slice(
@@ -102,6 +107,7 @@ def build_applicant(
:state,
:zipcode,
:phone,
+ :phone_source,
:dob,
:ssn,
:consent_given_at,
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index 48790c362c1..1f2e17fb19c 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -73,9 +73,9 @@ def email_confirmation_instructions(token, request_id:)
end
end
- def signup_with_your_email
+ def signup_with_your_email(request_id:)
with_user_locale(user) do
- @root_url = root_url(locale: locale_url_param)
+ @root_url = root_url(locale: locale_url_param, request_id: request_id)
mail(to: email_address.email, subject: t('mailer.email_reuse_notice.subject'))
end
end
@@ -242,13 +242,16 @@ def add_email_associated_with_another_account
end
end
- def account_verified(date_time:, sp_name:)
+ def account_verified(profile:)
+ attachments.inline['verified.png'] =
+ Rails.root.join('app/assets/images/email/user-signup-ial2.png').read
with_user_locale(user) do
- @date = I18n.l(date_time, format: :event_date)
- @sp_name = sp_name
+ @presenter = Idv::AccountVerifiedEmailPresenter.new(profile:)
+ @hide_title = true
+ @date = I18n.l(profile.verified_at, format: :event_date)
mail(
to: email_address.email,
- subject: t('user_mailer.account_verified.subject', sp_name: @sp_name),
+ subject: t('user_mailer.account_verified.subject', app_name: APP_NAME),
)
end
end
diff --git a/app/presenters/idv/account_verified_email_presenter.rb b/app/presenters/idv/account_verified_email_presenter.rb
new file mode 100644
index 00000000000..ceb89b83901
--- /dev/null
+++ b/app/presenters/idv/account_verified_email_presenter.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Idv
+ class AccountVerifiedEmailPresenter
+ include Rails.application.routes.url_helpers
+
+ attr_reader :profile
+
+ def initialize(profile:)
+ @profile = profile
+ end
+
+ def service_provider
+ profile.initiating_service_provider
+ end
+
+ def show_cta?
+ !service_provider || service_provider_homepage_url.present?
+ end
+
+ def sign_in_url
+ service_provider_homepage_url || root_url
+ end
+
+ def service_provider_homepage_url
+ sp_return_url_resolver.homepage_url if service_provider
+ end
+
+ def sp_name
+ service_provider&.friendly_name || APP_NAME
+ end
+
+ def url_options
+ {}
+ end
+
+ private
+
+ def sp_return_url_resolver
+ SpReturnUrlResolver.new(service_provider: service_provider)
+ end
+ end
+end
diff --git a/app/services/analytics.rb b/app/services/analytics.rb
index 8e7c59cf5c4..378a89712e7 100644
--- a/app/services/analytics.rb
+++ b/app/services/analytics.rb
@@ -134,6 +134,11 @@ def sp_request_attributes
[v.name.sub('http://idmanagement.gov/ns/assurance/', ''), true]
end.to_h
attributes.reject! { |_key, value| value == false }
+
+ if differentiator.present?
+ attributes[:app_differentiator] = differentiator
+ end
+
attributes.transform_keys! do |key|
key.to_s.chomp('?').to_sym
end
@@ -141,6 +146,16 @@ def sp_request_attributes
{ sp_request: attributes }
end
+ def differentiator
+ return @differentiator if defined?(@differentiator)
+ @differentiator ||= begin
+ sp_request_url = session&.dig(:sp, :request_url)
+ return nil if sp_request_url.blank?
+
+ UriService.params(sp_request_url)['login_gov_app_differentiator']
+ end
+ end
+
def resolved_authn_context_result
return nil if sp.nil? || session[:sp].blank?
return @resolved_authn_context_result if defined?(@resolved_authn_context_result)
diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb
index ea5ef9b3ddb..d7b9211ed51 100644
--- a/app/services/analytics_events.rb
+++ b/app/services/analytics_events.rb
@@ -4591,14 +4591,17 @@ def idv_session_error_visited(
# Logs a Socure KYC result alongside a resolution result for later comparison.
# @param [Hash] socure_result Result from Socure KYC API call
# @param [Hash] resolution_result Result from resolution proofing
+ # @param [String,nil] phone_source Whether the phone number is from MFA or hybrid handoff
def idv_socure_shadow_mode_proofing_result(
socure_result:,
resolution_result:,
+ phone_source:,
**extra
)
track_event(
:idv_socure_shadow_mode_proofing_result,
resolution_result: resolution_result.to_h,
+ phone_source:,
socure_result: socure_result.to_h,
**extra,
)
@@ -4654,6 +4657,19 @@ def idv_start_over(
)
end
+ # The JSON body of the response returned from Threatmetrix. PII has been removed.
+ # @param [Hash] response_body The response body returned by ThreatMetrix
+ def idv_threatmetrix_response_body(
+ response_body: nil,
+ **extra
+ )
+ track_event(
+ :idv_threatmetrix_response_body,
+ response_body: response_body,
+ **extra,
+ )
+ end
+
# Track when USPS auth token refresh job completed
def idv_usps_auth_token_refresh_job_completed(**extra)
track_event(
diff --git a/app/services/idv/analytics_events_enhancer.rb b/app/services/idv/analytics_events_enhancer.rb
index 92587341389..1dff6c81e89 100644
--- a/app/services/idv/analytics_events_enhancer.rb
+++ b/app/services/idv/analytics_events_enhancer.rb
@@ -98,6 +98,7 @@ module AnalyticsEventsEnhancer
idv_sdk_selfie_image_capture_opened
idv_selfie_image_added
idv_session_error_visited
+ idv_threatmetrix_response_body
idv_usps_auth_token_refresh_job_completed
idv_usps_auth_token_refresh_job_network_error
idv_usps_auth_token_refresh_job_started
diff --git a/app/services/proofing/aamva/proofer.rb b/app/services/proofing/aamva/proofer.rb
index b9b3bf1aee1..0ce39e7d144 100644
--- a/app/services/proofing/aamva/proofer.rb
+++ b/app/services/proofing/aamva/proofer.rb
@@ -28,6 +28,8 @@ class Proofer
first_name
].freeze
+ REQUIRED_IF_PRESENT_ATTRIBUTES = [:state_id_expiration].freeze
+
ADDRESS_ATTRIBUTES = [
:address1,
:address2,
@@ -56,6 +58,7 @@ def proof(applicant)
).send_verification_request(
applicant: aamva_applicant,
)
+
build_result_from_response(response, applicant[:state])
rescue => exception
failed_result = Proofing::StateIdResult.new(
@@ -133,6 +136,11 @@ def successful?(verification_response)
return false unless verification_response.verification_results[verification_attribute]
end
+ REQUIRED_IF_PRESENT_ATTRIBUTES.each do |verification_attribute|
+ value = verification_response.verification_results[verification_attribute]
+ return false unless value.nil? || value == true
+ end
+
true
end
diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb
index 5dc2be668d4..edb8a6f07f4 100644
--- a/app/services/proofing/resolution/progressive_proofer.rb
+++ b/app/services/proofing/resolution/progressive_proofer.rb
@@ -32,7 +32,7 @@ def proof(
ipp_enrollment_in_progress:,
current_sp:
)
- @applicant_pii = applicant_pii
+ @applicant_pii = applicant_pii.except(:best_effort_phone_number_for_socure)
@request_ip = request_ip
@threatmetrix_session_id = threatmetrix_session_id
@timer = timer
diff --git a/app/services/proofing/socure/id_plus/proofer.rb b/app/services/proofing/socure/id_plus/proofer.rb
index 91edb4c4300..09461aedf43 100644
--- a/app/services/proofing/socure/id_plus/proofer.rb
+++ b/app/services/proofing/socure/id_plus/proofer.rb
@@ -33,7 +33,7 @@ def initialize(config)
# @param [Hash] applicant
# @return [Proofing::Resolution::Result]
def proof(applicant)
- input = Input.new(applicant)
+ input = Input.new(applicant.except(:phone_source))
request = Request.new(config:, input:)
diff --git a/app/services/user_alerts/alert_user_about_account_verified.rb b/app/services/user_alerts/alert_user_about_account_verified.rb
index cc6e5c62ca6..2b844ccb7ed 100644
--- a/app/services/user_alerts/alert_user_about_account_verified.rb
+++ b/app/services/user_alerts/alert_user_about_account_verified.rb
@@ -4,11 +4,9 @@ module UserAlerts
class AlertUserAboutAccountVerified
def self.call(profile:)
user = profile.user
- sp_name = profile.initiating_service_provider&.friendly_name || APP_NAME
user.confirmed_email_addresses.each do |email_address|
UserMailer.with(user: user, email_address: email_address).account_verified(
- date_time: profile.verified_at,
- sp_name: sp_name,
+ profile: profile,
).deliver_now_or_later
end
end
diff --git a/app/views/user_mailer/account_verified.html.erb b/app/views/user_mailer/account_verified.html.erb
index dff3fe44a73..f4f796ffd0a 100644
--- a/app/views/user_mailer/account_verified.html.erb
+++ b/app/views/user_mailer/account_verified.html.erb
@@ -1,15 +1,67 @@
-
+<%= image_tag(
+ attachments['verified.png'].url,
+ width: 140,
+ height: 177,
+ alt: '',
+ role: 'img',
+ class: 'float-center padding-bottom-4',
+ ) %>
+
+
<%= message.subject %>
+
+
+ <%= t('user_mailer.account_verified.greeting') %>
+
+
+ <%= t('user_mailer.account_verified.intro', date: @date) %>
+
+
+
+<% if @presenter.service_provider.present? %>
+ <% if @presenter.show_cta? %>
+ <%= t('user_mailer.account_verified.next_sign_in.with_sp.with_cta', sp_name: @presenter.service_provider.friendly_name) %>
+ <% else %>
+ <%= t('user_mailer.account_verified.next_sign_in.with_sp.without_cta', sp_name: @presenter.service_provider.friendly_name) %>
+ <% end %>
+<% else %>
+ <%= t('user_mailer.account_verified.next_sign_in.without_sp', app_name: APP_NAME) %>
+<% end %>
+
+
+<% if @presenter.show_cta? %>
+
+
+ <%= link_to(@presenter.sign_in_url, @presenter.sign_in_url, target: '_blank', rel: 'noopener') %>
+
+<% end %>
+
+
<%= t(
- 'user_mailer.account_verified.intro_html',
- sp_name: @sp_name,
- app_name: APP_NAME,
- date: @date,
+ 'user_mailer.account_verified.warning_contact_us_html',
change_password_link_html: link_to(
t('user_mailer.account_verified.change_password_link'),
new_user_password_url,
),
- contact_link_html: link_to(t('user_mailer.account_verified.contact_link'), MarketingSite.contact_url),
- ) %>
+ contact_link_html: link_to(t('user_mailer.account_verified.contact_link', app_name: APP_NAME), MarketingSite.contact_url),
+ )
+ %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index a94e96cf5fb..aebea9e45e9 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1804,9 +1804,15 @@ user_mailer.account_reset_request.header: Your account will be deleted in %{inte
user_mailer.account_reset_request.intro_html: 'As a security measure, %{app_name} requires a two-step process to delete your account:
Step One: There is a waiting period of %{waiting_period} if you have lost access to your authentication methods and need to delete your account. If you locate your authentication methods, you can sign in to your %{app_name} account to cancel this request.
Step Two: After the waiting period of %{waiting_period}, you will receive an email that will ask you to confirm the deletion of your %{app_name} account. Your account will not be deleted until you confirm.'
user_mailer.account_reset_request.subject: How to delete your %{app_name} account
user_mailer.account_verified.change_password_link: change your password
-user_mailer.account_verified.contact_link: contact us
-user_mailer.account_verified.intro_html: You successfully verified your identity with %{sp_name} on %{date} using %{app_name}. If you did not perform this action, please %{contact_link_html} and sign in to %{change_password_link_html}.
-user_mailer.account_verified.subject: You verified your identity with %{sp_name}.
+user_mailer.account_verified.contact_link: contact %{app_name} support
+user_mailer.account_verified.greeting: Hello,
+user_mailer.account_verified.intro: You successfully verified your identity on %{date}.
+user_mailer.account_verified.next_sign_in.with_sp.with_cta: Next, click the button or copy the link below to access %{sp_name} and sign in.
+user_mailer.account_verified.next_sign_in.with_sp.without_cta: You can now sign in from %{sp_name}’s website.
+user_mailer.account_verified.next_sign_in.without_sp: Next, click the button or copy the link below to sign in to %{app_name}.
+user_mailer.account_verified.sign_in: Sign in
+user_mailer.account_verified.subject: You successfully verified your identity with %{app_name}
+user_mailer.account_verified.warning_contact_us_html: If you did not attempt to verify your identity, please sign in to %{change_password_link_html}. To report this, %{contact_link_html}.
user_mailer.add_email_associated_with_another_account.help_html: If you did not request a new email or suspect an error, please visit the %{app_name_html} %{help_link_html} or %{contact_link_html}.
user_mailer.add_email_associated_with_another_account.intro_html: This email address is already associated with a %{app_name_html} account, so we can’t add it to another account. You must first delete or remove it from the account it is associated with. To do this, follow the link below and sign in with this email address. If you are not trying to add this email address to an account, you can ignore this message.
user_mailer.add_email_associated_with_another_account.link_text: Go to %{app_name}
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 6833ee083d8..4cf1827c652 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -1815,10 +1815,16 @@ user_mailer.account_reset_request.cancel: '¿No desea eliminar su cuenta? Inicie
user_mailer.account_reset_request.header: Su cuenta será eliminada en %{interval}
user_mailer.account_reset_request.intro_html: 'Como medida de seguridad, %{app_name} requiere un proceso de dos pasos para eliminar su cuenta:
Paso uno: Hay un período de espera de %{waiting_period} si perdió el acceso a sus métodos de autenticación y necesita eliminar su cuenta. Si encuentra sus métodos de autenticación, puede iniciar sesión en su cuenta %{app_name} para cancelar esta solicitud.
Paso dos: Tras el período de espera de %{waiting_period}, recibirás un correo electrónico en el que te pediremos que confirmes la eliminación de tu cuenta %{app_name}. Tu cuenta no se eliminará hasta que lo confirmes.'
user_mailer.account_reset_request.subject: Cómo eliminar su cuenta de %{app_name}
-user_mailer.account_verified.change_password_link: cambie su contraseña
-user_mailer.account_verified.contact_link: contáctenos
-user_mailer.account_verified.intro_html: El %{date}, verificó correctamente su identidad con %{sp_name} usando %{app_name}. Si usted no efectuó esta acción, vaya a %{contact_link_html} e inicie sesión para %{change_password_link_html}.
-user_mailer.account_verified.subject: Verificó su identidad con %{sp_name}
+user_mailer.account_verified.change_password_link: restablecer su contraseña
+user_mailer.account_verified.contact_link: contacte con el servicio de asistencia de %{app_name}
+user_mailer.account_verified.greeting: 'Hola:'
+user_mailer.account_verified.intro: Verificó correctamente su identidad el %{date}.
+user_mailer.account_verified.next_sign_in.with_sp.with_cta: A continuación, haga clic en el botón o copie el vínculo siguiente para acceder a %{sp_name} e iniciar sesión.
+user_mailer.account_verified.next_sign_in.with_sp.without_cta: Ya puede iniciar la sesión en el sitio web de %{sp_name}.
+user_mailer.account_verified.next_sign_in.without_sp: Luego, haga clic en el botón o copie el vínculo siguiente para iniciar sesión en %{app_name}.
+user_mailer.account_verified.sign_in: Iniciar sesión
+user_mailer.account_verified.subject: Logró verificar su identidad con %{app_name}
+user_mailer.account_verified.warning_contact_us_html: Si usted no intentó verificar su identidad, inicie sesión para %{change_password_link_html}. Para informar de esto, %{contact_link_html}.
user_mailer.add_email_associated_with_another_account.help_html: Si no solicitó un nuevo correo electrónico o sospecha que hubo un error, visite %{help_link_html} de %{app_name_html} o %{contact_link_html}.
user_mailer.add_email_associated_with_another_account.intro_html: Esta dirección de correo electrónico ya está asociada con una cuenta de %{app_name_html}, por lo que no podemos agregarla a otra cuenta. Primero, debe eliminarla o quitarla de la cuenta con la que está asociada. Para hacerlo, siga este vínculo e inicie sesión con esta dirección de correo electrónico. Si no está intentando agregar esta dirección de correo electrónico a una cuenta, puede ignorar este mensaje.
user_mailer.add_email_associated_with_another_account.link_text: Ir a %{app_name}
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 2bf5eaeb887..f02e41a8902 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -1803,10 +1803,16 @@ user_mailer.account_reset_request.cancel: Vous ne voulez pas supprimer votre com
user_mailer.account_reset_request.header: Votre compte sera supprimé dans %{interval}
user_mailer.account_reset_request.intro_html: 'Par mesure de sécurité, %{app_name} nécessite un processus en deux étapes pour supprimer votre compte:
Étape 1: Il y a un delai d’attente de %{waiting_period} si vous avez perdu l’accès à vos méthodes d’authentification et devez supprimer votre compte. Si vous trouvez vos méthodes d’authentification, vous pouvez vous connecter à votre compte %{app_name} pour annuler cette demande.
Deuxième étape: après la période d’attente de %{waiting_period}, vous recevrez un e-mail qui vous demandera de confirmer la suppression de votre compte %{app_name}. Votre compte ne sera pas supprimé tant que vous n’aurez pas confirmé.'
user_mailer.account_reset_request.subject: Comment supprimer votre compte %{app_name}
-user_mailer.account_verified.change_password_link: changer votre mot de passe
-user_mailer.account_verified.contact_link: nous contacter
-user_mailer.account_verified.intro_html: Le %{date}, vous avez réussi à confirmer votre identité auprès de %{sp_name} à l’aide de %{app_name}. Si vous n’avez pas effectué cette action, veuillez %{contact_link_html} et vous connecter pour %{change_password_link_html}.
-user_mailer.account_verified.subject: Vous avez confirmé votre identité avec %{sp_name}.
+user_mailer.account_verified.change_password_link: réinitialiser votre mot de passe
+user_mailer.account_verified.contact_link: contactez le service d’assistance de %{app_name}
+user_mailer.account_verified.greeting: Bonjour,
+user_mailer.account_verified.intro: Vous avez réussi à confirmer votre identité le %{date}.
+user_mailer.account_verified.next_sign_in.with_sp.with_cta: Maintenant, cliquez sur le bouton ou copiez le lien ci-dessous pour accéder à %{sp_name} et vous connecter.
+user_mailer.account_verified.next_sign_in.with_sp.without_cta: Vous pouvez désormais vous connecter depuis le site Web de %{sp_name}.
+user_mailer.account_verified.next_sign_in.without_sp: Maintenant, cliquez sur le bouton ou copiez le lien ci-dessous pour vous connecter à %{app_name}.
+user_mailer.account_verified.sign_in: Se connecter
+user_mailer.account_verified.subject: Vous avez réussi à vérifier votre identité avec %{app_name}
+user_mailer.account_verified.warning_contact_us_html: Si vous n’avez pas essayé de confirmer votre identité, veuillez vous connecter pour %{change_password_link_html}. Pour signaler ce problème, %{contact_link_html}.
user_mailer.add_email_associated_with_another_account.help_html: Si vous n’avez pas demandé de nouvel e-mail ou suspectez une erreur, veuillez visiter le %{help_link_html} de %{app_name_html} ou %{contact_link_html}.
user_mailer.add_email_associated_with_another_account.intro_html: Cette adresse e-mail est déjà associée à un compte %{app_name_html}, nous ne pouvons donc pas l’ajouter à un autre compte. Vous devez d’abord la supprimer ou la retirer du compte auquel elle est associée. Pour ce faire, suivez le lien ci-dessous et connectez-vous avec cette adresse e-mail. Si vous n’essayez pas d’ajouter cette adresse e-mail à un compte, vous pouvez ignorer ce message.
user_mailer.add_email_associated_with_another_account.link_text: Allez sur %{app_name}
diff --git a/config/locales/zh.yml b/config/locales/zh.yml
index 97ddc9d565a..0ffe276079f 100644
--- a/config/locales/zh.yml
+++ b/config/locales/zh.yml
@@ -1817,9 +1817,15 @@ user_mailer.account_reset_request.header: 你的账户会在%{interval}后删除
user_mailer.account_reset_request.intro_html: 作为一项安全措施,%{app_name} 要求一个两步流程来删除你的帐户:
第一步:如果你丢失了身份证实方法但需删除账户,有一个%{waiting_period} 的等待期。如果你找到了身份证实方法,可以登录你的 %{app_name} 帐户来取消这个请求。
第二步:%{waiting_period}等待期之后,你会收到一封电邮,请你确认要删除 %{app_name} 账户。只有经你确认后,你的账户才会被删除。
user_mailer.account_reset_request.subject: 如何删除你的 %{app_name} 账户
user_mailer.account_verified.change_password_link: 更改密码
-user_mailer.account_verified.contact_link: 联系我们
-user_mailer.account_verified.intro_html: 你于 %{date} 使用 %{app_name} 在 %{sp_name}成功验证了身份。如果你没有采取这一行动,请 %{contact_link_html} 并登录 %{change_password_link_html}。
-user_mailer.account_verified.subject: 你在 %{sp_name} 验证了身份。
+user_mailer.account_verified.contact_link: 请联系 %{app_name}支持
+user_mailer.account_verified.greeting: 你好,
+user_mailer.account_verified.intro: 你在 %{date} 成功地验证了身份。
+user_mailer.account_verified.next_sign_in.with_sp.with_cta: 接下来请点击按钮或复制下面的连接来访问 %{sp_name} 并登录。
+user_mailer.account_verified.next_sign_in.with_sp.without_cta: 你现在可以从 %{sp_name} 的网站登录。
+user_mailer.account_verified.next_sign_in.without_sp: 接下来请点击按钮或复制下面的连接来登录 %{app_name}。
+user_mailer.account_verified.sign_in: 登录
+user_mailer.account_verified.subject: 你在 %{app_name} 成功地验证了身份
+user_mailer.account_verified.warning_contact_us_html: 如果你没有试图验证过身份,请登录来%{change_password_link_html}。要报告这件事,%{contact_link_html}。
user_mailer.add_email_associated_with_another_account.help_html: 如果你没有要求一封新电邮或怀疑有错, 请访问 %{app_name_html}的 %{help_link_html} 或者 %{contact_link_html}。
user_mailer.add_email_associated_with_another_account.intro_html: 该电邮地址已与一个 %{app_name_html}账户相关联,所以我们不能把它加到另外一个账户上。你必须首先将其从与之相关的账户中删除或去掉。要做到这一点,点击以下链接并用该电邮地址登录。如果你没有试图将此电邮地址加到一个账户,可忽略这一信息。
user_mailer.add_email_associated_with_another_account.link_text: 请到 %{app_name}
diff --git a/spec/controllers/idv/in_person/verify_info_controller_spec.rb b/spec/controllers/idv/in_person/verify_info_controller_spec.rb
index 22cb5cbe811..5332fd07369 100644
--- a/spec/controllers/idv/in_person/verify_info_controller_spec.rb
+++ b/spec/controllers/idv/in_person/verify_info_controller_spec.rb
@@ -106,6 +106,7 @@
transaction_id: 1,
review_status: review_status,
response_body: {
+ session_id: 'threatmetrix_session_id',
tmx_summary_reason_code: ['Identity_Negative_History'],
},
},
@@ -137,6 +138,13 @@
},
),
)
+ expect(@analytics).to have_logged_event(
+ :idv_threatmetrix_response_body,
+ response_body: {
+ session_id: 'threatmetrix_session_id',
+ tmx_summary_reason_code: ['Identity_Negative_History'],
+ },
+ )
end
end
@@ -268,6 +276,10 @@
expect(Idv::Agent).to receive(:new).with(
Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS.merge(
consent_given_at: subject.idv_session.idv_consent_given_at,
+ best_effort_phone_number_for_socure: {
+ source: :mfa,
+ phone: '+1 415-555-0130',
+ },
),
).and_call_original
put :update
diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb
index 0062867e8fe..f6aaf74bea4 100644
--- a/spec/controllers/idv/verify_info_controller_spec.rb
+++ b/spec/controllers/idv/verify_info_controller_spec.rb
@@ -235,6 +235,12 @@
),
),
)
+ expect(@analytics).to have_logged_event(
+ :idv_threatmetrix_response_body,
+ response_body: hash_including(
+ client: threatmetrix_client_id,
+ ),
+ )
end
end
@@ -393,7 +399,7 @@
end
end
- context 'when the reolution proofing job result is missing' do
+ context 'when the resolution proofing job result is missing' do
let(:async_state) do
ProofingSessionAsyncResult.new(status: ProofingSessionAsyncResult::MISSING)
end
@@ -491,4 +497,36 @@
end
end
end
+
+ describe '#best_effort_phone' do
+ it 'returns nil when there is no number available' do
+ expect(subject.best_effort_phone).to eq(nil)
+ end
+
+ context 'when there is a hybrid handoff number' do
+ before(:each) do
+ allow(subject.idv_session).to receive(:phone_for_mobile_flow).and_return('202-555-1234')
+ end
+
+ it 'returns the phone number from hybrid handoff' do
+ expect(subject.best_effort_phone[:phone]).to eq('202-555-1234')
+ end
+
+ it 'sets type to :hybrid_handoff' do
+ expect(subject.best_effort_phone[:source]).to eq(:hybrid_handoff)
+ end
+ end
+
+ context 'when there was an MFA phone number provided' do
+ let(:user) { create(:user, :with_phone) }
+
+ it 'returns the MFA phone number' do
+ expect(subject.best_effort_phone[:phone]).to eq('+1 202-555-1212')
+ end
+
+ it 'sets the phone source to :mfa' do
+ expect(subject.best_effort_phone[:source]).to eq(:mfa)
+ end
+ end
+ end
end
diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb
index 5ebd43e173f..5e3f1f61b70 100644
--- a/spec/features/idv/analytics_spec.rb
+++ b/spec/features/idv/analytics_spec.rb
@@ -10,21 +10,26 @@
let(:proofing_device_profiling) { :enabled }
let(:threatmetrix) { true }
let(:idv_level) { 'in_person' }
+ let(:threatmetrix_response_body) do
+ {
+ account_lex_id: 'super-cool-test-lex-id',
+ 'fraudpoint.score': '500',
+ request_id: '1234',
+ request_result: 'success',
+ review_status: 'pass',
+ risk_rating: 'trusted',
+ session_id: 'super-cool-test-session-id',
+ summary_risk_score: '-6',
+ tmx_risk_rating: 'neutral',
+ tmx_summary_reason_code: ['Identity_Negative_History'],
+ }
+ end
let(:threatmetrix_response) do
{
client: nil,
errors: {},
exception: nil,
- response_body: { "fraudpoint.score": '500',
- request_id: '1234',
- request_result: 'success',
- account_lex_id: 'super-cool-test-lex-id',
- session_id: 'super-cool-test-session-id',
- review_status: 'pass',
- risk_rating: 'trusted',
- summary_risk_score: '-6',
- tmx_risk_rating: 'neutral',
- tmx_summary_reason_code: ['Identity_Negative_History'] },
+ response_body: threatmetrix_response_body,
review_status: 'pass',
account_lex_id: 'super-cool-test-lex-id',
session_id: 'super-cool-test-session-id',
@@ -216,6 +221,11 @@
'IdV: doc auth verify submitted' => {
flow_path: 'standard', step: 'verify', analytics_id: 'Doc Auth'
},
+ idv_threatmetrix_response_body: (
+ if threatmetrix_response_body.present?
+ { response_body: threatmetrix_response_body }
+ end
+ ),
'IdV: doc auth verify proofing results' => {
success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', step: 'verify',
proofing_results: base_proofing_results
@@ -273,7 +283,7 @@
active_profile_idv_level: 'legacy_unsupervised',
proofing_components: lexis_nexis_address_proofing_components
},
- }
+ }.compact
end
let(:happy_hybrid_path_events) do
@@ -331,6 +341,11 @@
'IdV: doc auth verify submitted' => {
flow_path: 'hybrid', step: 'verify', analytics_id: 'Doc Auth'
},
+ idv_threatmetrix_response_body: (
+ if threatmetrix_response_body.present?
+ { response_body: threatmetrix_response_body }
+ end
+ ),
'IdV: doc auth verify proofing results' => {
success: true, errors: {}, flow_path: 'hybrid', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', step: 'verify',
proofing_results: base_proofing_results
@@ -388,7 +403,7 @@
active_profile_idv_level: 'legacy_unsupervised',
proofing_components: lexis_nexis_address_proofing_components
},
- }
+ }.compact
end
let(:gpo_path_events) do
@@ -443,6 +458,11 @@
'IdV: doc auth verify submitted' => {
flow_path: 'standard', step: 'verify', analytics_id: 'Doc Auth'
},
+ idv_threatmetrix_response_body: (
+ if threatmetrix_response_body.present?
+ { response_body: threatmetrix_response_body }
+ end
+ ),
'IdV: doc auth verify proofing results' => {
success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', step: 'verify',
proofing_results: base_proofing_results
@@ -477,7 +497,7 @@
pending_profile_idv_level: 'legacy_unsupervised',
proofing_components: gpo_letter_proofing_components,
},
- }
+ }.compact
end
let(:in_person_path_events) do
@@ -552,6 +572,11 @@
'IdV: doc auth verify submitted' => {
analytics_id: 'In Person Proofing', step: 'verify', flow_path: 'standard', same_address_as_id: false
},
+ idv_threatmetrix_response_body: (
+ if threatmetrix_response_body.present?
+ { response_body: threatmetrix_response_body }
+ end
+ ),
'IdV: doc auth verify proofing results' => {
success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'In Person Proofing', step: 'verify', same_address_as_id: false,
proofing_results: in_person_path_proofing_results
@@ -609,7 +634,7 @@
},
'IdV: user clicked what to bring link on ready to verify page' => {},
'IdV: user clicked sp link on ready to verify page' => {},
- }
+ }.compact
end
let(:happy_mobile_selfie_path_events) do
@@ -670,6 +695,11 @@
'IdV: doc auth verify submitted' => {
flow_path: 'standard', step: 'verify', analytics_id: 'Doc Auth'
},
+ idv_threatmetrix_response_body: (
+ if threatmetrix_response_body.present?
+ { response_body: threatmetrix_response_body }
+ end
+ ),
'IdV: doc auth verify proofing results' => {
success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', step: 'verify',
proofing_results: base_proofing_results
@@ -727,7 +757,7 @@
active_profile_idv_level: 'unsupervised_with_selfie',
proofing_components: lexis_nexis_address_proofing_components
},
- }
+ }.compact
end
# rubocop:enable Layout/LineLength
# rubocop:enable Layout/MultilineHashKeyLineBreaks
@@ -786,6 +816,7 @@
context 'proofing_device_profiling disabled' do
let(:proofing_device_profiling) { :disabled }
let(:threatmetrix) { false }
+ let(:threatmetrix_response_body) { nil }
let(:threatmetrix_response) do
{
client: 'tmx_disabled',
@@ -797,7 +828,7 @@
review_status: 'pass',
account_lex_id: nil,
session_id: nil,
- response_body: nil,
+ response_body: threatmetrix_response_body,
}
end
@@ -866,6 +897,7 @@
context 'proofing_device_profiling disabled' do
let(:proofing_device_profiling) { :disabled }
let(:threatmetrix) { false }
+ let(:threatmetrix_response_body) { nil }
let(:threatmetrix_response) do
{
client: 'tmx_disabled',
@@ -877,7 +909,7 @@
review_status: 'pass',
account_lex_id: nil,
session_id: nil,
- response_body: nil,
+ response_body: threatmetrix_response_body,
}
end
@@ -915,6 +947,7 @@
context 'proofing_device_profiling disabled' do
let(:proofing_device_profiling) { :disabled }
let(:threatmetrix) { false }
+ let(:threatmetrix_response_body) { nil }
let(:threatmetrix_response) do
{
client: 'tmx_disabled',
@@ -926,7 +959,7 @@
review_status: 'pass',
account_lex_id: nil,
session_id: nil,
- response_body: nil,
+ response_body: threatmetrix_response_body,
}
end
@@ -976,6 +1009,7 @@
let(:proofing_device_profiling) { :disabled }
let(:idv_level) { 'legacy_in_person' }
let(:threatmetrix) { false }
+ let(:threatmetrix_response_body) { nil }
let(:threatmetrix_response) do
{
client: 'tmx_disabled',
@@ -987,7 +1021,7 @@
review_status: 'pass',
account_lex_id: nil,
session_id: nil,
- response_body: nil,
+ response_body: threatmetrix_response_body,
}
end
@@ -1047,6 +1081,7 @@ def wait_for_event(event, wait)
context 'proofing_device_profiling disabled' do
let(:proofing_device_profiling) { :disabled }
let(:threatmetrix) { false }
+ let(:threatmetrix_response_body) { nil }
let(:threatmetrix_response) do
{
client: 'tmx_disabled',
@@ -1058,7 +1093,7 @@ def wait_for_event(event, wait)
review_status: 'pass',
account_lex_id: nil,
session_id: nil,
- response_body: nil,
+ response_body: threatmetrix_response_body,
}
end
@@ -1108,6 +1143,7 @@ def wait_for_event(event, wait)
context 'proofing_device_profiling disabled' do
let(:proofing_device_profiling) { :disabled }
let(:threatmetrix) { false }
+ let(:threatmetrix_response_body) { nil }
let(:threatmetrix_response) do
{
client: 'tmx_disabled',
@@ -1119,7 +1155,7 @@ def wait_for_event(event, wait)
review_status: 'pass',
account_lex_id: nil,
session_id: nil,
- response_body: nil,
+ response_body: threatmetrix_response_body,
}
end
diff --git a/spec/fixtures/proofing/aamva/responses/verification_response.xml b/spec/fixtures/proofing/aamva/responses/verification_response.xml
index 9cbf1ecc2ff..a55dc4e890c 100644
--- a/spec/fixtures/proofing/aamva/responses/verification_response.xml
+++ b/spec/fixtures/proofing/aamva/responses/verification_response.xml
@@ -8,6 +8,8 @@
true
+ true
+ true
true
true
true
@@ -17,7 +19,8 @@
true
true
true
+ true
true
true
true
-
+
\ No newline at end of file
diff --git a/spec/fixtures/proofing/aamva/responses/verification_response_namespaced_success.xml b/spec/fixtures/proofing/aamva/responses/verification_response_namespaced_success.xml
index d875f2d12a4..6fdc71ded19 100644
--- a/spec/fixtures/proofing/aamva/responses/verification_response_namespaced_success.xml
+++ b/spec/fixtures/proofing/aamva/responses/verification_response_namespaced_success.xml
@@ -16,15 +16,18 @@
true
+ true
+ true
true
true
true
true
true
+ true
true
true
true
-
+
\ No newline at end of file
diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb
index bb5a31a8779..1855adf69cb 100644
--- a/spec/jobs/resolution_proofing_job_spec.rb
+++ b/spec/jobs/resolution_proofing_job_spec.rb
@@ -114,7 +114,15 @@
expect(result_context_stages_state_id[:timed_out]).to eq(false)
expect(result_context_stages_state_id[:transaction_id]).to eq('1234-abcd-efgh')
expect(result_context_stages_state_id[:verified_attributes]).to match_array(
- %w[address state_id_number state_id_type dob last_name first_name],
+ %w[
+ address
+ state_id_expiration
+ state_id_issued
+ state_id_number
+ state_id_type dob
+ last_name
+ first_name
+ ],
)
# result[:context][:stages][:threatmetrix]
@@ -198,7 +206,15 @@
expect(result_context_stages_state_id[:vendor_name]).to eq('aamva:state_id')
expect(result_context_stages_state_id[:success]).to eq(true)
expect(result_context_stages_state_id[:verified_attributes]).to match_array(
- %w[address state_id_number state_id_type dob last_name first_name],
+ %w[
+ address
+ state_id_expiration
+ state_id_issued
+ state_id_number
+ state_id_type dob
+ last_name
+ first_name
+ ],
)
end
end
@@ -439,7 +455,15 @@
expect(result_context_stages_state_id[:timed_out]).to eq(false)
expect(result_context_stages_state_id[:transaction_id]).to eq('1234-abcd-efgh')
expect(result_context_stages_state_id[:verified_attributes]).to match_array(
- %w[address state_id_number state_id_type dob last_name first_name],
+ %w[
+ address
+ state_id_expiration
+ state_id_issued
+ state_id_number
+ state_id_type dob
+ last_name
+ first_name
+ ],
)
# result[:context][:stages][:threatmetrix]
diff --git a/spec/jobs/socure_shadow_mode_proofing_job_spec.rb b/spec/jobs/socure_shadow_mode_proofing_job_spec.rb
index 220ecb938fc..acc02e7e63d 100644
--- a/spec/jobs/socure_shadow_mode_proofing_job_spec.rb
+++ b/spec/jobs/socure_shadow_mode_proofing_job_spec.rb
@@ -18,7 +18,7 @@
end
let(:applicant_pii) do
- Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE
+ Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN
end
let(:encrypted_arguments) do
@@ -182,20 +182,8 @@
end
context 'when document capture session result is present in redis' do
- it 'makes a proofing call' do
- expect(job.proofer).to receive(:proof).and_call_original
- perform
- end
-
- it 'does not log an idv_socure_shadow_mode_proofing_result_missing event' do
- perform
- expect(analytics).not_to have_logged_event(:idv_socure_shadow_mode_proofing_result_missing)
- end
-
- it 'logs an event' do
- perform
- expect(analytics).to have_logged_event(
- :idv_socure_shadow_mode_proofing_result,
+ let(:expected_event_body) do
+ {
user_id: user.uuid,
resolution_result: {
success: true,
@@ -278,9 +266,52 @@
vendor_workflow: nil,
verified_attributes: %i[address first_name last_name phone ssn dob].to_set,
},
+ }
+ end
+
+ it 'makes a proofing call' do
+ expect(job.proofer).to receive(:proof).and_call_original
+ perform
+ end
+
+ it 'does not log an idv_socure_shadow_mode_proofing_result_missing event' do
+ perform
+ expect(analytics).not_to have_logged_event(:idv_socure_shadow_mode_proofing_result_missing)
+ end
+
+ it 'logs an event' do
+ perform
+ expect(analytics).to have_logged_event(
+ :idv_socure_shadow_mode_proofing_result,
+ expected_event_body,
)
end
+ context 'when the user has an MFA phone number' do
+ let(:applicant_pii) do
+ Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.merge(
+ best_effort_phone_number_for_socure: {
+ source: :mfa,
+ phone: '1 202-555-0000',
+ },
+ )
+ end
+
+ let(:encrypted_arguments) do
+ Encryption::Encryptors::BackgroundProofingArgEncryptor.new.encrypt(
+ JSON.generate({ applicant_pii: applicant_pii }),
+ )
+ end
+
+ it 'logs an event with the phone number' do
+ perform
+ expect(analytics).to have_logged_event(
+ :idv_socure_shadow_mode_proofing_result,
+ expected_event_body.merge(phone_source: 'mfa'),
+ )
+ end
+ end
+
context 'when socure proofer raises an error' do
before do
allow(job.proofer).to receive(:proof).and_raise
@@ -349,22 +380,71 @@
job.build_applicant(encrypted_arguments:, user_email:)
end
- it 'builds an applicant structure that looks right' do
- expect(build_applicant).to eql(
- {
- first_name: 'FAKEY',
- last_name: 'MCFAKERSON',
- address1: '1 FAKE RD',
- address2: nil,
- city: 'GREAT FALLS',
- state: 'MT',
- zipcode: '59010-1234',
+ let(:expected_attributes) do
+ {
+ first_name: 'FAKEY',
+ last_name: 'MCFAKERSON',
+ address1: '1 FAKE RD',
+ address2: nil,
+ city: 'GREAT FALLS',
+ state: 'MT',
+ zipcode: '59010-1234',
+ dob: '1938-10-06',
+ ssn: '900-66-1234',
+ email: user.email,
+ }
+ end
+
+ context 'when the user has a phone directly passed in' do
+ let(:applicant_pii) do
+ Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.merge(
phone: '12025551212',
- dob: '1938-10-06',
- ssn: '900-66-1234',
- email: user.email,
- },
- )
+ )
+ end
+
+ let(:encrypted_arguments) do
+ Encryption::Encryptors::BackgroundProofingArgEncryptor.new.encrypt(
+ JSON.generate({ applicant_pii: }),
+ )
+ end
+
+ it 'builds an applicant structure with that phone number' do
+ expect(build_applicant).to eql(
+ expected_attributes.merge(phone: '12025551212'),
+ )
+ end
+ end
+
+ context 'when the user has a hybrid-handoff phone' do
+ let(:applicant_pii_no_phone) do
+ Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.merge(
+ best_effort_phone_number_for_socure: {
+ source: :hybrid_handoff,
+ phone: '12025556789',
+ },
+ )
+ end
+
+ let(:encrypted_arguments) do
+ Encryption::Encryptors::BackgroundProofingArgEncryptor.new.encrypt(
+ JSON.generate({ applicant_pii: applicant_pii_no_phone }),
+ )
+ end
+
+ it 'builds an applicant using the hybrid handoff number' do
+ expect(build_applicant).to eql(
+ expected_attributes.merge(
+ phone: '12025556789',
+ phone_source: 'hybrid_handoff',
+ ),
+ )
+ end
+ end
+
+ context 'when no phone is available for the user' do
+ it 'does not set phone at all' do
+ expect(build_applicant).to eql(expected_attributes)
+ end
end
end
diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb
index 8acb83c15f6..05b63b0aaa8 100644
--- a/spec/mailers/previews/user_mailer_preview.rb
+++ b/spec/mailers/previews/user_mailer_preview.rb
@@ -8,7 +8,8 @@ def email_confirmation_instructions
end
def signup_with_your_email
- UserMailer.with(user: user, email_address: email_address_record).signup_with_your_email
+ UserMailer.with(user: user, email_address: email_address_record).
+ signup_with_your_email(request_id: SecureRandom.uuid)
end
def reset_password_instructions
@@ -144,9 +145,15 @@ def add_email_associated_with_another_account
end
def account_verified
+ service_provider = ServiceProvider.find_by(friendly_name: 'Example Sinatra App')
UserMailer.with(user: user, email_address: email_address_record).account_verified(
- date_time: DateTime.now,
- sp_name: 'Example App',
+ profile: unsaveable(
+ Profile.new(
+ user: user,
+ initiating_service_provider: service_provider,
+ verified_at: Time.zone.now,
+ ),
+ ),
)
end
diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb
index 4ec60f3f88a..ff529f49665 100644
--- a/spec/mailers/user_mailer_spec.rb
+++ b/spec/mailers/user_mailer_spec.rb
@@ -8,7 +8,11 @@
let(:is_enhanced_ipp) { false }
describe '#validate_user_and_email_address' do
- let(:mail) { UserMailer.with(user: user, email_address: email_address).signup_with_your_email }
+ let(:request_id) { '1234-abcd' }
+ let(:mail) do
+ UserMailer.with(user: user, email_address: email_address).
+ signup_with_your_email(request_id: request_id)
+ end
context 'with user and email address match' do
it 'does not raise an error' do
@@ -210,8 +214,10 @@
end
describe '#signup_with_your_email' do
+ let(:request_id) { '1234-abcd' }
let(:mail) do
- UserMailer.with(user: user, email_address: user.email_addresses.first).signup_with_your_email
+ UserMailer.with(user: user, email_address: user.email_addresses.first).
+ signup_with_your_email(request_id: request_id)
end
it_behaves_like 'a system email'
@@ -519,9 +525,10 @@ def expect_email_body_to_have_help_and_contact_links
describe '#account_verified' do
let(:sp_name) { '' }
let(:date_time) { Time.zone.now }
+ let(:profile) { create(:profile, :active) }
let(:mail) do
UserMailer.with(user: user, email_address: email_address).
- account_verified(date_time: date_time, sp_name: sp_name)
+ account_verified(profile: profile)
end
it_behaves_like 'a system email'
@@ -532,7 +539,7 @@ def expect_email_body_to_have_help_and_contact_links
end
it 'renders the subject' do
- expect(mail.subject).to eq t('user_mailer.account_verified.subject', sp_name: sp_name)
+ expect(mail.subject).to eq t('user_mailer.account_verified.subject', app_name: APP_NAME)
end
it 'links to the forgot password page' do
diff --git a/spec/presenters/idv/account_verified_email_presenter_spec.rb b/spec/presenters/idv/account_verified_email_presenter_spec.rb
new file mode 100644
index 00000000000..760b22b2fd0
--- /dev/null
+++ b/spec/presenters/idv/account_verified_email_presenter_spec.rb
@@ -0,0 +1,96 @@
+require 'rails_helper'
+
+RSpec.describe Idv::AccountVerifiedEmailPresenter do
+ include Rails.application.routes.url_helpers
+
+ let(:service_provider) { create(:service_provider) }
+
+ let(:profile) do
+ create(
+ :profile,
+ initiating_service_provider: service_provider,
+ )
+ end
+
+ subject(:presenter) { described_class.new(profile:) }
+
+ context 'when there is no associated service provider' do
+ let(:service_provider) { nil }
+
+ describe '#show_cta?' do
+ it 'is true' do
+ expect(presenter.show_cta?).to eq(true)
+ end
+ end
+
+ describe '#sp_name' do
+ it 'returns our APP_NAME instead' do
+ expect(presenter.sp_name).to eq(APP_NAME)
+ end
+ end
+
+ describe '#sign_in_url' do
+ it 'links to ourselves since there is no SP' do
+ expect(presenter.sign_in_url).to eq(root_url)
+ end
+ end
+ end
+
+ context 'where there is a service provider' do
+ context 'when the service provider has no return URL' do
+ let(:service_provider) do
+ create(
+ :service_provider,
+ return_to_sp_url: nil,
+ friendly_name: 'My Awesome SP',
+ )
+ end
+
+ describe '#show_cta?' do
+ it 'is false' do
+ expect(presenter.show_cta?).to eq(false)
+ end
+ end
+
+ describe '#sp_name' do
+ it 'returns the SP name' do
+ expect(presenter.sp_name).to eq('My Awesome SP')
+ end
+ end
+
+ describe '#sign_in_url' do
+ it 'links to ourselves' do
+ expect(presenter.sign_in_url).to eq(root_url)
+ end
+ end
+ end
+
+ context 'when the service provider does have a return URL' do
+ let(:service_provider) do
+ create(
+ :service_provider,
+ return_to_sp_url: 'https://www.example.com',
+ friendly_name: 'My Awesome SP',
+ )
+ end
+
+ describe '#show_cta?' do
+ it 'is true' do
+ expect(presenter.show_cta?).to eq(true)
+ end
+ end
+
+ describe '#sp_name' do
+ it 'shows the SP name' do
+ expect(presenter.sp_name).to eq('My Awesome SP')
+ end
+ end
+
+ describe '#sign_in_url' do
+ it 'links to the SP' do
+ expect(presenter.sign_in_url).to eq('https://www.example.com')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/analytics_spec.rb b/spec/services/analytics_spec.rb
index 07b0dd5dc02..402e75cda4a 100644
--- a/spec/services/analytics_spec.rb
+++ b/spec/services/analytics_spec.rb
@@ -358,4 +358,82 @@
end
end
end
+
+ context 'with an SP request_url saved in the session' do
+ context 'no request_url' do
+ let(:session) { { sp: { acr_values: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF } } }
+
+ let(:expected_attributes) do
+ {
+ sp_request: {
+ component_values: { 'ial/1' => true },
+ component_separator: ' ',
+ },
+ }
+ end
+
+ it 'includes the sp_request' do
+ expect(ahoy).to receive(:track).
+ with('Trackable Event', hash_including(expected_attributes))
+
+ analytics.track_event('Trackable Event')
+ end
+ end
+
+ context 'a request_url without login_gov_app_differentiator ' do
+ let(:session) do
+ {
+ sp: {
+ acr_values: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF,
+ request_url: 'http://localhost:3000/openid_connect/authorize?whatever=something_else',
+ },
+ }
+ end
+
+ let(:expected_attributes) do
+ {
+ sp_request: {
+ component_values: { 'ial/1' => true },
+ component_separator: ' ',
+ },
+ }
+ end
+
+ it 'includes the sp_request' do
+ expect(ahoy).to receive(:track).
+ with('Trackable Event', hash_including(expected_attributes))
+
+ analytics.track_event('Trackable Event')
+ end
+ end
+
+ context 'a request_url with login_gov_app_differentiator ' do
+ let(:session) do
+ {
+ sp: {
+ acr_values: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF,
+ request_url:
+ 'http://localhost:3000/openid_connect/authorize?login_gov_app_differentiator=NY',
+ },
+ }
+ end
+
+ let(:expected_attributes) do
+ {
+ sp_request: {
+ component_values: { 'ial/1' => true },
+ component_separator: ' ',
+ app_differentiator: 'NY',
+ },
+ }
+ end
+
+ it 'includes the sp_request' do
+ expect(ahoy).to receive(:track).
+ with('Trackable Event', hash_including(expected_attributes))
+
+ analytics.track_event('Trackable Event')
+ end
+ end
+ end
end
diff --git a/spec/services/proofing/aamva/proofer_spec.rb b/spec/services/proofing/aamva/proofer_spec.rb
index 1de70664a09..cab58477c75 100644
--- a/spec/services/proofing/aamva/proofer_spec.rb
+++ b/spec/services/proofing/aamva/proofer_spec.rb
@@ -14,7 +14,7 @@
}
end
- let(:verification_results) do
+ let(:verification_result) do
{
state_id_number: true,
dob: true,
@@ -44,6 +44,302 @@
end
describe '#proof' do
+ describe 'individual attributes' do
+ subject(:result) do
+ described_class.new(AamvaFixtures.example_config.to_h).proof(state_id_data)
+ end
+
+ def self.when_missing(&block)
+ context 'when missing' do
+ let(:verification_response) do
+ XmlHelper.delete_xml_at_xpath(
+ AamvaFixtures.verification_response,
+ "//#{match_indicator_name}",
+ )
+ end
+
+ instance_eval(&block)
+ end
+ end
+
+ def self.when_unverified(&block)
+ context 'when unverified' do
+ let(:verification_response) do
+ XmlHelper.modify_xml_at_xpath(
+ AamvaFixtures.verification_response,
+ "//#{match_indicator_name}",
+ 'false',
+ )
+ end
+
+ instance_eval(&block)
+ end
+ end
+
+ def self.test_in_requested_attributes(logged_attribute = nil)
+ if logged_attribute
+ it "does not stop #{logged_attribute} from appearing in requested_attributes" do
+ expect(result.requested_attributes).to include(logged_attribute => 1)
+ end
+ it 'does not itself appear in requested_attributes' do
+ expect(result.requested_attributes).not_to include(attribute => 1)
+ end
+ else
+ it 'appears in requested_attributes' do
+ expect(result.requested_attributes).to include(attribute => 1)
+ end
+ end
+ end
+
+ def self.test_not_in_requested_attributes(logged_attribute = nil)
+ if logged_attribute
+ it "stops #{logged_attribute} from appearing in requested_attributes" do
+ expect(result.requested_attributes).not_to include(logged_attribute => 1)
+ end
+ end
+ it 'does not appear in requested_attributes' do
+ expect(result.requested_attributes).not_to include(attribute => 1)
+ end
+ end
+
+ def self.test_in_verified_attributes(logged_attribute)
+ it "does not stop #{logged_attribute} from appearing in verified_attributes" do
+ expect(result.verified_attributes).to include(logged_attribute)
+ end
+
+ it 'does not itself appear in verified_attributes' do
+ expect(result.verified_attributes).not_to include(attribute)
+ end
+ end
+
+ def self.test_not_in_verified_attributes(logged_attribute = nil)
+ if logged_attribute
+ it "stops #{logged_attribute} from appearing in verified_attributes" do
+ expect(result.verified_attributes).not_to include(logged_attribute)
+ end
+ end
+ it 'does not appear in verified_attributes' do
+ expect(result.verified_attributes).not_to include(attribute)
+ end
+ end
+
+ def self.test_still_successful
+ it 'the result is still successful' do
+ expect(result.success?).to be true
+ end
+ end
+
+ def self.test_not_successful
+ it 'the result is not successful' do
+ expect(result.success?).to be false
+ end
+ end
+
+ describe '#address1' do
+ let(:attribute) { :address1 }
+ let(:match_indicator_name) { 'AddressLine1MatchIndicator' }
+
+ when_unverified do
+ test_still_successful
+ test_in_requested_attributes(:address)
+ test_not_in_verified_attributes(:address)
+ end
+
+ when_missing do
+ test_still_successful
+ test_not_in_requested_attributes(:address)
+ test_not_in_verified_attributes(:address)
+ end
+ end
+
+ describe '#address2' do
+ let(:attribute) { :address2 }
+ let(:match_indicator_name) { 'AddressLine2MatchIndicator' }
+
+ when_unverified do
+ test_still_successful
+ test_in_requested_attributes(:address)
+ test_in_verified_attributes(:address)
+ end
+
+ when_missing do
+ test_still_successful
+ test_in_requested_attributes(:address)
+ test_in_verified_attributes(:address)
+ end
+ end
+
+ describe '#city' do
+ let(:attribute) { :city }
+ let(:match_indicator_name) { 'AddressCityMatchIndicator' }
+
+ when_unverified do
+ test_still_successful
+ test_in_requested_attributes(:address)
+ test_not_in_verified_attributes(:address)
+ end
+
+ when_missing do
+ test_still_successful
+ test_not_in_requested_attributes(:address)
+ test_not_in_verified_attributes(:address)
+ end
+ end
+
+ describe '#state' do
+ let(:attribute) { :city }
+ let(:match_indicator_name) { 'AddressStateCodeMatchIndicator' }
+
+ when_unverified do
+ test_still_successful
+ test_in_requested_attributes(:address)
+ test_not_in_verified_attributes(:address)
+ end
+
+ when_missing do
+ test_still_successful
+ test_not_in_requested_attributes(:address)
+ test_not_in_verified_attributes(:address)
+ end
+ end
+
+ describe '#zipcode' do
+ let(:attribute) { :zipcode }
+ let(:match_indicator_name) { 'AddressZIP5MatchIndicator' }
+
+ when_unverified do
+ test_still_successful
+ test_in_requested_attributes(:address)
+ test_not_in_verified_attributes(:address)
+ end
+
+ when_missing do
+ test_still_successful
+ test_not_in_requested_attributes(:address)
+ test_not_in_verified_attributes(:address)
+ end
+ end
+
+ describe '#dob' do
+ let(:attribute) { :dob }
+ let(:match_indicator_name) { 'PersonBirthDateMatchIndicator' }
+
+ when_unverified do
+ test_not_successful
+ test_in_requested_attributes
+ test_not_in_verified_attributes
+ end
+
+ when_missing do
+ test_not_successful
+ test_not_in_requested_attributes
+ test_not_in_verified_attributes
+ end
+ end
+
+ describe '#state_id_issued' do
+ let(:attribute) { :state_id_issued }
+ let(:match_indicator_name) { 'DriverLicenseIssueDateMatchIndicator' }
+
+ when_unverified do
+ test_still_successful
+ test_in_requested_attributes
+ test_not_in_verified_attributes
+ end
+
+ when_missing do
+ test_still_successful
+ test_not_in_requested_attributes
+ test_not_in_verified_attributes
+ end
+ end
+
+ describe '#state_id_number' do
+ let(:attribute) { :state_id_number }
+ let(:match_indicator_name) { 'DriverLicenseNumberMatchIndicator' }
+
+ when_unverified do
+ test_not_successful
+ test_in_requested_attributes
+ test_not_in_verified_attributes
+ end
+
+ when_missing do
+ test_not_successful
+ test_not_in_requested_attributes
+ test_not_in_verified_attributes
+ end
+ end
+
+ describe '#state_id_expiration' do
+ let(:attribute) { :state_id_expiration }
+ let(:match_indicator_name) { 'DriverLicenseExpirationDateMatchIndicator' }
+
+ when_unverified do
+ test_not_successful
+ test_in_requested_attributes
+ test_not_in_verified_attributes
+ end
+
+ when_missing do
+ test_still_successful
+ test_not_in_requested_attributes
+ test_not_in_verified_attributes
+ end
+ end
+
+ describe '#state_id_type' do
+ let(:attribute) { :state_id_type }
+ let(:match_indicator_name) { 'DocumentCategoryMatchIndicator' }
+
+ when_unverified do
+ test_still_successful
+ test_in_requested_attributes
+ test_not_in_verified_attributes
+ end
+
+ when_missing do
+ test_still_successful
+ test_not_in_requested_attributes
+ test_not_in_verified_attributes
+ end
+ end
+
+ describe '#first_name' do
+ let(:attribute) { :first_name }
+ let(:match_indicator_name) { 'PersonFirstNameExactMatchIndicator' }
+
+ when_unverified do
+ test_not_successful
+ test_in_requested_attributes
+ test_not_in_verified_attributes
+ end
+
+ when_missing do
+ test_not_successful
+ test_not_in_requested_attributes
+ test_not_in_verified_attributes
+ end
+ end
+
+ describe '#last_name' do
+ let(:attribute) { :last_name }
+ let(:match_indicator_name) { 'PersonLastNameExactMatchIndicator' }
+
+ when_unverified do
+ test_not_successful
+ test_in_requested_attributes
+ test_not_in_verified_attributes
+ end
+
+ when_missing do
+ test_not_successful
+ test_not_in_requested_attributes
+ test_not_in_verified_attributes
+ end
+ end
+ end
+
context 'when verification is successful' do
it 'the result is successful' do
result = subject.proof(state_id_data)
@@ -59,6 +355,8 @@
expect(result.verified_attributes).to eq(
%i[
dob
+ state_id_issued
+ state_id_expiration
state_id_number
state_id_type
last_name
@@ -73,6 +371,8 @@
expect(result.requested_attributes).to eq(
{
dob: 1,
+ state_id_issued: 1,
+ state_id_expiration: 1,
state_id_number: 1,
state_id_type: 1,
last_name: 1,
@@ -104,6 +404,8 @@
expect(result.verified_attributes).to eq(
%i[
+ state_id_expiration
+ state_id_issued
state_id_number
state_id_type
last_name
@@ -118,6 +420,8 @@
expect(result.requested_attributes).to eq(
{
dob: 1,
+ state_id_expiration: 1,
+ state_id_issued: 1,
state_id_number: 1,
state_id_type: 1,
last_name: 1,
@@ -148,6 +452,8 @@
expect(result.verified_attributes).to eq(
%i[
+ state_id_expiration
+ state_id_issued
state_id_number
state_id_type
last_name
@@ -161,6 +467,8 @@
result = subject.proof(state_id_data)
expect(result.requested_attributes).to eq(
{
+ state_id_expiration: 1,
+ state_id_issued: 1,
state_id_number: 1,
state_id_type: 1,
last_name: 1,
diff --git a/spec/services/proofing/aamva/response/verification_response_spec.rb b/spec/services/proofing/aamva/response/verification_response_spec.rb
index d52f1165bb0..3c91ffb880a 100644
--- a/spec/services/proofing/aamva/response/verification_response_spec.rb
+++ b/spec/services/proofing/aamva/response/verification_response_spec.rb
@@ -13,15 +13,15 @@
end
let(:verification_results) do
{
- state_id_expiration: nil,
- state_id_issued: nil,
+ state_id_expiration: true,
+ state_id_issued: true,
state_id_number: true,
state_id_type: true,
dob: true,
last_name: true,
first_name: true,
address1: true,
- address2: nil,
+ address2: true,
city: true,
state: true,
zipcode: true,
diff --git a/spec/services/proofing/aamva/verification_client_spec.rb b/spec/services/proofing/aamva/verification_client_spec.rb
index 8f298374090..25fefd7702e 100644
--- a/spec/services/proofing/aamva/verification_client_spec.rb
+++ b/spec/services/proofing/aamva/verification_client_spec.rb
@@ -62,14 +62,14 @@
expect(response.verification_results).to eq(
{
address1: true,
- address2: nil,
+ address2: true,
city: true,
dob: true,
first_name: true,
last_name: true,
state: true,
- state_id_expiration: nil,
- state_id_issued: nil,
+ state_id_expiration: true,
+ state_id_issued: true,
state_id_number: true,
state_id_type: true,
zipcode: true,
@@ -93,14 +93,14 @@
expect(response.verification_results).to eq(
{
address1: true,
- address2: nil,
+ address2: true,
city: true,
dob: false,
first_name: true,
last_name: true,
state: true,
- state_id_expiration: nil,
- state_id_issued: nil,
+ state_id_expiration: true,
+ state_id_issued: true,
state_id_number: true,
state_id_type: true,
zipcode: true,
diff --git a/spec/services/user_alerts/alert_user_about_account_verified_spec.rb b/spec/services/user_alerts/alert_user_about_account_verified_spec.rb
index 41ab2c4f0a6..47dd8f362fa 100644
--- a/spec/services/user_alerts/alert_user_about_account_verified_spec.rb
+++ b/spec/services/user_alerts/alert_user_about_account_verified_spec.rb
@@ -20,29 +20,56 @@
described_class.call(profile: profile)
expect_delivered_email_count(3)
- expect_delivered_email(
- to: [confirmed_email_addresses[0].email],
- subject: t('user_mailer.account_verified.subject', sp_name: service_provider.friendly_name),
- )
- expect_delivered_email(
- to: [confirmed_email_addresses[1].email],
- subject: t('user_mailer.account_verified.subject', sp_name: service_provider.friendly_name),
- )
- expect_delivered_email(
- to: [confirmed_email_addresses[2].email],
- subject: t('user_mailer.account_verified.subject', sp_name: service_provider.friendly_name),
- )
+
+ confirmed_email_addresses.each do |email_address|
+ expect_delivered_email(
+ to: [email_address.email],
+ subject: t('user_mailer.account_verified.subject', app_name: APP_NAME),
+ )
+ end
end
context 'when no service provider initiated the proofing event' do
let(:service_provider) { nil }
- it 'sends the email with Login.gov as the initiating service provider' do
+ it 'sends the email linking to Login.gov' do
+ described_class.call(profile: profile)
+
+ expect_delivered_email(
+ to: [user.confirmed_email_addresses.first.email],
+ subject: t('user_mailer.account_verified.subject', app_name: APP_NAME),
+ body: ['