+
+ <%= simple_form_for('', url: sign_up_select_email_path) do |f| %>
+
+ <%= f.submit t('help_text.requested_attributes.change_email_link'), class: 'margin-top-4' %>
+ <% end %>
+
+ <%= render ButtonComponent.new(
+ url: add_email_path,
+ outline: true,
+ big: true,
+ wide: true,
+ class: 'margin-top-2',
+ ).with_content(t('account.index.email_add')) %>
+
+ <%= render PageFooterComponent.new do %>
+ <%= link_to t('forms.buttons.back'), sign_up_completed_path %>
+ <% end %>
+
+<% end %>
\ No newline at end of file
diff --git a/config/application.yml.default b/config/application.yml.default
index 017ef81a2d7..72726ab5de9 100644
--- a/config/application.yml.default
+++ b/config/application.yml.default
@@ -97,9 +97,9 @@ doc_auth_max_submission_attempts_before_native_camera: 3
doc_auth_selfie_desktop_test_mode: false
doc_auth_separate_pages_enabled: false
doc_auth_supported_country_codes: '["US", "GU", "VI", "AS", "MP", "PR", "USA" ,"GUM", "VIR", "ASM", "MNP", "PRI"]'
-doc_auth_vendor_randomize: false
-doc_auth_vendor_randomize_alternate_vendor: ''
-doc_auth_vendor_randomize_percent: 0
+doc_auth_vendor_lexis_nexis_percent: 100 # note, LN is currently the default vendor
+doc_auth_vendor_socure_percent: 0
+doc_auth_vendor_switching_enabled: false
doc_capture_polling_enabled: true
doc_capture_request_valid_for_minutes: 15
drop_off_report_config: '[{"emails":["ursula@example.com"],"issuers": ["urn:gov:gsa:openidconnect.profiles:sp:sso:agency_name:app_name"]}]'
@@ -116,6 +116,7 @@ enable_usps_verification: true
event_disavowal_expiration_hours: 240
feature_idv_force_gpo_verification_enabled: false
feature_idv_hybrid_flow_enabled: true
+feature_select_email_to_share_enabled: true
geo_data_file_path: 'geo_data/GeoLite2-City.mmdb'
get_usps_proofing_results_job_cron: '0/30 * * * *'
get_usps_proofing_results_job_reprocess_delay_minutes: 5
@@ -335,6 +336,7 @@ sign_in_user_id_per_ip_attempt_window_exponential_factor: 1.1
sign_in_user_id_per_ip_attempt_window_in_minutes: 720
sign_in_user_id_per_ip_attempt_window_max_minutes: 43_200
sign_in_user_id_per_ip_max_attempts: 50
+socure_webhook_enabled: false
socure_webhook_secret_key: ''
socure_webhook_secret_key_queue: '[]'
sp_handoff_bounce_max_seconds: 2
@@ -349,6 +351,7 @@ test_ssn_allowed_list: ''
totp_code_interval: 30
unauthorized_scope_enabled: false
use_dashboard_service_providers: false
+use_fed_domain_class: false
use_kms: false
use_vot_in_sp_requests: true
usps_auth_token_refresh_job_enabled: false
@@ -398,6 +401,7 @@ development:
dashboard_url: http://localhost:3001/api/service_providers
doc_auth_selfie_desktop_test_mode: true
doc_auth_vendor: 'mock'
+ doc_auth_vendor_default: 'mock'
domain_name: localhost:3000
enable_rate_limiting: false
hmac_fingerprinter_key: a2c813d4dca919340866ba58063e4072adc459b767a74cf2666d5c1eef3861db26708e7437abde1755eb24f4034386b0fea1850a1cb7e56bff8fae3cc6ade96c
@@ -432,6 +436,7 @@ development:
state_tracking_enabled: true
telephony_adapter: test
use_dashboard_service_providers: true
+ use_fed_domain_class: true
usps_eipp_sponsor_id: '222222222222222'
usps_ipp_sponsor_id: '111111111111111'
usps_ipp_transliteration_enabled: true
@@ -456,11 +461,13 @@ production:
dashboard_url: https://dashboard.demo.login.gov
disable_email_sending: false
disable_logout_get_request: false
- doc_auth_vendor: 'acuant'
+ doc_auth_vendor: 'lexisnexis'
+ doc_auth_vendor_default: 'lexisnexis'
domain_name: login.gov
email_registrations_per_ip_track_only_mode: true
enable_test_routes: false
enable_usps_verification: false
+ feature_select_email_to_share_enabled: false
hmac_fingerprinter_key:
hmac_fingerprinter_key_queue: '[]'
idv_sp_required: true
@@ -511,6 +518,7 @@ test:
doc_auth_max_attempts: 4
doc_auth_selfie_desktop_test_mode: true
doc_auth_vendor: 'mock'
+ doc_auth_vendor_default: 'mock'
doc_capture_polling_enabled: false
domain_name: www.example.com
email_registrations_per_ip_limit: 3
@@ -563,6 +571,7 @@ test:
telephony_adapter: test
test_ssn_allowed_list: '999999999'
totp_code_interval: 3
+ use_fed_domain_class: true
usps_eipp_sponsor_id: '222222222222222'
usps_ipp_root_url: 'http://localhost:1000'
usps_ipp_sponsor_id: '111111111111111'
diff --git a/config/initializers/ab_tests.rb b/config/initializers/ab_tests.rb
index 38b69641eed..70fdddd95c2 100644
--- a/config/initializers/ab_tests.rb
+++ b/config/initializers/ab_tests.rb
@@ -35,13 +35,17 @@ def self.all
constants.index_with { |test_name| const_get(test_name) }
end
+ # This "test" will permanently be in place to allow a graceful transition from TrueID being the
+ # sole vendor to a multi-vendor configuration.
DOC_AUTH_VENDOR = AbTest.new(
experiment_name: 'Doc Auth Vendor',
should_log: /^idv/i,
+ default_bucket: :lexis_nexis,
buckets: {
- alternate_vendor: IdentityConfig.store.doc_auth_vendor_randomize ?
- IdentityConfig.store.doc_auth_vendor_randomize_percent :
- 0,
+ socure: IdentityConfig.store.doc_auth_vendor_switching_enabled ?
+ IdentityConfig.store.doc_auth_vendor_socure_percent : 0,
+ lexis_nexis: IdentityConfig.store.doc_auth_vendor_switching_enabled ?
+ IdentityConfig.store.doc_auth_vendor_lexis_nexis_percent : 0,
}.compact,
) do |service_provider:, session:, user:, user_session:, **|
document_capture_session_uuid_discriminator(service_provider:, session:, user:, user_session:)
diff --git a/config/locales/en.yml b/config/locales/en.yml
index f1a3b37a92f..9dca9c10a6c 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -692,6 +692,7 @@ doc_auth.tips.review_issues_id_text1: Did you use a dark background?
doc_auth.tips.review_issues_id_text2: Did you take the photo on a flat surface?
doc_auth.tips.review_issues_id_text3: Is the flash on your camera off?
doc_auth.tips.review_issues_id_text4: Are all details sharp and clearly visible?
+email_address.not_found: 'Email not found'
email_addresses.add.duplicate: This email address is already registered to your account.
email_addresses.add.limit: You’ve added the maximum number of email addresses.
email_addresses.delete.bullet1: You won’t be able to sign in to %{app_name} (or any of the government applications linked to your account) using this email address
@@ -958,18 +959,18 @@ headings.webauthn_setup.new: Insert your security key
help_text.requested_attributes.address: Address
help_text.requested_attributes.all_emails: Email addresses on your account
help_text.requested_attributes.birthdate: Date of birth
+help_text.requested_attributes.change_email_link: Change
+help_text.requested_attributes.consent_reminder_html: You must consent each year to share your information with %{sp_html}.
help_text.requested_attributes.email: Email address
help_text.requested_attributes.full_name: Full name
-help_text.requested_attributes.ial1_consent_reminder_html: You must consent each year to share your information with %{sp}. We’ll share your information with %{sp} to connect your account.
-help_text.requested_attributes.ial1_intro_html: We’ll share your information with %{sp} to connect your account.
-help_text.requested_attributes.ial2_consent_reminder_html: '%{sp} needs to know who you are to connect to your account. You must consent each year to share your verified information with %{sp}. We’ll share this information:'
-help_text.requested_attributes.ial2_intro_html: '%{sp} needs to know who you are to connect your account. We’ll share this information with %{sp}:'
-help_text.requested_attributes.ial2_reverified_consent_info: 'Because you verified your identity again, we need your permission to share this information with %{sp}:'
+help_text.requested_attributes.ial2_reverified_consent_info_html: 'Because you verified your identity again, we need your permission to share this information with %{sp_html}:'
+help_text.requested_attributes.intro_html: 'We’ll share this information with %{sp_html}:'
help_text.requested_attributes.phone: Phone number
help_text.requested_attributes.social_security_number: Social Security number
help_text.requested_attributes.verified_at: Updated on
help_text.requested_attributes.x509_issuer: PIV/CAC Issuer
help_text.requested_attributes.x509_subject: PIV/CAC Identity
+help_text.select_preferred_email: You may change which email you share with %{sp} since you have multiple emails associated with your %{app_name} account.
i18n.language: Language
i18n.locale.en: English
i18n.locale.es: Español
@@ -1606,6 +1607,7 @@ titles.reactivate_account: Reactivate your account
titles.registrations.new: Create your account
titles.revoke_consent: Revoke Consent
titles.rules_of_use: Rules of Use
+titles.select_email: Select your preferred email
titles.sign_up.completion_consent_expired_ial1: It’s been a year since you gave us consent to share your information
titles.sign_up.completion_consent_expired_ial2: It’s been a year since you gave us consent to share your verified identity
titles.sign_up.completion_first_sign_in: Continue to %{sp}
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 2d5bcae878a..de9a55b97fb 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -703,6 +703,7 @@ doc_auth.tips.review_issues_id_text1: '¿Usó un fondo de color oscuro?'
doc_auth.tips.review_issues_id_text2: '¿Tomó la foto en una superficie plana?'
doc_auth.tips.review_issues_id_text3: '¿Está apagado el flash de su cámara?'
doc_auth.tips.review_issues_id_text4: '¿Todos los detalles se ven con precisión y claridad?'
+email_address.not_found: El correo electrónico no encontrado
email_addresses.add.duplicate: Esta dirección de correo electrónico ya está registrada en su cuenta.
email_addresses.add.limit: Agregó el número máximo de direcciones de correo electrónico.
email_addresses.delete.bullet1: Si usa esta dirección de correo electrónico, no podrá iniciar sesión en %{app_name} (ni en ninguna de las aplicaciones gubernamentales vinculadas a su cuenta).
@@ -969,18 +970,18 @@ headings.webauthn_setup.new: Inserte su clave de seguridad
help_text.requested_attributes.address: Dirección
help_text.requested_attributes.all_emails: Direcciones de correo electrónico en su cuenta
help_text.requested_attributes.birthdate: Fecha de nacimiento
+help_text.requested_attributes.change_email_link: Cambiar
+help_text.requested_attributes.consent_reminder_html: Debe dar su consentimiento cada año para divulgar su información a %{sp_html}.
help_text.requested_attributes.email: Dirección de correo electrónico
help_text.requested_attributes.full_name: Nombre completo
-help_text.requested_attributes.ial1_consent_reminder_html: Debe dar su consentimiento cada año para divulgar su información a %{sp}. Divulgaremos su información a %{sp} para conectar su cuenta.
-help_text.requested_attributes.ial1_intro_html: Divulgaremos su información a %{sp} para conectar su cuenta.
-help_text.requested_attributes.ial2_consent_reminder_html: 'Para conectar su cuenta, %{sp} necesita saber quién es usted. Debe dar su consentimiento cada año para divulgar su información verificada a %{sp}. Divulgaremos esta información:'
-help_text.requested_attributes.ial2_intro_html: 'Para conectar su cuenta, %{sp} necesita saber quién es usted. Divulgaremos esta información a %{sp}:'
-help_text.requested_attributes.ial2_reverified_consent_info: 'Como volvió a verificar su identidad, necesitamos su permiso para divulgar esta información a %{sp}:'
+help_text.requested_attributes.ial2_reverified_consent_info_html: 'Como volvió a verificar su identidad, necesitamos su permiso para divulgar esta información a %{sp_html}:'
+help_text.requested_attributes.intro_html: 'Divulgaremos esta información a %{sp_html}:'
help_text.requested_attributes.phone: Número de teléfono
help_text.requested_attributes.social_security_number: Número de Seguro Social
help_text.requested_attributes.verified_at: Actualizado en
help_text.requested_attributes.x509_issuer: Emisor de la tarjeta PIV o CAC
help_text.requested_attributes.x509_subject: Identidad de la tarjeta PIV o CAC
+help_text.select_preferred_email: Puede cambiar el correo electrónico que comparte con %{sp} ya que tiene varios correos electrónicos asociados a su cuenta de %{app_name}.
i18n.language: Idioma
i18n.locale.en: English
i18n.locale.es: Español
@@ -1618,6 +1619,7 @@ titles.reactivate_account: Reactive su cuenta
titles.registrations.new: Cree su cuenta
titles.revoke_consent: Revocar consentimiento
titles.rules_of_use: Reglas de uso
+titles.select_email: Seleccione el correo electrónico que prefiera
titles.sign_up.completion_consent_expired_ial1: Hace un año que nos dio su consentimiento para divulgar su información
titles.sign_up.completion_consent_expired_ial2: Hace un año que nos dio su consentimiento para divulgar su identidad verificada
titles.sign_up.completion_first_sign_in: Continuar con %{sp}
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index a1489bc0f48..2cf64287c9b 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -692,6 +692,7 @@ doc_auth.tips.review_issues_id_text1: Avez-vous utilisé un arrière-plan de cou
doc_auth.tips.review_issues_id_text2: Avez-vous pris la photo sur une surface plane ?
doc_auth.tips.review_issues_id_text3: Le flash de votre appareil photo est-il éteint ?
doc_auth.tips.review_issues_id_text4: Tous les détails sont-ils nets et clairement visibles ?
+email_address.not_found: Email non trouvé
email_addresses.add.duplicate: Cette adresse e-mail est déjà enregistrée sur votre compte.
email_addresses.add.limit: Vous avez ajouté le nombre maximum d’adresses e-mail.
email_addresses.delete.bullet1: Vous ne pourrez pas vous connecter à %{app_name} (ni à aucune des applications de l’administration associées à votre compte) à l’aide de cette adresse e-mail
@@ -958,18 +959,18 @@ headings.webauthn_setup.new: Insérer votre clé de sécurité
help_text.requested_attributes.address: Adresse
help_text.requested_attributes.all_emails: Adresses e-mail sur votre compte
help_text.requested_attributes.birthdate: Date de naissance
+help_text.requested_attributes.change_email_link: Modifier
+help_text.requested_attributes.consent_reminder_html: Vous devez consentir chaque année au partage de vos informations avec %{sp_html}.
help_text.requested_attributes.email: Adresse e-mail
help_text.requested_attributes.full_name: Nom complet
-help_text.requested_attributes.ial1_consent_reminder_html: Vous devez consentir chaque année au partage de vos informations avec %{sp}. Nous partagerons vos informations avec %{sp} pour connecter votre compte.
-help_text.requested_attributes.ial1_intro_html: Nous partagerons vos informations avec %{sp} pour connecter votre compte.
-help_text.requested_attributes.ial2_consent_reminder_html: '%{sp} a besoin de savoir qui vous êtes pour se connecter à votre compte. Vous devez consentir chaque année à partager vos informations vérifiées avec %{sp}. Nous partagerons ces informations :'
-help_text.requested_attributes.ial2_intro_html: '%{sp} a besoin de savoir qui vous êtes pour connecter votre compte. Nous partagerons ces informations avec %{sp} :'
-help_text.requested_attributes.ial2_reverified_consent_info: 'Étant donné que vous avez revérifié votre identité, nous avons besoin de votre autorisation pour partager ces informations avec %{sp} :'
+help_text.requested_attributes.ial2_reverified_consent_info_html: 'Étant donné que vous avez revérifié votre identité, nous avons besoin de votre autorisation pour partager ces informations avec %{sp_html} :'
+help_text.requested_attributes.intro_html: 'Nous partagerons ces informations avec %{sp_html}:'
help_text.requested_attributes.phone: Numéro de téléphone
help_text.requested_attributes.social_security_number: Numéro de sécurité sociale
help_text.requested_attributes.verified_at: Mis à jour le
help_text.requested_attributes.x509_issuer: Émetteur PIV/CAC
help_text.requested_attributes.x509_subject: Identité PIV/CAC
+help_text.select_preferred_email: Vous pouvez modifier l’adresse e-mail que vous partagez avec %{sp} car vous possédez plusieurs adresses e-mail associées à votre compte %{app_name}.
i18n.language: Langue
i18n.locale.en: English
i18n.locale.es: Español
@@ -1606,6 +1607,7 @@ titles.reactivate_account: Réactiver votre compte
titles.registrations.new: Créer votre compte
titles.revoke_consent: Révoquer le consentement
titles.rules_of_use: Règles d’utilisation
+titles.select_email: Sélectionner l’adresse e-mail de votre choix
titles.sign_up.completion_consent_expired_ial1: Cela fait un an que vous nous avez donné votre consentement pour partager vos informations
titles.sign_up.completion_consent_expired_ial2: Cela fait un an que vous nous avez donné votre consentement pour partager votre identité vérifiée
titles.sign_up.completion_first_sign_in: Continuer vers %{sp}
diff --git a/config/locales/zh.yml b/config/locales/zh.yml
index 316555349c5..d2c92c4f34b 100644
--- a/config/locales/zh.yml
+++ b/config/locales/zh.yml
@@ -703,6 +703,7 @@ doc_auth.tips.review_issues_id_text1: 你是否使用了暗色背景?
doc_auth.tips.review_issues_id_text2: 是否在平坦平面上拍的照?
doc_auth.tips.review_issues_id_text3: 你相机的闪光灯是否关闭?
doc_auth.tips.review_issues_id_text4: 是否所有细节都清晰可见?
+email_address.not_found: 未找到电子邮件
email_addresses.add.duplicate: 该电邮地址已注册到你的账户。
email_addresses.add.limit: 你添加的电邮地址数目已达最多。
email_addresses.delete.bullet1: 使用该电邮地址你无法登录进入 %{app_name} (或任何其他与你账户关联的政府应用程序)。
@@ -971,18 +972,18 @@ headings.webauthn_setup.new: 插入您的安全密钥
help_text.requested_attributes.address: 地址
help_text.requested_attributes.all_emails: 你账户上的电邮地址
help_text.requested_attributes.birthdate: 生日
+help_text.requested_attributes.change_email_link: 更改
+help_text.requested_attributes.consent_reminder_html: 你每年都必须授权同意与 %{sp_html} 分享信息。
help_text.requested_attributes.email: 电邮地址
help_text.requested_attributes.full_name: 姓名
-help_text.requested_attributes.ial1_consent_reminder_html: 你每年都必须授权同意与 %{sp} 分享信息。我们将与 %{sp} 分享你的信息来连接你账户。
-help_text.requested_attributes.ial1_intro_html: 我们将与 %{sp} 分享你的信息来连接你账户。
-help_text.requested_attributes.ial2_consent_reminder_html: '%{sp} 需要知道你是谁才能连接你的账户。你每年都必须授权同意与 %{sp} 分享已验证过的你的信息。我们会分享这些信息:'
-help_text.requested_attributes.ial2_intro_html: '%{sp} 需要知道你是谁才能连接你的账户。我们会与 %{sp} 分享这些信息:'
-help_text.requested_attributes.ial2_reverified_consent_info: '因为你重新验证了身份,我们需要得到你的许可才能与 %{sp} 分享该信息。'
+help_text.requested_attributes.ial2_reverified_consent_info_html: '因为你重新验证了身份,我们需要得到你的许可才能与 %{sp_html} 分享该信息。'
+help_text.requested_attributes.intro_html: 我们会与 %{sp_html} 分享这些信息:
help_text.requested_attributes.phone: 电话号码
help_text.requested_attributes.social_security_number: 社会保障号码
help_text.requested_attributes.verified_at: 更新是在
help_text.requested_attributes.x509_issuer: PIV/CAC 发放方
help_text.requested_attributes.x509_subject: PIV/CAC 身份
+help_text.select_preferred_email: 因为你有多个电邮与 %{app_name} 账户相关,你可以更改与我们 %{sp} 构分享哪个。
i18n.language: 语言
i18n.locale.en: English
i18n.locale.es: Español
@@ -1619,6 +1620,7 @@ titles.reactivate_account: 重新激活你账户
titles.registrations.new: 设立账户
titles.revoke_consent: 撤销同意
titles.rules_of_use: 使用规则
+titles.select_email: 选择你比较愿意分享的电邮
titles.sign_up.completion_consent_expired_ial1: 从你上次授权我们分享你的信息已经一年了。
titles.sign_up.completion_consent_expired_ial2: 从你上次授权我们分享你验证过的身份已经一年了。
titles.sign_up.completion_first_sign_in: 继续到 %{sp}
diff --git a/config/routes.rb b/config/routes.rb
index 30863b3e145..eb35a338ee7 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -304,6 +304,8 @@
get '/sign_up/enter_email' => 'sign_up/registrations#new', as: :sign_up_email
post '/sign_up/enter_email' => 'sign_up/registrations#create', as: :sign_up_register
get '/sign_up/enter_password' => 'sign_up/passwords#new'
+ get '/sign_up/select_email' => 'sign_up/select_email#show'
+ post '/sign_up/select_email' => 'sign_up/select_email#create'
get '/sign_up/verify_email' => 'sign_up/emails#show', as: :sign_up_verify_email
get '/sign_up/completed' => 'sign_up/completions#show', as: :sign_up_completed
post '/sign_up/completed' => 'sign_up/completions#update'
diff --git a/db/primary_migrate/20240809152808_create_federal_email_domain.rb b/db/primary_migrate/20240809152808_create_federal_email_domain.rb
new file mode 100644
index 00000000000..ef255ae82cc
--- /dev/null
+++ b/db/primary_migrate/20240809152808_create_federal_email_domain.rb
@@ -0,0 +1,9 @@
+class CreateFederalEmailDomain < ActiveRecord::Migration[7.1]
+ def change
+ create_table :federal_email_domains do |t|
+ t.citext :name, null: false
+ end
+
+ add_index :federal_email_domains, :name, unique: true
+ end
+end
diff --git a/db/primary_migrate/20240828182041_add_aaguid_to_webauthn_configuration.rb b/db/primary_migrate/20240828182041_add_aaguid_to_webauthn_configuration.rb
new file mode 100644
index 00000000000..bed1e8ca51c
--- /dev/null
+++ b/db/primary_migrate/20240828182041_add_aaguid_to_webauthn_configuration.rb
@@ -0,0 +1,5 @@
+class AddAaguidToWebauthnConfiguration < ActiveRecord::Migration[7.1]
+ def change
+ add_column :webauthn_configurations, :aaguid, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index ef682f62526..385018287e3 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_08_22_122355) do
+ActiveRecord::Schema[7.1].define(version: 2024_08_28_182041) do
# These are extensions that must be enabled in order to support this database
enable_extension "citext"
enable_extension "pg_stat_statements"
@@ -226,6 +226,11 @@
t.index ["user_id", "created_at"], name: "index_events_on_user_id_and_created_at"
end
+ create_table "federal_email_domains", force: :cascade do |t|
+ t.citext "name", null: false
+ t.index ["name"], name: "index_federal_email_domains_on_name", unique: true
+ end
+
create_table "fraud_review_requests", force: :cascade do |t|
t.integer "user_id"
t.string "uuid"
@@ -651,6 +656,7 @@
t.boolean "platform_authenticator"
t.string "transports", array: true
t.jsonb "authenticator_data_flags"
+ t.string "aaguid"
t.index ["user_id"], name: "index_webauthn_configurations_on_user_id"
end
diff --git a/lib/ab_test.rb b/lib/ab_test.rb
index ef090aabe43..840cd1cc828 100644
--- a/lib/ab_test.rb
+++ b/lib/ab_test.rb
@@ -5,7 +5,7 @@ class AbTest
MAX_SHA = (16 ** 64) - 1
- # @param [Proc,RegExp,string,Boolean,nil] should_log Controls whether bucket data for this
+ # @param [Proc,Regexp,string,Boolean,nil] should_log Controls whether bucket data for this
# A/B test is logged with specific
# events.
# @yieldparam [ActionDispatch::Request] request
diff --git a/lib/identity_config.rb b/lib/identity_config.rb
index 51d032a7b1e..246d4210f73 100644
--- a/lib/identity_config.rb
+++ b/lib/identity_config.rb
@@ -117,9 +117,10 @@ def self.store
config.add(:doc_auth_separate_pages_enabled, type: :boolean)
config.add(:doc_auth_supported_country_codes, type: :json)
config.add(:doc_auth_vendor, type: :string)
- config.add(:doc_auth_vendor_randomize, type: :boolean)
- config.add(:doc_auth_vendor_randomize_alternate_vendor, type: :string)
- config.add(:doc_auth_vendor_randomize_percent, type: :integer)
+ config.add(:doc_auth_vendor_default, type: :string)
+ config.add(:doc_auth_vendor_lexis_nexis_percent, type: :integer)
+ config.add(:doc_auth_vendor_socure_percent, type: :integer)
+ config.add(:doc_auth_vendor_switching_enabled, type: :boolean)
config.add(:doc_capture_polling_enabled, type: :boolean)
config.add(:doc_capture_request_valid_for_minutes, type: :integer)
config.add(:drop_off_report_config, type: :json)
@@ -137,6 +138,7 @@ def self.store
config.add(:event_disavowal_expiration_hours, type: :integer)
config.add(:feature_idv_force_gpo_verification_enabled, type: :boolean)
config.add(:feature_idv_hybrid_flow_enabled, type: :boolean)
+ config.add(:feature_select_email_to_share_enabled, type: :boolean)
config.add(:geo_data_file_path, type: :string)
config.add(:get_usps_proofing_results_job_cron, type: :string)
config.add(:get_usps_proofing_results_job_reprocess_delay_minutes, type: :integer)
@@ -383,6 +385,7 @@ def self.store
config.add(:sign_in_user_id_per_ip_max_attempts, type: :integer)
config.add(:sign_in_recaptcha_score_threshold, type: :float)
config.add(:skip_encryption_allowed_list, type: :json)
+ config.add(:socure_webhook_enabled, type: :boolean)
config.add(:socure_webhook_secret_key, type: :string)
config.add(:socure_webhook_secret_key_queue, type: :json)
config.add(:sp_handoff_bounce_max_seconds, type: :integer)
@@ -404,6 +407,7 @@ def self.store
config.add(:usps_auth_token_refresh_job_enabled, type: :boolean)
config.add(:usps_confirmation_max_days, type: :integer)
config.add(:usps_eipp_sponsor_id, type: :string)
+ config.add(:use_fed_domain_class, type: :boolean)
config.add(:usps_ipp_client_id, type: :string)
config.add(:usps_ipp_password, type: :string)
config.add(:usps_ipp_request_timeout, type: :integer)
diff --git a/lib/idp/constants.rb b/lib/idp/constants.rb
index 5a3b32bce42..d7ba925e42d 100644
--- a/lib/idp/constants.rb
+++ b/lib/idp/constants.rb
@@ -12,6 +12,7 @@ module Constants
module Vendors
ACUANT = 'acuant'
LEXIS_NEXIS = 'lexis_nexis'
+ SOCURE = 'socure'
MOCK = 'mock'
USPS = 'usps'
AAMVA = 'aamva'
diff --git a/lib/tasks/federal_email_domains.rake b/lib/tasks/federal_email_domains.rake
new file mode 100644
index 00000000000..95ac234d520
--- /dev/null
+++ b/lib/tasks/federal_email_domains.rake
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'faraday'
+require 'csv'
+
+DOT_GOV_DOWNLOAD_PATH = 'https://raw.githubusercontent.com/cisagov/dotgov-data/main/current-federal.csv'
+namespace :federal_email_domains do
+ task load_to_db: :environment do |_task, _args|
+ response = Faraday.get(DOT_GOV_DOWNLOAD_PATH)
+
+ csv = CSV.parse(response.body, col_sep: ',', headers: true)
+ csv.each do |row|
+ FederalEmailDomain.find_or_create_by(name: row['Domain name'])
+ end
+ end
+end
+# rake "federal_email_domains:load_to_db"
diff --git a/package.json b/package.json
index 65f2d0b3321..521c35d6c8d 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"source-map-loader": "^4.0.0",
- "webpack": "^5.91.0",
+ "webpack": "^5.94.0",
"webpack-assets-manifest": "^5.2.1",
"webpack-cli": "^5.1.4"
},
diff --git a/spec/config/initializers/ab_tests_spec.rb b/spec/config/initializers/ab_tests_spec.rb
index b6313e51bd6..f858344346d 100644
--- a/spec/config/initializers/ab_tests_spec.rb
+++ b/spec/config/initializers/ab_tests_spec.rb
@@ -3,13 +3,7 @@
RSpec.describe AbTests do
describe '#all' do
it 'returns all registered A/B tests' do
- expect(AbTests.all).to match(
- {
- ACUANT_SDK: an_instance_of(AbTest),
- DOC_AUTH_VENDOR: an_instance_of(AbTest),
-
- },
- )
+ expect(AbTests.all.values).to all(be_kind_of(AbTest))
end
end
@@ -119,20 +113,20 @@
let(:enable_ab_test) do
-> {
- allow(IdentityConfig.store).to receive(:doc_auth_vendor).
+ allow(IdentityConfig.store).to receive(:doc_auth_vendor_default).
and_return('vendor_a')
- allow(IdentityConfig.store).to receive(:doc_auth_vendor_randomize).
+ allow(IdentityConfig.store).to receive(:doc_auth_vendor_switching_enabled).
and_return(true)
- allow(IdentityConfig.store).to receive(:doc_auth_vendor_randomize_alternate_vendor).
- and_return('vendor_b')
- allow(IdentityConfig.store).to receive(:doc_auth_vendor_randomize_percent).
+ allow(IdentityConfig.store).to receive(:doc_auth_vendor_socure_percent).
and_return(50)
+ allow(IdentityConfig.store).to receive(:doc_auth_vendor_lexis_nexis_percent).
+ and_return(30)
}
end
let(:disable_ab_test) do
-> {
- allow(IdentityConfig.store).to receive(:doc_auth_vendor_randomize).
+ allow(IdentityConfig.store).to receive(:doc_auth_vendor_switching_enabled).
and_return(false)
}
end
diff --git a/spec/controllers/sign_up/select_email_controller_spec.rb b/spec/controllers/sign_up/select_email_controller_spec.rb
new file mode 100644
index 00000000000..898c402091d
--- /dev/null
+++ b/spec/controllers/sign_up/select_email_controller_spec.rb
@@ -0,0 +1,53 @@
+require 'rails_helper'
+
+RSpec.describe SignUp::SelectEmailController do
+ describe 'before_actions' do
+ it 'requires the user be logged in and authenticated' do
+ expect(subject).to have_actions(
+ :before,
+ :confirm_two_factor_authenticated,
+ )
+ end
+
+ it 'requires the user be in the completions flow' do
+ expect(subject).to have_actions(
+ :before,
+ :verify_needs_completions_screen,
+ )
+ end
+ end
+
+ describe '#create' do
+ let(:email) { 'michael.motorist@email.com' }
+ let(:email2) { 'michael.motorist2@email.com' }
+ let(:email3) { 'david.motorist@email.com' }
+ let(:user) { create(:user) }
+
+ before do
+ user.email_addresses = []
+ create(:email_address, user:, email: email)
+ create(:email_address, user:, email: email2)
+ end
+
+ it 'updates selected email address' do
+ post :create, params: { selected_email_id: email2 }
+
+ expect(user.email_addresses.last.email).
+ to include('michael.motorist2@email.com')
+ end
+
+ context 'with a corrupted email selected_email_id form' do
+ render_views
+ it 'rejects email not belonging to the user' do
+ stub_sign_in(user)
+ allow(controller).to receive(:needs_completion_screen_reason).and_return(true)
+ post :create, params: { selected_email_id: email3 }
+
+ expect(user.email_addresses.last.email).
+ to include('michael.motorist2@email.com')
+
+ expect(response).to redirect_to(sign_up_select_email_path)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/socure_webhook_controller_spec.rb b/spec/controllers/socure_webhook_controller_spec.rb
index 04a06d36d03..23d88f59fb7 100644
--- a/spec/controllers/socure_webhook_controller_spec.rb
+++ b/spec/controllers/socure_webhook_controller_spec.rb
@@ -6,12 +6,15 @@
describe 'POST /api/webhooks/socure/event' do
let(:socure_secret_key) { 'this-is-a-secret' }
let(:socure_secret_key_queue) { ['this-is-an-old-secret', 'this-is-an-older-secret'] }
+ let(:socure_webhook_enabled) { true }
before do
allow(IdentityConfig.store).to receive(:socure_webhook_secret_key).
and_return(socure_secret_key)
allow(IdentityConfig.store).to receive(:socure_webhook_secret_key_queue).
and_return(socure_secret_key_queue)
+ allow(IdentityConfig.store).to receive(:socure_webhook_enabled).
+ and_return(socure_webhook_enabled)
end
it 'returns OK with a correct secret key' do
@@ -40,5 +43,15 @@
expect(response).to have_http_status(:unauthorized)
end
+
+ context 'when socure webhook disabled' do
+ let(:socure_webhook_enabled) { false }
+ it 'the webhook route does not exist' do
+ request.headers['Authorization'] = socure_secret_key
+ post :create
+
+ expect(response).to be_not_found
+ end
+ end
end
end
diff --git a/spec/controllers/users/piv_cac_recommended_controller_spec.rb b/spec/controllers/users/piv_cac_recommended_controller_spec.rb
index a9bd996aa9f..69d179b4506 100644
--- a/spec/controllers/users/piv_cac_recommended_controller_spec.rb
+++ b/spec/controllers/users/piv_cac_recommended_controller_spec.rb
@@ -2,7 +2,8 @@
RSpec.describe Users::PivCacRecommendedController do
describe 'New user' do
- let(:user) { create(:user, email: 'example@example.gov') }
+ let(:user) { create(:user, email: 'example@gsa.gov') }
+ let!(:federal_domain) { create(:federal_email_domain, name: 'gsa.gov') }
before do
stub_sign_in_before_2fa(user)
stub_analytics
@@ -28,10 +29,10 @@
end
describe 'Sign in flow' do
- let(:user) { create(:user, :with_phone, { email: 'example@example.gov' }) }
+ let(:user) { create(:user, :with_phone, { email: 'example@gsa.gov' }) }
+ let!(:federal_domain) { create(:federal_email_domain, name: 'gsa.gov') }
before do
stub_analytics
-
stub_sign_in(user)
user.reload
end
@@ -49,7 +50,8 @@
end
context '#confirm' do
- let(:user) { create(:user, email: 'example@example.gov') }
+ let(:user) { create(:user, email: 'example@gsa.gov') }
+ let!(:federal_domain) { create(:federal_email_domain, name: 'gsa.gov') }
before do
stub_sign_in_before_2fa(user)
stub_analytics
@@ -77,7 +79,8 @@
end
context '#skip' do
- let(:user) { create(:user, email: 'example@example.gov') }
+ let(:user) { create(:user, email: 'example@gsa.gov') }
+ let!(:federal_domain) { create(:federal_email_domain, name: 'gsa.gov') }
before do
stub_sign_in_before_2fa(user)
stub_analytics
diff --git a/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb b/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb
index e22cfc60ffd..5672ea37eb6 100644
--- a/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb
+++ b/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb
@@ -19,10 +19,16 @@
)
end
- context 'with user having gov or mil email' do
+ context 'with user having gov or mil email and use_fed_domain_class set to false' do
let(:user) do
create(:user, email: 'example@example.gov', piv_cac_recommended_dismissed_at: Time.zone.now)
end
+ let!(:federal_domain) { create(:federal_email_domain, name: 'gsa.gov') }
+
+ before do
+ allow(IdentityConfig.store).to receive(:use_fed_domain_class).and_return(false)
+ end
+
context 'having already visited the PIV interstitial page' do
it 'tracks the visit in analytics' do
get :index
@@ -48,6 +54,40 @@
end
end
+ context 'with user having gov or mil email and use_fed_domain_class set to true' do
+ before do
+ allow(IdentityConfig.store).to receive(:use_fed_domain_class).and_return(true)
+ end
+
+ let!(:federal_domain) { create(:federal_email_domain, name: 'gsa.gov') }
+ let(:user) do
+ create(:user, email: 'example@gsa.gov', piv_cac_recommended_dismissed_at: Time.zone.now)
+ end
+ context 'having already visited the PIV interstitial page' do
+ it 'tracks the visit in analytics' do
+ get :index
+
+ expect(@analytics).to have_logged_event(
+ 'User Registration: 2FA Setup visited',
+ enabled_mfa_methods_count: 0,
+ gov_or_mil_email: true,
+ )
+ end
+ end
+
+ context 'directed to page without having visited PIV interstitial page' do
+ let(:user) do
+ create(:user, email: 'example@gsa.gov')
+ end
+
+ it 'redirects user to piv_recommended_path' do
+ get :index
+
+ expect(response).to redirect_to(login_piv_cac_recommended_url)
+ end
+ end
+ end
+
context 'when signed out' do
let(:user) { nil }
diff --git a/spec/factories/federal_email_domain.rb b/spec/factories/federal_email_domain.rb
new file mode 100644
index 00000000000..6903d7eb11a
--- /dev/null
+++ b/spec/factories/federal_email_domain.rb
@@ -0,0 +1,4 @@
+FactoryBot.define do
+ factory :federal_email_domain do
+ end
+end
diff --git a/spec/factories/in_person_enrollments.rb b/spec/factories/in_person_enrollments.rb
index 70238b182c2..bfb36296ea4 100644
--- a/spec/factories/in_person_enrollments.rb
+++ b/spec/factories/in_person_enrollments.rb
@@ -16,6 +16,9 @@
enrollment_established_at { Time.zone.now }
status { :pending }
status_updated_at { Time.zone.now }
+ profile do
+ association(:profile, :in_person_verification_pending, user: user)
+ end
end
trait :expired do
diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb
index b321848299e..2e722d7f654 100644
--- a/spec/features/idv/analytics_spec.rb
+++ b/spec/features/idv/analytics_spec.rb
@@ -57,7 +57,8 @@
verified_attributes: [],
state: 'MT',
state_id_jurisdiction: 'ND',
- state_id_number: '#############' }
+ state_id_number: '#############',
+ jurisdiction_in_maintenance_window: false }
end
let(:resolution_block) do
diff --git a/spec/features/multiple_emails/sp_sign_in_spec.rb b/spec/features/multiple_emails/sp_sign_in_spec.rb
index dbae13eb376..be6d75b55b6 100644
--- a/spec/features/multiple_emails/sp_sign_in_spec.rb
+++ b/spec/features/multiple_emails/sp_sign_in_spec.rb
@@ -17,15 +17,62 @@
fill_in_code_with_last_phone_otp
click_submit_default
click_agree_and_continue if current_path == sign_up_completed_path
-
decoded_id_token = fetch_oidc_id_token_info
- expect(decoded_id_token[:email]).to eq(email)
+ expect(decoded_id_token[:email]).to eq(emails.first)
expect(decoded_id_token[:all_emails]).to be_nil
Capybara.reset_session!
end
end
+ scenario 'signing in with OIDC and selecting an alternative email address at first sign in' do
+ user = create(:user, :fully_registered, :with_multiple_emails)
+ emails = user.reload.email_addresses.map(&:email)
+
+ visit_idp_from_oidc_sp(scope: 'openid email')
+ signin(emails.first, user.password)
+ fill_in_code_with_last_phone_otp
+ click_submit_default
+ click_link(t('help_text.requested_attributes.change_email_link'))
+
+ choose emails.second
+
+ click_button(t('help_text.requested_attributes.change_email_link'))
+
+ expect(current_path).to eq(sign_up_completed_path)
+ click_agree_and_continue
+ decoded_id_token = fetch_oidc_id_token_info
+ expect(decoded_id_token[:email]).to eq(emails.second)
+ end
+
+ scenario 'signing in with OIDC after deleting email linked to identity' do
+ user = create(:user, :fully_registered)
+ email1 = create(:email_address, user:, email: 'email1@example.com')
+ email2 = create(:email_address, user:, email: 'email2@example.com')
+
+ # Link identity with email
+ visit_idp_from_oidc_sp(scope: 'openid email')
+ signin(email1.email, user.password)
+ fill_in_code_with_last_phone_otp
+ click_submit_default
+ click_link(t('help_text.requested_attributes.change_email_link'))
+ choose email2.email
+ click_button(t('help_text.requested_attributes.change_email_link'))
+ expect(current_path).to eq(sign_up_completed_path)
+ click_agree_and_continue
+ click_submit_default
+
+ # Delete email from account
+ visit manage_email_confirm_delete_url(id: email2.id)
+ click_button t('forms.email.buttons.delete')
+
+ # Sign in again to partner application
+ visit_idp_from_oidc_sp(scope: 'openid email')
+
+ decoded_id_token = fetch_oidc_id_token_info
+ expect(decoded_id_token[:email]).to eq(email1.email)
+ end
+
scenario 'signing in with SAML sends the email address used to sign in' do
user = create(:user, :fully_registered, :with_multiple_emails)
emails = user.reload.email_addresses.map(&:email)
@@ -42,12 +89,63 @@
xmldoc = SamlResponseDoc.new('feature', 'response_assertion')
email_from_saml_response = xmldoc.attribute_value_for('email')
-
- expect(email_from_saml_response).to eq(email)
+ expect(email_from_saml_response).to eq(emails.first)
Capybara.reset_session!
end
end
+
+ scenario 'signing in with SAML and selecting an alternative email address at first sign in' do
+ user = create(:user, :fully_registered, :with_multiple_emails)
+ emails = user.reload.email_addresses.map(&:email)
+
+ visit authn_request
+ signin(emails.first, user.password)
+ fill_in_code_with_last_phone_otp
+ click_submit_default_twice
+
+ click_link(t('help_text.requested_attributes.change_email_link'))
+ choose emails.second
+ click_button(t('help_text.requested_attributes.change_email_link'))
+
+ expect(current_path).to eq(sign_up_completed_path)
+
+ click_agree_and_continue
+ click_submit_default
+
+ xmldoc = SamlResponseDoc.new('feature', 'response_assertion')
+ email_from_saml_response = xmldoc.attribute_value_for('email')
+ expect(email_from_saml_response).to eq(emails.second)
+ end
+
+ scenario 'signing in with SAML after deleting email linked to identity' do
+ user = create(:user, :fully_registered)
+ email1 = create(:email_address, user:, email: 'email1@example.com')
+ email2 = create(:email_address, user:, email: 'email2@example.com')
+
+ # Link identity with email
+ visit authn_request
+ signin(email1.email, user.password)
+ fill_in_code_with_last_phone_otp
+ click_submit_default_twice
+ click_link(t('help_text.requested_attributes.change_email_link'))
+ choose email2.email
+ click_button(t('help_text.requested_attributes.change_email_link'))
+ expect(current_path).to eq(sign_up_completed_path)
+ click_agree_and_continue
+ click_submit_default
+
+ # Delete email from account
+ visit manage_email_confirm_delete_url(id: email2.id)
+ click_button t('forms.email.buttons.delete')
+
+ # Sign in again to partner application
+ visit authn_request
+
+ xmldoc = SamlResponseDoc.new('feature', 'response_assertion')
+ email_from_saml_response = xmldoc.attribute_value_for('email')
+ expect(email_from_saml_response).to eq(email1.email)
+ end
end
context 'with the all_emails scope' do
diff --git a/spec/features/sign_in/piv_recommended_after_sign_in_spec.rb b/spec/features/sign_in/piv_recommended_after_sign_in_spec.rb
new file mode 100644
index 00000000000..2906a36a34e
--- /dev/null
+++ b/spec/features/sign_in/piv_recommended_after_sign_in_spec.rb
@@ -0,0 +1,132 @@
+require 'rails_helper'
+
+RSpec.feature 'Piv recommended after Sign in' do
+ context 'use_fed_domain_class set to true' do
+ let!(:federal_email_domain) { create(:federal_email_domain, name: 'gsa.gov') }
+
+ before do
+ allow(IdentityConfig.store).to receive(:use_fed_domain_class).and_return(true)
+ end
+
+ scenario 'User with valid fed email directed to recommend page and get to setup piv' do
+ user = create(:user, :with_phone, { email: 'example@gsa.gov' })
+
+ visit new_user_session_path
+ fill_in_credentials_and_submit(user.email, user.password)
+ fill_in_code_with_last_phone_otp
+ click_submit_default
+ expect(page).to have_current_path(login_piv_cac_recommended_path)
+ click_button(t('two_factor_authentication.piv_cac_upsell.add_piv'))
+ expect(page).to have_current_path(setup_piv_cac_path)
+ end
+
+ scenario 'User with mil email directed to recommended PIV page and goes to add piv page' do
+ user = create(:user, :with_phone, { email: 'example@army.mil' })
+
+ visit new_user_session_path
+ fill_in_credentials_and_submit(user.email, user.password)
+ fill_in_code_with_last_phone_otp
+ click_submit_default
+ expect(page).to have_current_path(login_piv_cac_recommended_path)
+ click_button(t('two_factor_authentication.piv_cac_upsell.add_piv'))
+ expect(page).to have_current_path(setup_piv_cac_path)
+ end
+
+ scenario 'User with fed email and skips recommendation page' do
+ user = create(:user, :with_phone, { email: 'example@gsa.gov' })
+
+ visit new_user_session_path
+ fill_in_credentials_and_submit(user.email, user.password)
+ fill_in_code_with_last_phone_otp
+ click_submit_default
+ expect(page).to have_current_path(login_piv_cac_recommended_path)
+ click_button(t('two_factor_authentication.piv_cac_upsell.skip'))
+ expect(page).to have_current_path(account_path)
+ end
+
+ scenario 'User with mil email and skips recommendation page' do
+ user = create(:user, :with_phone, { email: 'example@army.mil' })
+
+ visit new_user_session_path
+ fill_in_credentials_and_submit(user.email, user.password)
+ fill_in_code_with_last_phone_otp
+ click_submit_default
+ expect(page).to have_current_path(login_piv_cac_recommended_path)
+ click_button(t('two_factor_authentication.piv_cac_upsell.skip'))
+ expect(page).to have_current_path(account_path)
+ end
+
+ scenario 'User with invalid .gov email directed to account page' do
+ user = create(:user, :with_phone, { email: 'example@bad.gov' })
+
+ visit new_user_session_path
+ fill_in_credentials_and_submit(user.email, user.password)
+ fill_in_code_with_last_phone_otp
+ click_submit_default
+ expect(page).to have_current_path(account_path)
+ end
+ end
+
+ context 'use_fed_domain_class set to false' do
+ before do
+ allow(IdentityConfig.store).to receive(:use_fed_domain_class).and_return(false)
+ end
+ scenario 'User with .gov email directed to recommend page and get to setup piv' do
+ user = create(:user, :with_phone, { email: 'example@good.gov' })
+
+ visit new_user_session_path
+ fill_in_credentials_and_submit(user.email, user.password)
+ fill_in_code_with_last_phone_otp
+ click_submit_default
+ expect(page).to have_current_path(login_piv_cac_recommended_path)
+ click_button(t('two_factor_authentication.piv_cac_upsell.add_piv'))
+ expect(page).to have_current_path(setup_piv_cac_path)
+ end
+
+ scenario 'User with .mil email directed to recommended PIV page and goes to add piv page' do
+ user = create(:user, :with_phone, { email: 'example@army.mil' })
+
+ visit new_user_session_path
+ fill_in_credentials_and_submit(user.email, user.password)
+ fill_in_code_with_last_phone_otp
+ click_submit_default
+ expect(page).to have_current_path(login_piv_cac_recommended_path)
+ click_button(t('two_factor_authentication.piv_cac_upsell.add_piv'))
+ expect(page).to have_current_path(setup_piv_cac_path)
+ end
+
+ scenario 'User with fed email and skips recommendation page' do
+ user = create(:user, :with_phone, { email: 'example@example.gov' })
+
+ visit new_user_session_path
+ fill_in_credentials_and_submit(user.email, user.password)
+ fill_in_code_with_last_phone_otp
+ click_submit_default
+ expect(page).to have_current_path(login_piv_cac_recommended_path)
+ click_button(t('two_factor_authentication.piv_cac_upsell.skip'))
+ expect(page).to have_current_path(account_path)
+ end
+
+ scenario 'User with mil email and skips recommendation page' do
+ user = create(:user, :with_phone, { email: 'example@army.mil' })
+
+ visit new_user_session_path
+ fill_in_credentials_and_submit(user.email, user.password)
+ fill_in_code_with_last_phone_otp
+ click_submit_default
+ expect(page).to have_current_path(login_piv_cac_recommended_path)
+ click_button(t('two_factor_authentication.piv_cac_upsell.skip'))
+ expect(page).to have_current_path(account_path)
+ end
+
+ scenario 'User with invalid no .gov or .mil email directed to account page' do
+ user = create(:user, :with_phone, { email: 'example@bad.com' })
+
+ visit new_user_session_path
+ fill_in_credentials_and_submit(user.email, user.password)
+ fill_in_code_with_last_phone_otp
+ click_submit_default
+ expect(page).to have_current_path(account_path)
+ end
+ end
+end
diff --git a/spec/features/users/sign_in_spec.rb b/spec/features/users/sign_in_spec.rb
index b05147aaea5..215c19fe083 100644
--- a/spec/features/users/sign_in_spec.rb
+++ b/spec/features/users/sign_in_spec.rb
@@ -102,30 +102,6 @@
expect(oidc_redirect_url).to start_with service_provider.redirect_uris.first
end
- scenario 'User with gov/mil email directed to recommended PIV page' do
- user = create(:user, :with_phone, { email: 'example@example.gov' })
-
- visit new_user_session_path
- fill_in_credentials_and_submit(user.email, user.password)
- fill_in_code_with_last_phone_otp
- click_submit_default
- expect(page).to have_current_path(login_piv_cac_recommended_path)
- click_button(t('two_factor_authentication.piv_cac_upsell.add_piv'))
- expect(page).to have_current_path(setup_piv_cac_path)
- end
-
- scenario 'User with gov/mil email and skips recommendation page' do
- user = create(:user, :with_phone, { email: 'example@example.gov' })
-
- visit new_user_session_path
- fill_in_credentials_and_submit(user.email, user.password)
- fill_in_code_with_last_phone_otp
- click_submit_default
- expect(page).to have_current_path(login_piv_cac_recommended_path)
- click_button(t('two_factor_authentication.piv_cac_upsell.skip'))
- expect(page).to have_current_path(account_path)
- end
-
scenario 'user attempts sign in with piv/cac with no account then creates account' do
visit_idp_from_sp_with_ial1(:oidc)
click_on t('account.login.piv_cac')
diff --git a/spec/features/users/sign_up_spec.rb b/spec/features/users/sign_up_spec.rb
index 6d1d2f0fa95..b71f4ce0a78 100644
--- a/spec/features/users/sign_up_spec.rb
+++ b/spec/features/users/sign_up_spec.rb
@@ -511,30 +511,117 @@ def clipboard_text
end
end
- describe 'mil or gov email account' do
- before do
- confirm_email('test@test.gov')
- submit_form_with_valid_password
- end
- it 'should land user on piv cac suggestion page' do
- expect(current_path).to eq login_piv_cac_recommended_path
- end
+ describe 'User Directed to Piv Cac recommended' do
+ context 'set config use_fed_domain_class to false' do
+ let(:email) { 'test@test.gov' }
+ before do
+ allow(IdentityConfig.store).to receive(:use_fed_domain_class).and_return(false)
+ end
- context 'user can skip piv cac prompt' do
- it 'should skip piv cac prompt and land on mfa screen' do
+ it 'should land user on piv cac suggestion page' do
+ confirm_email(email)
+ submit_form_with_valid_password
expect(current_path).to eq login_piv_cac_recommended_path
- click_button t('two_factor_authentication.piv_cac_upsell.choose_other_method')
+ end
- expect(current_path).to eq authentication_methods_setup_path
+ context 'user can skip piv cac prompt' do
+ it 'should skip piv cac prompt and land on mfa screen' do
+ confirm_email(email)
+ submit_form_with_valid_password
+ expect(current_path).to eq login_piv_cac_recommended_path
+ click_button t('two_factor_authentication.piv_cac_upsell.choose_other_method')
+
+ expect(current_path).to eq authentication_methods_setup_path
+ end
+ end
+
+ context 'user who selects to add piv is directed to piv screen' do
+ it 'should be directed straight to piv add screen' do
+ confirm_email(email)
+ submit_form_with_valid_password
+ expect(current_path).to eq login_piv_cac_recommended_path
+ click_button t('two_factor_authentication.piv_cac_upsell.add_piv')
+
+ expect(current_path).to eq setup_piv_cac_path
+ end
end
end
- context 'user who selects to add piv is directed to piv screen' do
- it 'should be directed straight to piv add screen' do
- expect(current_path).to eq login_piv_cac_recommended_path
- click_button t('two_factor_authentication.piv_cac_upsell.add_piv')
+ context 'set config use_fed_domain_class to true' do
+ let!(:federal_email_domain) { create(:federal_email_domain, name: 'gsa.gov') }
+ let(:email) { 'test@gsa.gov' }
+
+ before do
+ allow(IdentityConfig.store).to receive(:use_fed_domain_class).and_return(true)
+ end
+ context 'valid fed email' do
+ it 'should land user on piv cac suggestion page when fed government' do
+ confirm_email(email)
+ submit_form_with_valid_password
+ expect(current_path).to eq login_piv_cac_recommended_path
+ end
+
+ context 'user can skip piv cac prompt' do
+ it 'should skip piv cac prompt and land on mfa screen' do
+ confirm_email(email)
+ submit_form_with_valid_password
+ expect(current_path).to eq login_piv_cac_recommended_path
+ click_button t('two_factor_authentication.piv_cac_upsell.choose_other_method')
+
+ expect(current_path).to eq authentication_methods_setup_path
+ end
+ end
+
+ context 'user who selects to add piv is directed to piv screen' do
+ it 'should be directed straight to piv add screen' do
+ confirm_email(email)
+ submit_form_with_valid_password
+ expect(current_path).to eq login_piv_cac_recommended_path
+ click_button t('two_factor_authentication.piv_cac_upsell.add_piv')
+
+ expect(current_path).to eq setup_piv_cac_path
+ end
+ end
+ end
+
+ context 'any mil email' do
+ let(:email) { 'test@example.mil' }
+ it 'should land user on piv cac suggestion page when fed government' do
+ confirm_email(email)
+ submit_form_with_valid_password
+ expect(current_path).to eq login_piv_cac_recommended_path
+ end
+
+ context 'user can skip piv cac prompt' do
+ it 'should skip piv cac prompt and land on mfa screen' do
+ confirm_email(email)
+ submit_form_with_valid_password
+ expect(current_path).to eq login_piv_cac_recommended_path
+ click_button t('two_factor_authentication.piv_cac_upsell.choose_other_method')
+
+ expect(current_path).to eq authentication_methods_setup_path
+ end
+ end
+
+ context 'user who selects to add piv is directed to piv screen' do
+ it 'should be directed straight to piv add screen' do
+ confirm_email(email)
+ submit_form_with_valid_password
+ expect(current_path).to eq login_piv_cac_recommended_path
+ click_button t('two_factor_authentication.piv_cac_upsell.add_piv')
+
+ expect(current_path).to eq setup_piv_cac_path
+ end
+ end
+ end
- expect(current_path).to eq setup_piv_cac_path
+ context 'invalid fed email' do
+ let(:email) { 'test@example.gov' }
+ it 'should land user on piv cac suggestion page when fed government' do
+ confirm_email(email)
+ submit_form_with_valid_password
+ expect(current_path).to eq authentication_methods_setup_path
+ end
end
end
end
diff --git a/spec/forms/delete_user_email_form_spec.rb b/spec/forms/delete_user_email_form_spec.rb
index 8bc089fa4b6..68e9fbd88b6 100644
--- a/spec/forms/delete_user_email_form_spec.rb
+++ b/spec/forms/delete_user_email_form_spec.rb
@@ -60,6 +60,18 @@
submit
end
+
+ it 'removes associated identity email address id' do
+ user.identities << ServiceProviderIdentity.create(
+ service_provider: 'http://localhost:3000',
+ last_authenticated_at: Time.zone.now,
+ )
+ user.identities.last.email_address_id = email_address.id
+
+ submit
+
+ expect(user.identities.last.email_address_id).to be(nil)
+ end
end
context 'with a email of a different user' do
diff --git a/spec/forms/openid_connect_authorize_form_spec.rb b/spec/forms/openid_connect_authorize_form_spec.rb
index 568c2553535..580983f27d7 100644
--- a/spec/forms/openid_connect_authorize_form_spec.rb
+++ b/spec/forms/openid_connect_authorize_form_spec.rb
@@ -661,6 +661,7 @@
current_user: user,
ial: 1,
rails_session_id: rails_session_id,
+ email_address_id: 4,
)
identity = user.identities.where(service_provider: client_id).first
@@ -684,6 +685,7 @@
current_user: user,
ial: 1,
rails_session_id: rails_session_id,
+ email_address_id: 4,
)
end
diff --git a/spec/forms/select_email_form_spec.rb b/spec/forms/select_email_form_spec.rb
new file mode 100644
index 00000000000..23a17bdbba5
--- /dev/null
+++ b/spec/forms/select_email_form_spec.rb
@@ -0,0 +1,60 @@
+require 'rails_helper'
+
+RSpec.describe SelectEmailForm do
+ let(:user) { create(:user, :fully_registered, :with_multiple_emails) }
+ describe '#submit' do
+ it 'returns the email successfully' do
+ form = SelectEmailForm.new(user)
+ response = form.submit(selected_email_id: user.email_addresses.last.id)
+
+ expect(response.success?).to eq(true)
+ end
+
+ it 'returns an error when submitting an invalid email' do
+ form = SelectEmailForm.new(user)
+ response = form.submit(selected_email_id: nil)
+
+ expect(response.success?).to eq(false)
+ end
+
+ context 'with an unconfirmed email address added' do
+ before do
+ create(
+ :email_address,
+ email: 'michael.business@business.com',
+ user: user,
+ confirmed_at: nil,
+ confirmation_sent_at: 1.month.ago,
+ )
+ end
+
+ it 'returns an error' do
+ form = SelectEmailForm.new(user)
+ response = form.submit(selected_email_id: user.email_addresses.last.id)
+
+ expect(response.success?).to eq(false)
+ end
+ end
+
+ context 'with another user\'s email' do
+ let(:user2) { create(:user, :fully_registered, :with_multiple_emails) }
+ before do
+ create(
+ :email_address,
+ email: 'michael.business@business.com',
+ user: user2,
+ confirmed_at: nil,
+ confirmation_sent_at: 1.month.ago,
+ )
+ @email2 = user2.email_addresses.last.id
+ end
+
+ it 'returns an error' do
+ form = SelectEmailForm.new(user)
+ response = form.submit(selected_email_id: @email2)
+
+ expect(response.success?).to eq(false)
+ end
+ end
+ end
+end
diff --git a/spec/jobs/get_usps_proofing_results_job_spec.rb b/spec/jobs/get_usps_proofing_results_job_spec.rb
index 77433303c03..454651054c6 100644
--- a/spec/jobs/get_usps_proofing_results_job_spec.rb
+++ b/spec/jobs/get_usps_proofing_results_job_spec.rb
@@ -259,12 +259,6 @@
before do
enrollment_records = InPersonEnrollment.where(id: pending_enrollments.map(&:id))
- # Below sets in_person_verification_pending_at
- # on the profile associated with each pending enrollment
- enrollment_records.each do |enrollment|
- profile = enrollment.profile
- profile.update(in_person_verification_pending_at: enrollment.created_at)
- end
allow(InPersonEnrollment).to receive(:needs_usps_status_check).
and_return(enrollment_records)
allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true)
@@ -824,6 +818,16 @@
request_passed_proofing_unsupported_id_results_response,
)
+ it 'deactivates the associated profile' do
+ expect(pending_enrollment.profile.in_person_verification_pending_at).not_to be_nil
+ job.perform Time.zone.now
+ pending_enrollment.reload
+
+ expect(pending_enrollment.profile.in_person_verification_pending_at).to be_nil
+ expect(pending_enrollment.profile.active).to be false
+ expect(pending_enrollment.profile.deactivation_reason).to eq('verification_cancelled')
+ end
+
it 'logs a message about the unsupported ID' do
expected_wait_until = nil
freeze_time do
@@ -884,10 +888,11 @@
end
it 'deactivates the associated profile' do
+ expect(pending_enrollment.profile.in_person_verification_pending_at).not_to be_nil
job.perform(Time.zone.now)
pending_enrollment.reload
- expect(pending_enrollment.profile).not_to be_active
+ expect(pending_enrollment.profile.active).to be false
expect(pending_enrollment.profile.in_person_verification_pending_at).to be_nil
expect(pending_enrollment.profile.deactivation_reason).to eq('verification_cancelled')
end
@@ -989,6 +994,17 @@
),
)
end
+
+ it 'deactivates the associated profile' do
+ expect(pending_enrollment.profile.in_person_verification_pending_at).not_to be_nil
+ job.perform(Time.zone.now)
+ pending_enrollment.reload
+
+ expect(pending_enrollment.profile.in_person_verification_pending_at).to be_nil
+ expect(pending_enrollment.profile.active).to be false
+ expect(pending_enrollment.profile.deactivation_reason).
+ to eq('verification_cancelled')
+ end
end
context 'when a unique id is invalid' do
@@ -1348,57 +1364,84 @@
),
)
end
+ end
- context 'when the enrollment has failed' do
- before do
- stub_request_failed_proofing_results
- end
+ context 'when the enrollment has failed' do
+ before do
+ stub_request_failed_proofing_results
+ end
+
+ it 'sends proofing failed email on response with failed status' do
+ user = pending_enrollment.user
- it 'sends proofing failed email on response with failed status' do
- user = pending_enrollment.user
-
- freeze_time do
- expect do
- job.perform(Time.zone.now)
- end.to have_enqueued_mail(UserMailer, :in_person_failed).with(
- params: { user: user, email_address: user.email_addresses.first },
- args: [{ enrollment: pending_enrollment }],
- )
- expect(job_analytics).to have_logged_event(
- 'GetUspsProofingResultsJob: Success or failure email initiated',
- hash_including(
- email_type: 'Failed',
- job_name: 'GetUspsProofingResultsJob',
- ),
- )
- end
+ freeze_time do
+ expect do
+ job.perform(Time.zone.now)
+ end.to have_enqueued_mail(UserMailer, :in_person_failed).with(
+ params: { user: user, email_address: user.email_addresses.first },
+ args: [{ enrollment: pending_enrollment }],
+ )
+ expect(job_analytics).to have_logged_event(
+ 'GetUspsProofingResultsJob: Success or failure email initiated',
+ hash_including(
+ email_type: 'Failed',
+ job_name: 'GetUspsProofingResultsJob',
+ ),
+ )
end
end
- end
- it 'deactivates and sets fraud related fields of an expired enrollment' do
- stub_request_expired_id_ipp_proofing_results
+ it 'deactivates the associated profile' do
+ expect(pending_enrollment.profile.in_person_verification_pending_at).not_to be_nil
- job.perform(Time.zone.now)
+ job.perform(Time.zone.now)
+ pending_enrollment.reload
+ expect(pending_enrollment.profile.in_person_verification_pending_at).to be_nil
+ expect(pending_enrollment.profile.active).to be false
+ expect(pending_enrollment.profile.deactivation_reason).
+ to eq('verification_cancelled')
+ end
- profile = pending_enrollment.reload.profile
- expect(profile).not_to be_active
- expect(profile.fraud_review_pending_at).to be_nil
- expect(profile.fraud_rejection_at).not_to be_nil
- expect(job_analytics).to have_logged_event(
- :idv_ipp_deactivated_for_never_visiting_post_office,
- )
+ it 'deactivates and sets fraud related fields of an expired enrollment' do
+ stub_request_expired_id_ipp_proofing_results
+
+ job.perform(Time.zone.now)
+
+ profile = pending_enrollment.reload.profile
+ expect(profile).not_to be_active
+ expect(profile.fraud_review_pending_at).to be_nil
+ expect(profile.fraud_rejection_at).not_to be_nil
+ expect(job_analytics).to have_logged_event(
+ :idv_ipp_deactivated_for_never_visiting_post_office,
+ )
+ end
end
end
end
end
describe 'Proofed with secondary id' do
- let(:pending_enrollment) do
- create(
- :in_person_enrollment, :pending
- )
+ let!(:pending_enrollments) do
+ ['BALTIMORE', 'FRIENDSHIP', 'WASHINGTON', 'ARLINGTON', 'DEANWOOD'].map do |name|
+ create(
+ :in_person_enrollment,
+ :pending,
+ :with_notification_phone_configuration,
+ issuer: 'http://localhost:3000',
+ selected_location_details: { name: name },
+ sponsor_id: usps_ipp_sponsor_id,
+ )
+ end
end
+ let(:pending_enrollment) { pending_enrollments.first }
+
+ before do
+ enrollment_records = InPersonEnrollment.where(id: pending_enrollments.map(&:id))
+ allow(InPersonEnrollment).to receive(:needs_usps_status_check).
+ and_return(enrollment_records)
+ allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true)
+ end
+
before do
allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true)
end
@@ -1417,6 +1460,16 @@
request_passed_proofing_secondary_id_type_results_response,
)
+ it 'deactivates the associated profile' do
+ expect(pending_enrollment.profile.in_person_verification_pending_at).not_to be_nil
+ job.perform(Time.zone.now)
+ pending_enrollment.reload
+
+ expect(pending_enrollment.profile.in_person_verification_pending_at).to be_nil
+ expect(pending_enrollment.profile.active).to be false
+ expect(pending_enrollment.profile.deactivation_reason).to eq('verification_cancelled')
+ end
+
it 'logs a message about enrollment with secondary ID' do
allow(IdentityConfig.store).to receive(
:in_person_send_proofing_notifications_enabled,
@@ -1428,6 +1481,7 @@
end.to have_enqueued_job(InPerson::SendProofingNotificationJob).
with(pending_enrollment.id).at(1.hour.from_now).on_queue(:intentionally_delayed)
end
+
expect(pending_enrollment.proofed_at).to eq(transaction_end_date_time)
expect(pending_enrollment.profile.active).to eq(false)
expect(job_analytics).to have_logged_event(
diff --git a/spec/models/email_address_spec.rb b/spec/models/email_address_spec.rb
index eefbe9832e3..50adfa6256f 100644
--- a/spec/models/email_address_spec.rb
+++ b/spec/models/email_address_spec.rb
@@ -89,25 +89,80 @@
end
end
- describe '#gov_or_mil?' do
- subject(:result) { email_address.gov_or_mil? }
+ describe '#fed_or_mil_email?' do
+ subject(:result) { email_address.fed_or_mil_email? }
- context 'with an email domain ending in anything other than .gov or .mil' do
- let(:email) { 'example@example.com' }
+ context 'with an email domain that is a fed email' do
+ before do
+ allow(IdentityConfig.store).to receive(:use_fed_domain_class).and_return(false)
+ end
+ let(:email) { 'example@example.gov' }
+
+ it { expect(result).to eq(true) }
+ end
+
+ context 'with an email that is a mil email' do
+ let(:email) { 'example@example.mil' }
+
+ it { expect(result).to eq(true) }
+ end
+
+ context 'with an email that is not a mil or fed email' do
+ before do
+ allow(IdentityConfig.store).to receive(:use_fed_domain_class).and_return(true)
+ end
+
+ let(:email) { 'example@bad.gov' }
it { expect(result).to eq(false) }
end
- context 'with an email domain ending in .gov' do
+ context 'with a non fed email while use_fed_domain_class set to true' do
+ before do
+ allow(IdentityConfig.store).to receive(:use_fed_domain_class).and_return(true)
+ end
+ let(:email) { 'example@good.gov' }
+
+ it { expect(result).to eq(false) }
+ end
+ end
+
+ describe '#mil_email?' do
+ subject(:result) { email_address.mil_email? }
+
+ context 'with an email domain not a mil email' do
let(:email) { 'example@example.gov' }
- it { expect(result).to eq(true) }
+ it { expect(result).to eq(false) }
end
- context 'with an email domain ending in .mil' do
+ context 'with an email domain ending in a mil domain email' do
let(:email) { 'example@example.mil' }
it { expect(result).to eq(true) }
end
end
+
+ describe '#fed_email?' do
+ subject(:result) { email_address.fed_email? }
+ let!(:federal_email_domain) { create(:federal_email_domain, name: 'gsa.gov') }
+
+ context 'use_fed_domain_class set to true' do
+ before do
+ allow(IdentityConfig.store).to receive(:use_fed_domain_class).and_return(true)
+ end
+
+ context 'with an email domain not a fed email' do
+ let(:email) { 'example@bad.gov' }
+
+ it { expect(result).to eq(false) }
+ end
+
+ context 'with an email domain ending in a fed domain email' do
+ let(:email) { 'example@gsa.gov' }
+
+ it { expect(result).to eq(true) }
+ end
+ end
+ end
end
diff --git a/spec/models/profile_spec.rb b/spec/models/profile_spec.rb
index b90c1bd4dbb..0f2058980aa 100644
--- a/spec/models/profile_spec.rb
+++ b/spec/models/profile_spec.rb
@@ -1001,10 +1001,10 @@
end
end
- describe '#deactivate_due_to_ipp_expiration' do
+ describe '#deactivate_due_to_in_person_verification_cancelled' do
let(:profile) { create(:profile, :in_person_verification_pending) }
it 'updates the profile' do
- profile.deactivate_due_to_ipp_expiration
+ profile.deactivate_due_to_in_person_verification_cancelled
expect(profile.active).to be false
expect(profile.deactivation_reason).to eq('verification_cancelled')
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 5958a803ed2..046e855586b 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1693,4 +1693,41 @@ def it_should_not_send_survey
expect(user.second_last_signed_in_at).to eq(event2.reload.created_at)
end
end
+
+ describe '#has_fed_or_mil_email?' do
+ before do
+ allow(IdentityConfig.store).to receive(:use_fed_domain_class).and_return(false)
+ end
+
+ context 'with a valid fed email in domain file' do
+ let(:user) { create(:user, email: 'example@example.gov') }
+ it 'should return true' do
+ expect(user.has_fed_or_mil_email?).to eq(true)
+ end
+ end
+
+ context 'with use_fed_domain_class set to false and random .gov email' do
+ let(:user) { create(:user, email: 'example@example.gov') }
+ before do
+ allow(IdentityConfig.store).to receive(:use_fed_domain_class).and_return(false)
+ end
+ it 'should return true' do
+ expect(user.has_fed_or_mil_email?).to eq(true)
+ end
+ end
+
+ context 'with a valid mil email' do
+ let(:user) { create(:user, email: 'example@example.mil') }
+ it 'should return true' do
+ expect(user.has_fed_or_mil_email?).to eq(true)
+ end
+ end
+
+ context 'with an invalid fed or mil email' do
+ let(:user) { create(:user, email: 'example@example.com') }
+ it 'should return false' do
+ expect(user.has_fed_or_mil_email?).to eq(false)
+ end
+ end
+ end
end
diff --git a/spec/presenters/completions_presenter_spec.rb b/spec/presenters/completions_presenter_spec.rb
index 102087eaa70..132dd2fe5e8 100644
--- a/spec/presenters/completions_presenter_spec.rb
+++ b/spec/presenters/completions_presenter_spec.rb
@@ -1,6 +1,9 @@
require 'rails_helper'
RSpec.describe CompletionsPresenter do
+ include ActionView::Helpers::OutputSafetyHelper
+ include ActionView::Helpers::TagHelper
+
let(:identities) do
[
build(
@@ -12,6 +15,7 @@
end
let(:current_user) { create(:user, :fully_registered, identities: identities) }
let(:current_sp) { create(:service_provider, friendly_name: 'Friendly service provider') }
+ let(:selected_email_id) { current_user.email_addresses.first.id }
let(:decrypted_pii) do
Pii::Attributes.new(
first_name: 'Testy',
@@ -38,12 +42,13 @@
subject(:presenter) do
described_class.new(
- current_user: current_user,
- current_sp: current_sp,
- decrypted_pii: decrypted_pii,
- requested_attributes: requested_attributes,
- ial2_requested: ial2_requested,
- completion_context: completion_context,
+ current_user:,
+ current_sp:,
+ decrypted_pii:,
+ requested_attributes:,
+ ial2_requested:,
+ completion_context:,
+ selected_email_id:,
)
end
@@ -147,43 +152,49 @@
end
describe '#intro' do
- describe 'ial1' do
- context 'consent has expired since the last sign in' do
- let(:identities) do
- [
- build(
- :service_provider_identity,
- service_provider: current_sp.issuer,
- last_consented_at: 2.years.ago,
- ),
- ]
- end
- let(:completion_context) { :consent_expired }
+ it 'renders the standard intro message' do
+ expect(presenter.intro).to eq(
+ t(
+ 'help_text.requested_attributes.intro_html',
+ sp_html: content_tag(:strong, current_sp.friendly_name),
+ ),
+ )
+ end
- it 'renders the expired IAL1 consent intro message' do
- expect(presenter.intro).to eq(
- I18n.t(
- 'help_text.requested_attributes.ial1_consent_reminder_html',
- sp: current_sp.friendly_name,
- ),
- )
- end
+ context 'consent has expired since the last sign in' do
+ let(:identities) do
+ [
+ build(
+ :service_provider_identity,
+ service_provider: current_sp.issuer,
+ last_consented_at: 2.years.ago,
+ ),
+ ]
end
+ let(:completion_context) { :consent_expired }
- context 'when consent has not expired' do
- it 'renders the standard intro message' do
- expect(presenter.intro).to eq(
- I18n.t(
- 'help_text.requested_attributes.ial1_intro_html',
- sp: current_sp.friendly_name,
- ),
- )
- end
+ it 'renders the expired consent intro message' do
+ expect(presenter.intro).to eq(
+ safe_join(
+ [
+ t(
+ 'help_text.requested_attributes.consent_reminder_html',
+ sp_html: content_tag(:strong, current_sp.friendly_name),
+ ),
+ t(
+ 'help_text.requested_attributes.intro_html',
+ sp_html: content_tag(:strong, current_sp.friendly_name),
+ ),
+ ],
+ ' ',
+ ),
+ )
end
end
describe 'ial2' do
let(:ial2_requested) { true }
+
context 'consent has expired since the last sign in' do
let(:identities) do
[
@@ -196,11 +207,20 @@
end
let(:completion_context) { :consent_expired }
- it 'renders the expired IAL2 consent intro message' do
+ it 'renders the expired consent intro message' do
expect(presenter.intro).to eq(
- I18n.t(
- 'help_text.requested_attributes.ial2_consent_reminder_html',
- sp: current_sp.friendly_name,
+ safe_join(
+ [
+ t(
+ 'help_text.requested_attributes.consent_reminder_html',
+ sp_html: content_tag(:strong, current_sp.friendly_name),
+ ),
+ t(
+ 'help_text.requested_attributes.intro_html',
+ sp_html: content_tag(:strong, current_sp.friendly_name),
+ ),
+ ],
+ ' ',
),
)
end
@@ -217,22 +237,12 @@
]
end
let(:completion_context) { :reverified_after_consent }
- it 'renders the reverified IAL2 consent intro message' do
- expect(presenter.intro).to eq(
- I18n.t(
- 'help_text.requested_attributes.ial2_reverified_consent_info',
- sp: current_sp.friendly_name,
- ),
- )
- end
- end
- context 'when consent has not expired' do
- it 'renders the standard intro message' do
+ it 'renders the reverified IAL2 consent intro message' do
expect(presenter.intro).to eq(
- I18n.t(
- 'help_text.requested_attributes.ial2_intro_html',
- sp: current_sp.friendly_name,
+ t(
+ 'help_text.requested_attributes.ial2_reverified_consent_info_html',
+ sp_html: content_tag(:strong, current_sp.friendly_name),
),
)
end
diff --git a/spec/presenters/openid_connect_user_info_presenter_spec.rb b/spec/presenters/openid_connect_user_info_presenter_spec.rb
index e22176e0329..8aa2bfecce4 100644
--- a/spec/presenters/openid_connect_user_info_presenter_spec.rb
+++ b/spec/presenters/openid_connect_user_info_presenter_spec.rb
@@ -368,5 +368,47 @@
end
end
end
+
+ context 'with a deleted email' do
+ let(:identity) do
+ build(
+ :service_provider_identity,
+ rails_session_id: rails_session_id,
+ user: create(:user, :fully_registered, :with_multiple_emails),
+ scope: scope,
+ )
+ end
+
+ before do
+ identity.email_address_id = identity.user.email_addresses.first.id
+ identity.user.email_addresses.first.delete
+ end
+
+ it 'defers to user alternate email' do
+ expect(identity.user.reload.email_addresses.first.id).
+ to_not eq(identity.email_address_id)
+ expect(identity.user.reload.email_addresses.count).to be 1
+ expect(user_info[:email]).to eq(identity.user.email_addresses.last.email)
+ end
+ end
+
+ context 'with nil email id' do
+ let(:identity) do
+ build(
+ :service_provider_identity,
+ rails_session_id: rails_session_id,
+ user: create(:user, :fully_registered),
+ scope: scope,
+ )
+ end
+
+ before do
+ identity.email_address_id = nil
+ end
+
+ it 'adds the signed in email id to the identity' do
+ expect(user_info[:email]).to eq(identity.user.email_addresses.last.email)
+ end
+ end
end
end
diff --git a/spec/presenters/two_factor_authentication/set_up_piv_cac_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/set_up_piv_cac_selection_presenter_spec.rb
index 3a46e5e2010..8499e9c53fb 100644
--- a/spec/presenters/two_factor_authentication/set_up_piv_cac_selection_presenter_spec.rb
+++ b/spec/presenters/two_factor_authentication/set_up_piv_cac_selection_presenter_spec.rb
@@ -2,6 +2,7 @@
RSpec.describe TwoFactorAuthentication::SetUpPivCacSelectionPresenter do
let(:user) { create(:user) }
+ let!(:federal_domain) { create(:federal_email_domain, name: 'gsa.gov') }
subject(:presenter) { described_class.new(user:) }
describe '#type' do
@@ -42,7 +43,7 @@
end
context 'with a confirmed email address ending in .gov or .mil' do
- let(:user) { create(:user, email: 'example@example.gov') }
+ let(:user) { create(:user, email: 'example@gsa.gov') }
it { expect(recommended).to eq(true) }
end
diff --git a/spec/services/attribute_asserter_spec.rb b/spec/services/attribute_asserter_spec.rb
index 5ad52b0a9c0..f8e656f3e75 100644
--- a/spec/services/attribute_asserter_spec.rb
+++ b/spec/services/attribute_asserter_spec.rb
@@ -717,6 +717,136 @@
it_behaves_like 'unverified user'
end
+
+ context 'with a deleted email' do
+ let(:subject) do
+ described_class.new(
+ user: user,
+ name_id_format: name_id_format,
+ service_provider: service_provider,
+ authn_request: authn_request,
+ decrypted_pii: decrypted_pii,
+ user_session: user_session,
+ )
+ end
+ before do
+ user.identities << identity
+ allow(service_provider.metadata).to receive(:[]).with(:attribute_bundle).
+ and_return(%w[email phone first_name])
+ create(:email_address, user:, email: 'email@example.com')
+
+ ident = user.identities.last
+ ident.email_address_id = user.email_addresses.first.id
+ ident.save
+ subject.build
+
+ user.email_addresses.first.delete
+
+ subject.build
+ end
+
+ it 'defers to user alternate email' do
+ expect(get_asserted_attribute(user, :email)).
+ to eq 'email@example.com'
+ end
+ end
+
+ context 'with a nil email id' do
+ let(:subject) do
+ described_class.new(
+ user: user,
+ name_id_format: name_id_format,
+ service_provider: service_provider,
+ authn_request: authn_request,
+ decrypted_pii: decrypted_pii,
+ user_session: user_session,
+ )
+ end
+ before do
+ user.identities << identity
+ allow(service_provider.metadata).to receive(:[]).with(:attribute_bundle).
+ and_return(%w[email phone first_name])
+
+ ident = user.identities.last
+ ident.email_address_id = nil
+ ident.save
+ subject.build
+ end
+
+ it 'defers to user alternate email' do
+ expect(get_asserted_attribute(user, :email)).
+ to eq user.email_addresses.last.email
+ end
+ end
+
+ context 'select email to send to partner feature is disabled' do
+ before do
+ allow(IdentityConfig.store).to receive(
+ :feature_select_email_to_share_enabled,
+ ).and_return(false)
+ end
+
+ context 'with a deleted email' do
+ let(:subject) do
+ described_class.new(
+ user: user,
+ name_id_format: name_id_format,
+ service_provider: service_provider,
+ authn_request: authn_request,
+ decrypted_pii: decrypted_pii,
+ user_session: user_session,
+ )
+ end
+ before do
+ user.identities << identity
+ allow(service_provider.metadata).to receive(:[]).with(:attribute_bundle).
+ and_return(%w[email phone first_name])
+ create(:email_address, user:, email: 'email@example.com')
+
+ ident = user.identities.last
+ ident.email_address_id = user.email_addresses.first.id
+ ident.save
+ subject.build
+
+ user.email_addresses.first.delete
+
+ subject.build
+ end
+
+ it 'defers to user alternate email' do
+ expect(get_asserted_attribute(user, :email)).
+ to eq 'email@example.com'
+ end
+ end
+
+ context 'with a nil email id' do
+ let(:subject) do
+ described_class.new(
+ user: user,
+ name_id_format: name_id_format,
+ service_provider: service_provider,
+ authn_request: authn_request,
+ decrypted_pii: decrypted_pii,
+ user_session: user_session,
+ )
+ end
+ before do
+ user.identities << identity
+ allow(service_provider.metadata).to receive(:[]).with(:attribute_bundle).
+ and_return(%w[email phone first_name])
+
+ ident = user.identities.last
+ ident.email_address_id = nil
+ ident.save
+ subject.build
+ end
+
+ it 'defers to user alternate email' do
+ expect(get_asserted_attribute(user, :email)).
+ to eq user.email_addresses.last.email
+ end
+ end
+ end
end
describe 'aal attributes handling' do
diff --git a/spec/services/displayable_pii_formatter_spec.rb b/spec/services/displayable_pii_formatter_spec.rb
index b62885eaeb9..e2f6aa407bf 100644
--- a/spec/services/displayable_pii_formatter_spec.rb
+++ b/spec/services/displayable_pii_formatter_spec.rb
@@ -48,6 +48,8 @@
)
end
+ let(:selected_email_id) { current_user.email_addresses.first.id }
+
let(:pii) do
{
first_name: first_name,
@@ -63,7 +65,13 @@
}
end
- subject(:formatter) { described_class.new(current_user: current_user, pii: pii) }
+ subject(:formatter) do
+ described_class.new(
+ current_user:,
+ pii:,
+ selected_email_id:,
+ )
+ end
describe '#format' do
context 'ial1' do
diff --git a/spec/services/doc_auth_router_spec.rb b/spec/services/doc_auth_router_spec.rb
index 76ecdf41e40..6e4cc22a49f 100644
--- a/spec/services/doc_auth_router_spec.rb
+++ b/spec/services/doc_auth_router_spec.rb
@@ -19,44 +19,6 @@
end
end
- describe '.doc_auth_vendor' do
- def reload_ab_test_initializer!
- # undefine the AB tests instances so we can re-initialize them with different config values
- AbTests.constants.each do |const_name|
- AbTests.class_eval { remove_const(const_name) }
- end
- load Rails.root.join('config', 'initializers', 'ab_tests.rb').to_s
- end
-
- let(:doc_auth_vendor) { 'test1' }
- let(:doc_auth_vendor_randomize_alternate_vendor) { 'test2' }
- let(:analytics) { FakeAnalytics.new }
- let(:doc_auth_vendor_randomize_percent) { 57 }
- let(:doc_auth_vendor_randomize) { true }
-
- before do
- allow(IdentityConfig.store).to receive(:doc_auth_vendor).and_return(doc_auth_vendor)
- allow(IdentityConfig.store).to receive(:doc_auth_vendor_randomize_alternate_vendor).
- and_return(doc_auth_vendor_randomize_alternate_vendor)
-
- allow(IdentityConfig.store).to receive(:doc_auth_vendor_randomize_percent).
- and_return(doc_auth_vendor_randomize_percent)
- allow(IdentityConfig.store).to receive(:doc_auth_vendor_randomize).
- and_return(doc_auth_vendor_randomize)
-
- reload_ab_test_initializer!
- end
-
- after do
- allow(IdentityConfig.store).to receive(:doc_auth_vendor_randomize_percent).
- and_call_original
- allow(IdentityConfig.store).to receive(:doc_auth_vendor_randomize).
- and_call_original
-
- reload_ab_test_initializer!
- end
- end
-
describe DocAuthRouter::DocAuthErrorTranslatorProxy do
subject(:proxy) do
DocAuthRouter::DocAuthErrorTranslatorProxy.new(DocAuth::Mock::DocAuthMockClient.new)
diff --git a/spec/services/idv/aamva_state_maintenance_window_spec.rb b/spec/services/idv/aamva_state_maintenance_window_spec.rb
new file mode 100644
index 00000000000..19560ba16d5
--- /dev/null
+++ b/spec/services/idv/aamva_state_maintenance_window_spec.rb
@@ -0,0 +1,68 @@
+require 'rails_helper'
+
+RSpec.describe Idv::AamvaStateMaintenanceWindow do
+ let(:tz) { 'America/New_York' }
+ let(:eastern_time) { ActiveSupport::TimeZone[tz] }
+
+ before do
+ travel_to eastern_time.parse('2024-06-02T00:00:00')
+ end
+
+ describe '#in_maintenance_window?' do
+ let(:state) { 'DC' }
+
+ subject { described_class.in_maintenance_window?(state) }
+
+ context 'for a state with a defined outage window' do
+ it 'is true during the maintenance window' do
+ travel_to(eastern_time.parse('June 2, 2024 at 1am')) do
+ expect(subject).to eq(true)
+ end
+ end
+
+ it 'is false outside of the maintenance window' do
+ travel_to(eastern_time.parse('June 2, 2024 at 8am')) do
+ expect(subject).to eq(false)
+ end
+ end
+ end
+
+ context 'for a state without a defined outage window' do
+ let(:state) { 'LG' }
+
+ it 'returns false without an exception' do
+ expect(subject).to eq(false)
+ end
+ end
+ end
+
+ describe '.windows_for_state' do
+ subject { described_class.windows_for_state(state) }
+
+ context 'for a state with no entries' do
+ let(:state) { 'LG' }
+
+ it 'returns an empty array for a state with no entries' do
+ expect(subject).to eq([])
+ end
+ end
+
+ context 'for a state with multiple overlapping windows' do
+ let(:state) { 'CA' }
+ let(:expected_windows) do
+ [
+ eastern_time.parse('2024-06-01 04:00:00')..eastern_time.parse('2024-06-01 05:30:00'),
+ eastern_time.parse('2024-05-27 01:00:00')..eastern_time.parse('2024-05-27 01:45:00'),
+ eastern_time.parse('2024-05-06 01:00:00')..eastern_time.parse('2024-05-06 04:30:00'),
+ eastern_time.parse('2024-05-20 01:00:00')..eastern_time.parse('2024-05-20 04:30:00'),
+ ]
+ end
+
+ it 'returns all of them as ranges' do
+ Time.use_zone(tz) do
+ expect(subject).to eq(expected_windows)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/idv/proofing_components_spec.rb b/spec/services/idv/proofing_components_spec.rb
index d78fb48b1fc..97e78cc5ab6 100644
--- a/spec/services/idv/proofing_components_spec.rb
+++ b/spec/services/idv/proofing_components_spec.rb
@@ -32,7 +32,7 @@
let(:pii_from_doc) { Idp::Constants::MOCK_IDV_APPLICANT }
before do
- allow(IdentityConfig.store).to receive(:doc_auth_vendor).and_return('test_vendor')
+ allow(IdentityConfig.store).to receive(:doc_auth_vendor_default).and_return('test_vendor')
idv_session.mark_verify_info_step_complete!
idv_session.address_verification_mechanism = 'gpo'
allow(FeatureManagement).to receive(:proofing_device_profiling_collecting_enabled?).
@@ -78,7 +78,7 @@
context 'doc auth' do
before do
- allow(IdentityConfig.store).to receive(:doc_auth_vendor).and_return('test_vendor')
+ allow(IdentityConfig.store).to receive(:doc_auth_vendor_default).and_return('test_vendor')
end
context 'before doc auth complete' do
it 'returns nil' do
diff --git a/spec/services/proofing/aamva/proofer_spec.rb b/spec/services/proofing/aamva/proofer_spec.rb
index 6afb19420a8..1de70664a09 100644
--- a/spec/services/proofing/aamva/proofer_spec.rb
+++ b/spec/services/proofing/aamva/proofer_spec.rb
@@ -293,5 +293,29 @@
end
end
end
+
+ context 'when the DMV is in a defined maintenance window' do
+ before do
+ expect(Idv::AamvaStateMaintenanceWindow).to receive(:in_maintenance_window?).
+ and_return(true)
+ end
+
+ it 'sets jurisdiction_in_maintenance_window to true' do
+ result = subject.proof(state_id_data)
+ expect(result.jurisdiction_in_maintenance_window?).to eq(true)
+ end
+ end
+
+ context 'when the DMV is not in a defined maintenance window' do
+ before do
+ expect(Idv::AamvaStateMaintenanceWindow).to receive(:in_maintenance_window?).
+ and_return(false)
+ end
+
+ it 'sets jurisdiction_in_maintenance_window to false' do
+ result = subject.proof(state_id_data)
+ expect(result.jurisdiction_in_maintenance_window?).to eq(false)
+ end
+ end
end
end
diff --git a/spec/services/proofing/mock/state_id_mock_client_spec.rb b/spec/services/proofing/mock/state_id_mock_client_spec.rb
index a342cff43c9..6615ac2ec5b 100644
--- a/spec/services/proofing/mock/state_id_mock_client_spec.rb
+++ b/spec/services/proofing/mock/state_id_mock_client_spec.rb
@@ -24,6 +24,7 @@
transaction_id: transaction_id,
vendor_name: 'StateIdMock',
verified_attributes: [],
+ jurisdiction_in_maintenance_window: false,
)
end
end
@@ -49,6 +50,7 @@
transaction_id: transaction_id,
vendor_name: 'StateIdMock',
verified_attributes: [],
+ jurisdiction_in_maintenance_window: false,
)
end
end
@@ -72,6 +74,7 @@
transaction_id: transaction_id,
vendor_name: 'StateIdMock',
verified_attributes: [],
+ jurisdiction_in_maintenance_window: false,
)
end
end
diff --git a/spec/services/proofing/state_id_result_spec.rb b/spec/services/proofing/state_id_result_spec.rb
index eb40e013c3f..d40e44d6568 100644
--- a/spec/services/proofing/state_id_result_spec.rb
+++ b/spec/services/proofing/state_id_result_spec.rb
@@ -8,6 +8,7 @@
let(:transaction_id) { 'ABCD1234' }
let(:requested_attributes) { { dob: 1, first_name: 1 } }
let(:verified_attributes) { [:dob, :first_name] }
+ let(:jurisdiction_in_maintenance_window) { false }
subject do
described_class.new(
@@ -18,6 +19,7 @@
transaction_id:,
requested_attributes:,
verified_attributes:,
+ jurisdiction_in_maintenance_window:,
)
end
@@ -34,6 +36,7 @@
transaction_id: 'ABCD1234',
requested_attributes: { dob: 1, first_name: 1 },
verified_attributes: [:dob, :first_name],
+ jurisdiction_in_maintenance_window: false,
},
)
end
diff --git a/spec/views/accounts/show.html.erb_spec.rb b/spec/views/accounts/show.html.erb_spec.rb
index d24efb61757..178e15b4240 100644
--- a/spec/views/accounts/show.html.erb_spec.rb
+++ b/spec/views/accounts/show.html.erb_spec.rb
@@ -121,7 +121,7 @@
in_person_enrollment = user.in_person_enrollments.first
in_person_enrollment.update!(status: :expired, status_check_completed_at: Time.zone.now)
profile = user.profiles.first
- profile.deactivate_due_to_ipp_expiration
+ profile.deactivate_due_to_in_person_verification_cancelled
end
it 'renders the idv partial' do
diff --git a/spec/views/sign_up/completions/show.html.erb_spec.rb b/spec/views/sign_up/completions/show.html.erb_spec.rb
index 41938c7ff20..352e3e293e1 100644
--- a/spec/views/sign_up/completions/show.html.erb_spec.rb
+++ b/spec/views/sign_up/completions/show.html.erb_spec.rb
@@ -3,6 +3,7 @@
RSpec.describe 'sign_up/completions/show.html.erb' do
let(:user) { create(:user, :fully_registered) }
let(:service_provider) { create(:service_provider) }
+ let(:selected_email_id) { user.email_addresses.first.id }
let(:decrypted_pii) { {} }
let(:requested_attributes) { [:email] }
let(:ial2_requested) { false }
@@ -22,10 +23,11 @@
CompletionsPresenter.new(
current_user: user,
current_sp: service_provider,
- decrypted_pii: decrypted_pii,
- requested_attributes: requested_attributes,
- ial2_requested: ial2_requested,
- completion_context: completion_context,
+ decrypted_pii:,
+ requested_attributes:,
+ ial2_requested:,
+ completion_context:,
+ selected_email_id:,
)
end
@@ -43,9 +45,9 @@
expect(text).to_not include(service_provider.agency.name)
expect(text).to include(
view_context.strip_tags(
- I18n.t(
- 'help_text.requested_attributes.ial1_intro_html',
- sp: service_provider.friendly_name,
+ t(
+ 'help_text.requested_attributes.intro_html',
+ sp_html: content_tag(:strong, service_provider.friendly_name),
),
),
)
@@ -59,6 +61,48 @@
)
end
+ context 'select email to send to partner and select email feature is disabled' do
+ before do
+ allow(IdentityConfig.store).to receive(
+ :feature_select_email_to_share_enabled,
+ ).and_return(false)
+ end
+
+ it 'does not show a link to select different email' do
+ create(:email_address, user: user)
+ user.reload
+ render
+
+ expect(rendered).to_not include(t('help_text.requested_attributes.change_email_link'))
+ expect(rendered).to_not include(t('account.index.email_add'))
+ end
+
+ it 'does not show a link to add another email' do
+ render
+
+ expect(rendered).to_not include(t('help_text.requested_attributes.change_email_link'))
+ expect(rendered).to_not include(t('account.index.email_add'))
+ end
+ end
+
+ context 'select email to send to partner' do
+ it 'does not show a link to select different email' do
+ create(:email_address, user: user)
+ user.reload
+ render
+
+ expect(rendered).to include(t('help_text.requested_attributes.change_email_link'))
+ expect(rendered).to_not include(t('account.index.email_add'))
+ end
+
+ it 'does not show a link to add another email' do
+ render
+
+ expect(rendered).to_not include(t('help_text.requested_attributes.change_email_link'))
+ expect(rendered).to include(t('account.index.email_add'))
+ end
+ end
+
context 'the all_emails scope is requested' do
let(:requested_attributes) { [:email, :all_emails] }
diff --git a/spec/views/sign_up/select_email/show.html.erb_spec.rb b/spec/views/sign_up/select_email/show.html.erb_spec.rb
new file mode 100644
index 00000000000..a699da041ae
--- /dev/null
+++ b/spec/views/sign_up/select_email/show.html.erb_spec.rb
@@ -0,0 +1,22 @@
+require 'rails_helper'
+
+RSpec.describe 'sign_up/select_email/show.html.erb' do
+ let(:email) { 'michael.motorist@email.com' }
+ let(:email2) { 'michael.motorist2@email.com' }
+ let(:user) { create(:user) }
+
+ before do
+ user.email_addresses.create(email: email, confirmed_at: Time.zone.now)
+ user.email_addresses.create(email: email2, confirmed_at: Time.zone.now)
+ user.reload
+ @user_emails = user.email_addresses
+ @select_email_form = SelectEmailForm.new(user)
+ end
+
+ it 'shows all of the user\'s emails' do
+ render
+
+ expect(rendered).to include('michael.motorist@email.com')
+ expect(rendered).to include('michael.motorist2@email.com')
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index 1bacc1cdecc..1a803270cc0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1475,23 +1475,7 @@
"@types/chai" "*"
"@types/chai-as-promised" "*"
-"@types/eslint-scope@^3.7.3":
- version "3.7.4"
- resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
- integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==
- dependencies:
- "@types/eslint" "*"
- "@types/estree" "*"
-
-"@types/eslint@*":
- version "8.4.5"
- resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.5.tgz#acdfb7dd36b91cc5d812d7c093811a8f3d9b31e4"
- integrity sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==
- dependencies:
- "@types/estree" "*"
- "@types/json-schema" "*"
-
-"@types/estree@*", "@types/estree@^1.0.5":
+"@types/estree@^1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
@@ -1566,7 +1550,7 @@
dependencies:
"@types/sizzle" "*"
-"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
+"@types/json-schema@^7.0.12", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
@@ -2069,10 +2053,10 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8:
mime-types "~2.1.34"
negotiator "0.6.3"
-acorn-import-assertions@^1.9.0:
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac"
- integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==
+acorn-import-attributes@^1.9.5:
+ version "1.9.5"
+ resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef"
+ integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==
acorn-jsx@^5.3.2:
version "5.3.2"
@@ -3165,10 +3149,10 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
-enhanced-resolve@^5.16.0:
- version "5.16.0"
- resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz#65ec88778083056cb32487faa9aef82ed0864787"
- integrity sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==
+enhanced-resolve@^5.17.1:
+ version "5.17.1"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15"
+ integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==
dependencies:
graceful-fs "^4.2.4"
tapable "^2.2.0"
@@ -7100,21 +7084,20 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
-webpack@^5.91.0:
- version "5.91.0"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.91.0.tgz#ffa92c1c618d18c878f06892bbdc3373c71a01d9"
- integrity sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==
+webpack@^5.94.0:
+ version "5.94.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f"
+ integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==
dependencies:
- "@types/eslint-scope" "^3.7.3"
"@types/estree" "^1.0.5"
"@webassemblyjs/ast" "^1.12.1"
"@webassemblyjs/wasm-edit" "^1.12.1"
"@webassemblyjs/wasm-parser" "^1.12.1"
acorn "^8.7.1"
- acorn-import-assertions "^1.9.0"
+ acorn-import-attributes "^1.9.5"
browserslist "^4.21.10"
chrome-trace-event "^1.0.2"
- enhanced-resolve "^5.16.0"
+ enhanced-resolve "^5.17.1"
es-module-lexer "^1.2.1"
eslint-scope "5.1.1"
events "^3.2.0"