diff --git a/app/controllers/accounts/connected_accounts/selected_email_controller.rb b/app/controllers/accounts/connected_accounts/selected_email_controller.rb index 1ae8b77a6fe..2d4b38bfc4f 100644 --- a/app/controllers/accounts/connected_accounts/selected_email_controller.rb +++ b/app/controllers/accounts/connected_accounts/selected_email_controller.rb @@ -14,7 +14,7 @@ def edit @select_email_form = build_select_email_form @can_add_email = EmailPolicy.new(current_user).can_add_email? analytics.sp_select_email_visited - @email_id = @identity.email_address_id || last_email + @email_id = @identity.email_address_id || last_email_id end def update @@ -52,7 +52,7 @@ def identity @identity = current_user.identities.find_by(id: params[:identity_id]) end - def last_email + def last_email_id current_user.last_sign_in_email_address.id end end diff --git a/app/controllers/concerns/saml_idp_auth_concern.rb b/app/controllers/concerns/saml_idp_auth_concern.rb index 80fd3eb54ee..d98ee3e8f1c 100644 --- a/app/controllers/concerns/saml_idp_auth_concern.rb +++ b/app/controllers/concerns/saml_idp_auth_concern.rb @@ -154,10 +154,11 @@ def link_identity_from_session_data def email_address_id return nil unless IdentityConfig.store.feature_select_email_to_share_enabled + identity = current_user.identities.find_by(service_provider: sp_session[:issuer]) + return nil if !identity&.verified_single_email_attribute? if user_session[:selected_email_id_for_linked_identity].present? return user_session[:selected_email_id_for_linked_identity] end - identity = current_user.identities.find_by(service_provider: sp_session['issuer']) email_id = identity&.email_address_id return email_id if email_id.is_a? Integer end diff --git a/app/controllers/idv/welcome_controller.rb b/app/controllers/idv/welcome_controller.rb index 47bc048a266..8284e069c2a 100644 --- a/app/controllers/idv/welcome_controller.rb +++ b/app/controllers/idv/welcome_controller.rb @@ -63,7 +63,7 @@ def create_document_capture_session def cancel_previous_in_person_enrollments return unless IdentityConfig.store.in_person_proofing_enabled UspsInPersonProofing::EnrollmentHelper - .cancel_stale_establishing_enrollments_for_user(current_user) + .cancel_establishing_and_pending_enrollments(current_user) end end end diff --git a/app/controllers/openid_connect/authorization_controller.rb b/app/controllers/openid_connect/authorization_controller.rb index 1480697d6b6..e92f4824c70 100644 --- a/app/controllers/openid_connect/authorization_controller.rb +++ b/app/controllers/openid_connect/authorization_controller.rb @@ -90,10 +90,12 @@ def link_identity_to_service_provider def email_address_id return nil unless IdentityConfig.store.feature_select_email_to_share_enabled + identity = current_user.identities.find_by(service_provider: sp_session[:issuer]) + return nil if !identity&.verified_single_email_attribute? if user_session[:selected_email_id_for_linked_identity].present? return user_session[:selected_email_id_for_linked_identity] end - identity = current_user.identities.find_by(service_provider: sp_session[:issuer]) + identity&.email_address_id end diff --git a/app/forms/reset_password_form.rb b/app/forms/reset_password_form.rb index d07ffa8ebdd..3a0d2b4cc03 100644 --- a/app/forms/reset_password_form.rb +++ b/app/forms/reset_password_form.rb @@ -43,7 +43,7 @@ def valid_token def handle_valid_password update_user - mark_profile_inactive + mark_profile_as_password_reset end def update_user @@ -60,13 +60,24 @@ def update_user user.update!(attributes) end - def mark_profile_inactive - return if active_profile.blank? + def mark_profile_as_password_reset + profile = password_reset_profile + return if profile.blank? - active_profile.deactivate(:password_reset) + profile.deactivate(:password_reset) Funnel::DocAuth::ResetSteps.call(user.id) end + def password_reset_profile + FeatureManagement.pending_in_person_password_reset_enabled? ? + find_pending_in_person_or_active_profile : + active_profile + end + + def find_pending_in_person_or_active_profile + user.pending_in_person_enrollment&.profile || active_profile + end + # It is possible for an account that is resetting their password to be "invalid". # If an unconfirmed account (which must have one unconfirmed email address) resets their # password and a different account then adds and confirms that same email address, @@ -86,8 +97,16 @@ def extra_analytics_attributes { user_id: user.uuid, profile_deactivated: active_profile.present?, - pending_profile_invalidated: pending_profile.present?, + pending_profile_invalidated: pending_profile_invalidated?, pending_profile_pending_reasons: (pending_profile&.pending_reasons || [])&.join(','), } end + + def pending_profile_invalidated? + if FeatureManagement.pending_in_person_password_reset_enabled? + pending_profile.present? && !pending_profile.in_person_verification_pending? + else + pending_profile.present? + end + end end diff --git a/app/forms/verify_password_form.rb b/app/forms/verify_password_form.rb index 02aea64add5..1f9bc897523 100644 --- a/app/forms/verify_password_form.rb +++ b/app/forms/verify_password_form.rb @@ -36,7 +36,7 @@ def valid_password? def reencrypt_pii personal_key = profile.encrypt_pii(decrypted_pii, password) - profile.activate_after_password_reset + profile.clear_password_reset_deactivation_reason personal_key end diff --git a/app/forms/webauthn_visit_form.rb b/app/forms/webauthn_visit_form.rb index 96f4f9e2bd7..a263dd56322 100644 --- a/app/forms/webauthn_visit_form.rb +++ b/app/forms/webauthn_visit_form.rb @@ -41,10 +41,9 @@ def check_params(params) return unless error if @platform_authenticator - errors.add error, translate_platform_authenticator_error(error), - type: :"#{translate_platform_authenticator_error(error).split('.').last}" + errors.add error, translate_platform_authenticator_error(error), type: :invalid else - errors.add error, translate_error(error), type: :"#{translate_error(error).split('.').last}" + errors.add error, translate_error(error), type: :invalid end end diff --git a/app/javascript/packages/phone-input/package.json b/app/javascript/packages/phone-input/package.json index b2d439a5b1a..0d1cb043cfe 100644 --- a/app/javascript/packages/phone-input/package.json +++ b/app/javascript/packages/phone-input/package.json @@ -4,7 +4,7 @@ "version": "1.0.0", "dependencies": { "intl-tel-input": "^24.5.0", - "libphonenumber-js": "^1.11.18" + "libphonenumber-js": "^1.11.19" }, "sideEffects": [ "./index.ts" diff --git a/app/services/create_new_device_alert.rb b/app/jobs/create_new_device_alert_job.rb similarity index 95% rename from app/services/create_new_device_alert.rb rename to app/jobs/create_new_device_alert_job.rb index 3881f8a7f96..6091269347d 100644 --- a/app/services/create_new_device_alert.rb +++ b/app/jobs/create_new_device_alert_job.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class CreateNewDeviceAlert < ApplicationJob +class CreateNewDeviceAlertJob < ApplicationJob queue_as :long_running def perform(now) diff --git a/app/jobs/get_usps_proofing_results_job.rb b/app/jobs/get_usps_proofing_results_job.rb index ebb16537316..4eb13e9f343 100644 --- a/app/jobs/get_usps_proofing_results_job.rb +++ b/app/jobs/get_usps_proofing_results_job.rb @@ -34,6 +34,7 @@ def perform(_now) enrollments_cancelled: 0, enrollments_in_progress: 0, enrollments_passed: 0, + enrollments_skipped: 0, } started_at = Time.zone.now @@ -106,7 +107,7 @@ def check_enrollment(enrollment) profile_deactivation_reason = enrollment.profile_deactivation_reason - if profile_deactivation_reason.present? + if profile_deactivation_reason.present? && profile_deactivation_reason != 'password_reset' log_enrollment_updated_analytics( enrollment: enrollment, enrollment_passed: false, @@ -118,9 +119,7 @@ def check_enrollment(enrollment) return end - response = proofer.request_proofing_results( - enrollment, - ) + response = proofer.request_proofing_results(enrollment) rescue Faraday::BadRequestError => err # 400 status code. This is used for some status updates and some common client errors handle_bad_request_error(err, enrollment) @@ -131,7 +130,11 @@ def check_enrollment(enrollment) rescue StandardError => err handle_standard_error(err, enrollment) else - process_enrollment_response(enrollment, response) + if profile_deactivation_reason == 'password_reset' + skip_enrollment(enrollment, profile_deactivation_reason) + else + process_enrollment_response(enrollment, response) + end ensure # Record the attempt to update the enrollment enrollment.update(status_check_attempted_at: status_check_attempted_at) @@ -143,6 +146,15 @@ def cancel_enrollment(enrollment) enrollment.profile.deactivate_due_to_in_person_verification_cancelled end + def skip_enrollment(enrollment, profile_deactivation_reason) + analytics.idv_in_person_usps_proofing_results_job_enrollment_skipped( + **enrollment_analytics_attributes(enrollment, complete: false), + reason: "Profile has a deactivation reason of #{profile_deactivation_reason}", + job_name: self.class.name, + ) + enrollment_outcomes[:enrollments_skipped] += 1 + end + def passed_with_unsupported_secondary_id_type?(enrollment, response) return false if enrollment.enhanced_ipp? diff --git a/app/jobs/grant_account_reset_requests_and_send_emails_job.rb b/app/jobs/grant_account_reset_requests_and_send_emails_job.rb new file mode 100644 index 00000000000..3bde907bb64 --- /dev/null +++ b/app/jobs/grant_account_reset_requests_and_send_emails_job.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +class GrantAccountResetRequestsAndSendEmailsJob < ApplicationJob + queue_as :long_running + + def perform(now) + notifications_sent = 0 + AccountResetRequest.where( + sql_query_for_users_eligible_to_delete_their_accounts, + tvalue: now - IdentityConfig.store.account_reset_wait_period_days.days, + ).order('requested_at ASC').each do |arr| + notifications_sent += 1 if grant_request_and_send_email(arr) + end + + analytics.account_reset_notifications(count: notifications_sent) + + notifications_sent + end + + private + + def analytics + @analytics ||= Analytics.new( + user: AnonymousUser.new, + request: nil, + sp: nil, + session: {}, + ) + end + + def sql_query_for_users_eligible_to_delete_their_accounts + <<~SQL + cancelled_at IS NULL AND + granted_at IS NULL AND + requested_at < :tvalue AND + request_token IS NOT NULL AND + granted_token IS NULL + SQL + end + + def grant_request_and_send_email(arr) + user = arr.user + return false unless AccountReset::GrantRequest.new(user).call + + arr = arr.reload + user.confirmed_email_addresses.each do |email_address| + UserMailer.with(user: user, email_address: email_address) + .account_reset_granted(arr).deliver_now_or_later + end + true + end +end diff --git a/app/models/event.rb b/app/models/event.rb index 75e33e20959..ed1ea10ce41 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -29,6 +29,8 @@ class Event < ApplicationRecord password_invalidated: 22, sign_in_unsuccessful_2fa: 23, sign_in_notification_timeframe_expired: 24, + webauthn_platform_added: 25, + webauthn_platform_removed: 26, } validates :event_type, presence: true diff --git a/app/models/in_person_enrollment.rb b/app/models/in_person_enrollment.rb index 395a7598f92..a89f5fa2d98 100644 --- a/app/models/in_person_enrollment.rb +++ b/app/models/in_person_enrollment.rb @@ -155,6 +155,13 @@ def profile_deactivation_reason profile&.deactivation_reason end + # Updates the in-person enrollment to status cancelled and deactivates the + # associated profile with reason "in_person_verification_cancelled". + def cancel + cancelled! + profile&.deactivate_due_to_in_person_verification_cancelled + end + private def days_to_expire diff --git a/app/models/profile.rb b/app/models/profile.rb index e05d3c0a33f..43219c8b851 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -176,13 +176,14 @@ def activate_after_passing_in_person end end - def activate_after_password_reset + # Removes the deactivation reason from the profile if it had a password_reset + # deactivation reason. If the profile was activated previously it will be + # reactivated. + def clear_password_reset_deactivation_reason if password_reset? transaction do - update!( - deactivation_reason: nil, - ) - activate(reason_deactivated: :password_reset) + update!(deactivation_reason: nil) + activate(reason_deactivated: :password_reset) if activated_at.present? end end end diff --git a/app/models/service_provider_identity.rb b/app/models/service_provider_identity.rb index 3da98d4eb9f..b99a9a00b20 100644 --- a/app/models/service_provider_identity.rb +++ b/app/models/service_provider_identity.rb @@ -8,6 +8,7 @@ class ServiceProviderIdentity < ApplicationRecord belongs_to :user validates :service_provider, presence: true + before_save :clear_email_address_id_if_not_supported # rubocop:disable Rails/InverseOf belongs_to :deleted_user, foreign_key: 'user_id', primary_key: 'user_id' @@ -71,6 +72,13 @@ def verified_single_email_attribute? !verified_attributes.include?('all_emails') end + def clear_email_address_id_if_not_supported + if !verified_single_email_attribute? && + IdentityConfig.store.feature_select_email_to_share_enabled + self.email_address_id = nil + end + end + def email_address_for_sharing if IdentityConfig.store.feature_select_email_to_share_enabled && email_address return email_address diff --git a/app/models/user.rb b/app/models/user.rb index c0528992740..242168e2666 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -377,10 +377,11 @@ def identity_verified_with_facial_match? active_profile.present? && active_profile.facial_match? end - # This user's most recently activated profile that has also been deactivated - # due to a password reset, or nil if there is no such profile + # The users most recently activated or pending in person enrollment profile + # that has also been deactivated due to a password reset, or nil if there is + # no such profile def password_reset_profile - profile = profiles.where.not(activated_at: nil).order(activated_at: :desc).first + profile = find_password_reset_profile profile if profile&.password_reset? end @@ -534,6 +535,21 @@ def last_sign_in_email_address private + def find_password_reset_profile + FeatureManagement.pending_in_person_password_reset_enabled? ? + find_pending_in_person_or_active_profile : + find_active_profile + end + + def find_active_profile + profiles.where.not(activated_at: nil).order(activated_at: :desc).first + end + + def find_pending_in_person_or_active_profile + pending_in_person_enrollment&.profile || + profiles.where.not(activated_at: nil).order(activated_at: :desc).first + end + def lockout_period IdentityConfig.store.lockout_period_in_minutes.minutes end diff --git a/app/policies/pending_profile_policy.rb b/app/policies/pending_profile_policy.rb index 8eb1c9a58fd..d68f586f888 100644 --- a/app/policies/pending_profile_policy.rb +++ b/app/policies/pending_profile_policy.rb @@ -21,7 +21,7 @@ def user_has_pending_profile? attr_reader :user, :resolved_authn_context_result def pending_facial_match_profile? - user.pending_profile&.idv_level == 'unsupervised_with_selfie' + Profile::FACIAL_MATCH_IDV_LEVELS.include?(user.pending_profile&.idv_level) end def facial_match_requested? @@ -29,7 +29,8 @@ def facial_match_requested? end def pending_legacy_profile? - user.pending_profile.present? && user.pending_profile&.idv_level != 'unsupervised_with_selfie' + user.pending_profile&.present? && + user.pending_profile.idv_level != 'unsupervised_with_selfie' end def fraud_review_pending? diff --git a/app/services/account_reset/grant_requests_and_send_emails.rb b/app/services/account_reset/grant_requests_and_send_emails.rb deleted file mode 100644 index ad148f93276..00000000000 --- a/app/services/account_reset/grant_requests_and_send_emails.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -module AccountReset - class GrantRequestsAndSendEmails < ApplicationJob - queue_as :long_running - - def perform(now) - notifications_sent = 0 - AccountResetRequest.where( - sql_query_for_users_eligible_to_delete_their_accounts, - tvalue: now - IdentityConfig.store.account_reset_wait_period_days.days, - ).order('requested_at ASC').each do |arr| - notifications_sent += 1 if grant_request_and_send_email(arr) - end - - analytics.account_reset_notifications(count: notifications_sent) - - notifications_sent - end - - private - - def analytics - @analytics ||= Analytics.new( - user: AnonymousUser.new, - request: nil, - sp: nil, - session: {}, - ) - end - - def sql_query_for_users_eligible_to_delete_their_accounts - <<~SQL - cancelled_at IS NULL AND - granted_at IS NULL AND - requested_at < :tvalue AND - request_token IS NOT NULL AND - granted_token IS NULL - SQL - end - - def grant_request_and_send_email(arr) - user = arr.user - return false unless AccountReset::GrantRequest.new(user).call - - arr = arr.reload - user.confirmed_email_addresses.each do |email_address| - UserMailer.with(user: user, email_address: email_address) - .account_reset_granted(arr).deliver_now_or_later - end - true - end - end -end diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index b52f97caf0c..af455b15ea3 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -3370,6 +3370,7 @@ def idv_in_person_usps_proofing_enrollment_code_email_received( # @param [Integer] enrollments_failed number of enrollments which failed identity proofing # @param [Integer] enrollments_in_progress number of enrollments which did not have any change # @param [Integer] enrollments_passed number of enrollments which passed identity proofing + # @param [Integer] enrollments_skipped number of enrollments skipped # @param [Integer] enrollments_network_error # @param [Integer] enrollments_cancelled # @param [Float] percent_enrollments_errored @@ -3383,6 +3384,7 @@ def idv_in_person_usps_proofing_results_job_completed( enrollments_failed:, enrollments_in_progress:, enrollments_passed:, + enrollments_skipped:, enrollments_network_error:, enrollments_cancelled:, percent_enrollments_errored:, @@ -3399,6 +3401,7 @@ def idv_in_person_usps_proofing_results_job_completed( enrollments_failed:, enrollments_in_progress:, enrollments_passed:, + enrollments_skipped:, enrollments_network_error:, enrollments_cancelled:, percent_enrollments_errored:, @@ -3505,6 +3508,47 @@ def idv_in_person_usps_proofing_results_job_enrollment_incomplete( ) end + # Tracks skipped enrollments during the execution of the GetUspsProofingResultsJob + # + # @param [String] enrollment_code The in-person enrollment code. + # @param [String] enrollment_id The in-person enrollment ID. + # @param [String] reason The reason for skipping the enrollment. + # @param [String] job_name The class name of the job. + # @param [Float] minutes_since_established + # @param [Float] minutes_since_last_status_check + # @param [Float] minutes_since_last_status_check_completed + # @param [Float] minutes_since_last_status_update + # @param [Float] minutes_to_completion + # @param [String] issuer + def idv_in_person_usps_proofing_results_job_enrollment_skipped( + enrollment_code:, + enrollment_id:, + reason:, + job_name:, + minutes_since_established:, + minutes_since_last_status_check:, + minutes_since_last_status_check_completed:, + minutes_since_last_status_update:, + minutes_to_completion:, + issuer:, + **extra + ) + track_event( + :idv_in_person_usps_proofing_results_job_enrollment_skipped, + enrollment_code:, + enrollment_id:, + reason:, + job_name:, + minutes_since_established:, + minutes_since_last_status_check:, + minutes_since_last_status_check_completed:, + minutes_since_last_status_update:, + minutes_to_completion:, + issuer:, + **extra, + ) + end + # Tracks individual enrollments that are updated during GetUspsProofingResultsJob # @param [String] enrollment_code # @param [String] enrollment_id diff --git a/app/services/form_response.rb b/app/services/form_response.rb index a2b86a77d09..c7c2f5dc98c 100644 --- a/app/services/form_response.rb +++ b/app/services/form_response.rb @@ -19,7 +19,7 @@ def success? def to_h hash = { success: success } - hash[:errors] = errors.presence if !serialize_error_details_only? + hash[:errors] = (!defined?(@error_details) && errors).presence if !serialize_error_details_only? hash[:error_details] = flatten_details(error_details) if error_details.present? hash.merge!(extra) hash diff --git a/app/services/usps_in_person_proofing/enrollment_helper.rb b/app/services/usps_in_person_proofing/enrollment_helper.rb index 374838c8965..d4ec71cba0d 100644 --- a/app/services/usps_in_person_proofing/enrollment_helper.rb +++ b/app/services/usps_in_person_proofing/enrollment_helper.rb @@ -91,6 +91,16 @@ def cancel_stale_establishing_enrollments_for_user(user) .find_each(&:cancelled!) end + # Cancel a user's associated establishing and pending in-person enrollments. + # + # @param user [User] The user model + def cancel_establishing_and_pending_enrollments(user) + user + .in_person_enrollments + .where(status: [:establishing, :pending]) + .find_each(&:cancel) + end + def usps_proofer if IdentityConfig.store.usps_mock_fallback UspsInPersonProofing::Mock::Proofer.new diff --git a/config/application.yml.default b/config/application.yml.default index 23a5a22d3bc..b07e655782a 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -135,6 +135,7 @@ event_disavowal_expiration_hours: 240 facial_match_general_availability_enabled: true feature_idv_force_gpo_verification_enabled: false feature_idv_hybrid_flow_enabled: true +feature_pending_in_person_password_reset_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 * * * *' @@ -524,6 +525,7 @@ production: enable_usps_verification: false encrypted_document_storage_s3_bucket: '' facial_match_general_availability_enabled: false + feature_pending_in_person_password_reset_enabled: false feature_select_email_to_share_enabled: false idv_sp_required: true invalid_gpo_confirmation_zipcode: '' diff --git a/config/initializers/job_configurations.rb b/config/initializers/job_configurations.rb index 0f3bb7adf49..c684240419b 100644 --- a/config/initializers/job_configurations.rb +++ b/config/initializers/job_configurations.rb @@ -26,13 +26,13 @@ }, # Send account deletion confirmation notifications account_reset_grant_requests_send_emails: { - class: 'AccountReset::GrantRequestsAndSendEmails', + class: 'GrantAccountResetRequestsAndSendEmailsJob', cron: cron_5m, args: -> { [Time.zone.now] }, }, # Send new device alert notifications create_new_device_alert_send_emails: { - class: 'CreateNewDeviceAlert', + class: 'CreateNewDeviceAlertJob', cron: cron_5m, args: -> { [Time.zone.now] }, }, diff --git a/config/locales/en.yml b/config/locales/en.yml index 85a5c2fdbd2..9ffcf21e00e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -845,6 +845,8 @@ event_types.sign_in_notification_timeframe_expired: Expired notification timefra event_types.sign_in_unsuccessful_2fa: Failed to authenticate event_types.webauthn_key_added: Hardware security key added event_types.webauthn_key_removed: Hardware security key removed +event_types.webauthn_platform_added: Face or touch unlock added +event_types.webauthn_platform_removed: Face or touch unlock removed forms.backup_code_regenerate.caution: If you regenerate your backup codes you will receive a new set of backup codes. Your original backup codes will no longer be valid. forms.backup_code_regenerate.confirm: Are you sure you want to regenerate your backup codes? forms.backup_code_reminder.body_info: If you ever lose access to your primary authentication method, you can use backup codes to regain access to your account. diff --git a/config/locales/es.yml b/config/locales/es.yml index fd63d3dc7c5..cc4081aa873 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -856,6 +856,8 @@ event_types.sign_in_notification_timeframe_expired: Venció el tiempo del mensaj event_types.sign_in_unsuccessful_2fa: No se pudo autenticar event_types.webauthn_key_added: Clave de seguridad de hardware añadida event_types.webauthn_key_removed: Clave de seguridad de hardware eliminada +event_types.webauthn_platform_added: Desbloqueo facial o táctil añadido +event_types.webauthn_platform_removed: Desbloqueo facial o táctil eliminado forms.backup_code_regenerate.caution: Si vuelve a generar sus códigos de recuperación, recibirá un conjunto nuevo de códigos. Sus códigos de recuperación originales ya no serán válidos. forms.backup_code_regenerate.confirm: '¿Está seguro de que desea volver a generar sus códigos de recuperación?' forms.backup_code_reminder.body_info: Si no puede acceder a su método de autenticación principal, puede usar códigos de recuperación para acceder a su cuenta. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 9741c609887..93c8f84454e 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -845,6 +845,8 @@ event_types.sign_in_notification_timeframe_expired: Expiration du délai de noti event_types.sign_in_unsuccessful_2fa: Échec de l’authentification event_types.webauthn_key_added: Clé de sécurité physique ajoutée event_types.webauthn_key_removed: Clé de sécurité physique supprimée +event_types.webauthn_platform_added: Déverrouillage facial ou tactile ajouté +event_types.webauthn_platform_removed: Déverrouillage facial ou tactile supprimé forms.backup_code_regenerate.caution: Si vous régénérez vos codes de sauvegarde, vous recevrez un nouvel ensemble de codes de sauvegarde. Vos codes de sauvegarde d’origine ne seront plus valides. forms.backup_code_regenerate.confirm: Êtes-vous sûr de vouloir régénérer vos codes de sauvegarde ? forms.backup_code_reminder.body_info: Si vous perdez l’accès à votre méthode d’authentification principale, vous pouvez utiliser des codes de sauvegarde pour accéder à nouveau à votre compte. diff --git a/config/locales/zh.yml b/config/locales/zh.yml index a16d5822c49..9398e18f931 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -856,6 +856,8 @@ event_types.sign_in_notification_timeframe_expired: 从新设备登录的通知 event_types.sign_in_unsuccessful_2fa: 身份证实失败 event_types.webauthn_key_added: 硬件安全密钥已添加 event_types.webauthn_key_removed: 硬件安全密钥已去掉 +event_types.webauthn_platform_added: 人脸或触摸解锁已添加。 +event_types.webauthn_platform_removed: 人脸或触摸解锁已去掉。 forms.backup_code_regenerate.caution: 如果你重新生成备用代码,会收到新的一套备用代码。你原来的备用代码就会失效。 forms.backup_code_regenerate.confirm: 你确定要重新生成备用代码吗? forms.backup_code_reminder.body_info: 如果你无法使用自己的主要身份证实方法,可以使用备用代码重新获得对账户的访问权。 diff --git a/lib/feature_management.rb b/lib/feature_management.rb index a34a2ec6ef7..0c4a35fd9f7 100644 --- a/lib/feature_management.rb +++ b/lib/feature_management.rb @@ -174,4 +174,9 @@ def self.idv_by_mail_only? outage_status.any_phone_vendor_outage? || outage_status.phone_finder_outage? end + + # Whether pending in person password reset is enabled. + def self.pending_in_person_password_reset_enabled? + IdentityConfig.store.feature_pending_in_person_password_reset_enabled + end end diff --git a/lib/identity_config.rb b/lib/identity_config.rb index bd7f8b65868..1ece95d3f3f 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -153,6 +153,7 @@ def self.store config.add(:facial_match_general_availability_enabled, type: :boolean) config.add(:feature_idv_force_gpo_verification_enabled, type: :boolean) config.add(:feature_idv_hybrid_flow_enabled, type: :boolean) + config.add(:feature_pending_in_person_password_reset_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) diff --git a/lib/tasks/account_reset.rake b/lib/tasks/account_reset.rake index fdc6522fa04..65fe2be1fee 100644 --- a/lib/tasks/account_reset.rake +++ b/lib/tasks/account_reset.rake @@ -3,6 +3,6 @@ namespace :account_reset do desc 'Send Notifications' task send_notifications: :environment do - AccountReset::GrantRequestsAndSendEmails.new.call + GrantAccountResetRequestsAndSendEmails.new.call end end diff --git a/spec/config/initializers/job_configurations_spec.rb b/spec/config/initializers/job_configurations_spec.rb index 261daa8740b..3933453c4fe 100644 --- a/spec/config/initializers/job_configurations_spec.rb +++ b/spec/config/initializers/job_configurations_spec.rb @@ -12,6 +12,14 @@ end end + it 'has a consistent class name' do + aggregate_failures do + Rails.application.config.good_job.cron.each do |_key, config| + expect(config[:class]).to match(/Job\z|Report/) + end + end + end + describe 'weekly reporting' do %w[drop_off_report authentication_report protocols_report].each do |job_name| it "schedules the #{job_name} to run after the end of the week with yesterday's date" do diff --git a/spec/controllers/account_reset/cancel_controller_spec.rb b/spec/controllers/account_reset/cancel_controller_spec.rb index e97a28913e3..beebab93c85 100644 --- a/spec/controllers/account_reset/cancel_controller_spec.rb +++ b/spec/controllers/account_reset/cancel_controller_spec.rb @@ -32,7 +32,6 @@ expect(@analytics).to have_logged_event( 'Account Reset: cancel', success: false, - errors: { token: [t('errors.account_reset.cancel_token_invalid', app_name: APP_NAME)] }, error_details: { token: { cancel_token_invalid: true }, }, @@ -48,7 +47,6 @@ expect(@analytics).to have_logged_event( 'Account Reset: cancel', success: false, - errors: { token: [t('errors.account_reset.cancel_token_missing', app_name: APP_NAME)] }, error_details: { token: { blank: true } }, user_id: 'anonymous-uuid', ) @@ -92,7 +90,6 @@ 'Account Reset: cancel token validation', user_id: 'anonymous-uuid', success: false, - errors: { token: [t('errors.account_reset.cancel_token_invalid', app_name: APP_NAME)] }, error_details: { token: { cancel_token_invalid: true }, }, diff --git a/spec/controllers/account_reset/delete_account_controller_spec.rb b/spec/controllers/account_reset/delete_account_controller_spec.rb index 94b847b35d5..b4ba6dfce7b 100644 --- a/spec/controllers/account_reset/delete_account_controller_spec.rb +++ b/spec/controllers/account_reset/delete_account_controller_spec.rb @@ -6,7 +6,6 @@ let(:invalid_token_message) do t('errors.account_reset.granted_token_invalid', app_name: APP_NAME) end - let(:invalid_token_error) { { token: [invalid_token_message] } } before { stub_analytics } describe '#delete' do @@ -45,7 +44,6 @@ 'Account Reset: delete', user_id: 'anonymous-uuid', success: false, - errors: invalid_token_error, error_details: { token: { granted_token_invalid: true } }, mfa_method_counts: {}, identity_verified: false, @@ -63,7 +61,6 @@ 'Account Reset: delete', user_id: 'anonymous-uuid', success: false, - errors: { token: [t('errors.account_reset.granted_token_missing', app_name: APP_NAME)] }, error_details: { token: { blank: true } }, mfa_method_counts: {}, identity_verified: false, @@ -91,7 +88,6 @@ 'Account Reset: delete', user_id: user.uuid, success: false, - errors: { token: [t('errors.account_reset.granted_token_expired', app_name: APP_NAME)] }, error_details: { token: { granted_token_expired: true } }, mfa_method_counts: {}, identity_verified: false, @@ -179,7 +175,6 @@ 'Account Reset: granted token validation', user_id: 'anonymous-uuid', success: false, - errors: invalid_token_error, error_details: { token: { granted_token_invalid: true } }, ) expect(response).to redirect_to(root_url) @@ -199,7 +194,6 @@ 'Account Reset: granted token validation', user_id: user.uuid, success: false, - errors: { token: [t('errors.account_reset.granted_token_expired', app_name: APP_NAME)] }, error_details: { token: { granted_token_expired: true } }, ) expect(response).to redirect_to(root_url) diff --git a/spec/controllers/accounts/connected_accounts/selected_email_controller_spec.rb b/spec/controllers/accounts/connected_accounts/selected_email_controller_spec.rb index 53826485151..2d70ca3af20 100644 --- a/spec/controllers/accounts/connected_accounts/selected_email_controller_spec.rb +++ b/spec/controllers/accounts/connected_accounts/selected_email_controller_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe Accounts::ConnectedAccounts::SelectedEmailController do - let(:identity) { create(:service_provider_identity, :active) } + let(:identity) { create(:service_provider_identity, :active, verified_attributes: ['email']) } let(:user) { create(:user, :with_multiple_emails, identities: [identity]) } before do @@ -89,8 +89,18 @@ describe '#update' do let(:identity_id) { user.identities.take.id } - let(:selected_email) { user.confirmed_email_addresses.sample } - let(:params) { { identity_id:, select_email_form: { selected_email_id: selected_email.id } } } + let(:verified_attributes) { %w[email] } + let(:selected_email_id) { user.confirmed_email_addresses.sample.id } + let(:params) { { identity_id:, select_email_form: { selected_email_id: selected_email_id } } } + let(:sp) { create(:service_provider) } + before do + identity = ServiceProviderIdentity.find(identity_id) + identity.user_id = user&.id + identity.service_provider = sp.issuer + identity.verified_attributes = verified_attributes + identity.save! + end + subject(:response) { patch :update, params: } it 'redirects to connected accounts path with the appropriate flash message' do @@ -106,7 +116,7 @@ expect(@analytics).to have_logged_event( :sp_select_email_submitted, success: true, - selected_email_id: selected_email.id, + selected_email_id: selected_email_id, ) end @@ -133,7 +143,7 @@ context 'signed out' do let(:other_user) { create(:user, identities: [create(:service_provider_identity, :active)]) } - let(:selected_email) { other_user.confirmed_email_addresses.sample } + let(:selected_email_id) { other_user.confirmed_email_addresses.sample.id } let(:identity_id) { other_user.identities.take.id } let(:user) { nil } diff --git a/spec/controllers/concerns/two_factor_authenticatable_methods_spec.rb b/spec/controllers/concerns/two_factor_authenticatable_methods_spec.rb index 8bd4e65e97d..d83d4ecd833 100644 --- a/spec/controllers/concerns/two_factor_authenticatable_methods_spec.rb +++ b/spec/controllers/concerns/two_factor_authenticatable_methods_spec.rb @@ -184,7 +184,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication', success: false, - errors: { code: ['pattern_mismatch'] }, error_details: { code: { pattern_mismatch: true } }, multi_factor_auth_method: TwoFactorAuthenticatable::AuthMethod::SMS, enabled_mfa_methods_count: 1, diff --git a/spec/controllers/event_disavowal_controller_spec.rb b/spec/controllers/event_disavowal_controller_spec.rb index 3ca8d2d91ee..0f0f8bb309e 100644 --- a/spec/controllers/event_disavowal_controller_spec.rb +++ b/spec/controllers/event_disavowal_controller_spec.rb @@ -48,7 +48,6 @@ build_analytics_hash( user_id: event.user.uuid, success: false, - errors: { event: [t('event_disavowals.errors.event_already_disavowed')] }, error_details: { event: { event_already_disavowed: true } }, ), ) @@ -64,7 +63,6 @@ build_analytics_hash( user_id: event.user.uuid, success: false, - errors: { event: [t('event_disavowals.errors.event_already_disavowed')] }, error_details: { event: { event_already_disavowed: true } }, ), ) @@ -102,13 +100,6 @@ build_analytics_hash( user_id: event.user.uuid, success: false, - errors: { - password: [ - t( - 'errors.attributes.password.too_short.other', count: Devise.password_length.first - ), - ], - }, error_details: { password: { too_short: true } }, ), ) @@ -127,7 +118,6 @@ build_analytics_hash( user_id: event.user.uuid, success: false, - errors: { password: ['Password must be at least 12 characters long'] }, error_details: { password: { too_short: true } }, ), ) @@ -151,7 +141,6 @@ build_analytics_hash( user_id: event.user.uuid, success: false, - errors: { event: [t('event_disavowals.errors.event_already_disavowed')] }, error_details: { event: { event_already_disavowed: true } }, ), ) @@ -173,9 +162,6 @@ 'Event disavowal token invalid', build_analytics_hash( success: false, - errors: { - user: [t('event_disavowals.errors.no_account')], - }, error_details: { user: { blank: true }, }, @@ -185,12 +171,11 @@ end end - def build_analytics_hash(success: true, errors: nil, error_details: nil, user_id: nil) + def build_analytics_hash(success: true, error_details: nil, user_id: nil) { event_created_at: event.created_at, disavowed_device_last_used_at: event.device&.last_used_at, success:, - errors:, error_details:, event_id: event.id, event_type: event.event_type, diff --git a/spec/controllers/idv/by_mail/enter_code_controller_spec.rb b/spec/controllers/idv/by_mail/enter_code_controller_spec.rb index 24102dbc1ea..7126a4c95f4 100644 --- a/spec/controllers/idv/by_mail/enter_code_controller_spec.rb +++ b/spec/controllers/idv/by_mail/enter_code_controller_spec.rb @@ -163,7 +163,6 @@ end describe '#create' do - let(:otp_code_error_message) { { otp: [t('errors.messages.confirmation_code_incorrect')] } } let(:success_properties) { { success: true } } context 'user does not have a pending profile' do @@ -382,7 +381,6 @@ expect(@analytics).to have_logged_event( 'IdV: enter verify by mail code submitted', success: false, - errors: otp_code_error_message, pending_in_person_enrollment: false, fraud_check_failed: false, letter_count: 1, @@ -416,7 +414,6 @@ it 'redirects to the rate limited index page to show errors' do analytics_args = { success: false, - errors: otp_code_error_message, pending_in_person_enrollment: false, fraud_check_failed: false, letter_count: 1, @@ -450,11 +447,11 @@ failed_gpo_submission_events = @analytics.events['IdV: enter verify by mail code submitted'] - .reject { |event_attributes| event_attributes[:errors].blank? } + .reject { |event_attributes| event_attributes[:error_details].blank? } successful_gpo_submission_events = @analytics.events['IdV: enter verify by mail code submitted'] - .select { |event_attributes| event_attributes[:errors].blank? } + .select { |event_attributes| event_attributes[:error_details].blank? } expect(failed_gpo_submission_events.count).to eq(max_attempts - 1) expect(successful_gpo_submission_events.count).to eq(1) diff --git a/spec/controllers/idv/how_to_verify_controller_spec.rb b/spec/controllers/idv/how_to_verify_controller_spec.rb index d5a450e99be..29fb81ae104 100644 --- a/spec/controllers/idv/how_to_verify_controller_spec.rb +++ b/spec/controllers/idv/how_to_verify_controller_spec.rb @@ -171,7 +171,6 @@ step: 'how_to_verify', analytics_id: 'Doc Auth', error_details: { selection: { blank: true } }, - errors: { selection: ['Select a way to verify your identity.'] }, success: false, } end @@ -191,7 +190,6 @@ analytics_id: 'Doc Auth', selection:, error_details: { selection: { inclusion: true } }, - errors: { selection: ['Select a way to verify your identity.'] }, success: false, } end diff --git a/spec/controllers/idv/image_uploads_controller_spec.rb b/spec/controllers/idv/image_uploads_controller_spec.rb index 545301c94f7..c35528847ef 100644 --- a/spec/controllers/idv/image_uploads_controller_spec.rb +++ b/spec/controllers/idv/image_uploads_controller_spec.rb @@ -103,9 +103,6 @@ expect(@analytics).to have_logged_event( 'IdV: doc auth image upload form submitted', success: false, - errors: { - front: [I18n.t('doc_auth.errors.not_a_file')], - }, error_details: { front: { not_a_file: true }, }, @@ -219,9 +216,6 @@ expect(@analytics).to have_logged_event( 'IdV: doc auth image upload form submitted', success: false, - errors: { - limit: [I18n.t('doc_auth.errors.rate_limited_heading')], - }, error_details: { limit: { rate_limited: true }, }, @@ -520,9 +514,6 @@ expect(@analytics).to have_logged_event( 'IdV: doc auth image upload vendor pii validation', success: false, - errors: { - name: [I18n.t('doc_auth.errors.alerts.full_name_check')], - }, error_details: { name: { name: true }, }, @@ -598,9 +589,6 @@ expect(@analytics).to have_logged_event( 'IdV: doc auth image upload vendor pii validation', success: false, - errors: { - state: [I18n.t('doc_auth.errors.general.no_liveness')], - }, error_details: { state: { inclusion: true }, }, @@ -676,9 +664,6 @@ expect(@analytics).to have_logged_event( 'IdV: doc auth image upload vendor pii validation', success: false, - errors: { - state_id_number: [I18n.t('doc_auth.errors.general.no_liveness')], - }, error_details: { state_id_number: { blank: true }, }, @@ -750,9 +735,6 @@ expect(@analytics).to have_logged_event( 'IdV: doc auth image upload vendor pii validation', success: false, - errors: { - dob: [I18n.t('doc_auth.errors.alerts.birth_date_checks')], - }, error_details: { dob: { dob: true }, }, @@ -825,11 +807,6 @@ expect(@analytics).to have_logged_event( 'IdV: doc auth image upload vendor pii validation', success: false, - errors: { - state_id_expiration: [ - 'Try taking new pictures.', - ], - }, error_details: { state_id_expiration: { state_id_expiration: true }, }, diff --git a/spec/controllers/idv/in_person/ssn_controller_spec.rb b/spec/controllers/idv/in_person/ssn_controller_spec.rb index 48fef202097..da9ea25d4ef 100644 --- a/spec/controllers/idv/in_person/ssn_controller_spec.rb +++ b/spec/controllers/idv/in_person/ssn_controller_spec.rb @@ -197,9 +197,6 @@ flow_path: 'standard', step: 'ssn', success: false, - errors: { - ssn: ['Enter a nine-digit Social Security number'], - }, error_details: { ssn: { invalid: true } }, } end diff --git a/spec/controllers/idv/phone_controller_spec.rb b/spec/controllers/idv/phone_controller_spec.rb index e572dd1a4ee..90f60af5356 100644 --- a/spec/controllers/idv/phone_controller_spec.rb +++ b/spec/controllers/idv/phone_controller_spec.rb @@ -290,10 +290,6 @@ expect(@analytics).to have_logged_event( 'IdV: phone confirmation form', success: false, - errors: { - phone: [improbable_phone_message], - otp_delivery_preference: [improbable_otp_message], - }, error_details: { phone: { improbable_phone: true }, otp_delivery_preference: { inclusion: true }, diff --git a/spec/controllers/idv/ssn_controller_spec.rb b/spec/controllers/idv/ssn_controller_spec.rb index 22f315c532a..fbc9c012cdd 100644 --- a/spec/controllers/idv/ssn_controller_spec.rb +++ b/spec/controllers/idv/ssn_controller_spec.rb @@ -234,9 +234,6 @@ flow_path: 'standard', step: 'ssn', success: false, - errors: { - ssn: [t('idv.errors.pattern_mismatch.ssn')], - }, error_details: { ssn: { invalid: true } }, } end diff --git a/spec/controllers/idv/welcome_controller_spec.rb b/spec/controllers/idv/welcome_controller_spec.rb index 94d523c8e49..3fd0e2a2c0b 100644 --- a/spec/controllers/idv/welcome_controller_spec.rb +++ b/spec/controllers/idv/welcome_controller_spec.rb @@ -134,18 +134,24 @@ .to change { subject.idv_session.document_capture_session_uuid }.from(nil) end - context 'with previous establishing in-person enrollments' do - let!(:enrollment) { create(:in_person_enrollment, :establishing, user: user, profile: nil) } + context 'with previous establishing and pending in-person enrollments' do + let!(:establishing_enrollment) { create(:in_person_enrollment, :establishing, user: user) } + let(:password_reset_profile) { create(:profile, :password_reset, user: user) } + let!(:pending_enrollment) do + create(:in_person_enrollment, :pending, user: user, profile: password_reset_profile) + end before do allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true) end - it 'cancels all previous establishing enrollments' do + it 'cancels all previous establishing and pending enrollments' do put :update - expect(enrollment.reload.status).to eq(InPersonEnrollment::STATUS_CANCELLED) + expect(establishing_enrollment.reload.status).to eq(InPersonEnrollment::STATUS_CANCELLED) + expect(pending_enrollment.reload.status).to eq(InPersonEnrollment::STATUS_CANCELLED) expect(user.establishing_in_person_enrollment).to be_blank + expect(user.pending_in_person_enrollment).to be_blank end end end diff --git a/spec/controllers/openid_connect/authorization_controller_spec.rb b/spec/controllers/openid_connect/authorization_controller_spec.rb index 68287933ad6..5b93ceffad8 100644 --- a/spec/controllers/openid_connect/authorization_controller_spec.rb +++ b/spec/controllers/openid_connect/authorization_controller_spec.rb @@ -2009,7 +2009,6 @@ prompt: '', allow_prompt_login: true, unauthorized_scope: true, - errors: hash_including(:prompt), error_details: hash_including(:prompt), user_fully_authenticated: true, acr_values: acr_values, @@ -2071,7 +2070,6 @@ prompt:, allow_prompt_login: true, unauthorized_scope: false, - errors: hash_including(:acr_values), error_details: hash_including(:acr_values), user_fully_authenticated: true, acr_values: '', @@ -2237,7 +2235,6 @@ prompt: '', allow_prompt_login: true, unauthorized_scope: true, - errors: hash_including(:prompt), error_details: hash_including(:prompt), user_fully_authenticated: true, acr_values: '', @@ -2276,7 +2273,6 @@ success: false, prompt: 'select_account', unauthorized_scope: true, - errors: hash_including(:client_id), error_details: hash_including(:client_id), user_fully_authenticated: true, acr_values: 'http://idmanagement.gov/ns/assurance/ial/1', @@ -2311,7 +2307,6 @@ success: false, prompt: 'select_account', unauthorized_scope: true, - errors: hash_including(:client_id), error_details: hash_including(:client_id), user_fully_authenticated: true, acr_values: '', @@ -2324,6 +2319,107 @@ expect(@analytics).to_not have_logged_event('SP redirect initiated') end end + + context 'with SP requesting a single email' do + let(:acr_values) { Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF } + let(:vtr) { nil } + let(:verified_attributes) { %w[email] } + let(:shared_email_address) do + create( + :email_address, + email: 'shared2@email.com', + user: user, + last_sign_in_at: 1.hour.ago, + ) + end + let!(:identity) do + create( + :service_provider_identity, + user: user, + session_uuid: SecureRandom.uuid, + service_provider: service_provider.issuer, + verified_attributes: verified_attributes, + ) + end + before do + allow(IdentityConfig.store).to receive(:feature_select_email_to_share_enabled) + .and_return(true) + controller.user_session[:selected_email_id_for_linked_identity] = shared_email_address.id + end + + it 'updates identity to be the value in session' do + identity = user.identities.find_by(service_provider: service_provider.issuer) + action + identity.reload + expect(identity.email_address_id).to eq(shared_email_address.id) + end + end + + context 'with SP requesting a single email and all emails' do + let(:verified_attributes) { %w[email all_emails] } + let(:shared_email_address) do + create( + :email_address, + email: 'shared2@email.com', + user: user, + last_sign_in_at: 1.hour.ago, + ) + end + let!(:identity) do + create( + :service_provider_identity, + user: user, + session_uuid: SecureRandom.uuid, + service_provider: service_provider.issuer, + verified_attributes: verified_attributes, + email_address_id: shared_email_address.id, + ) + end + before do + allow(IdentityConfig.store).to receive(:feature_select_email_to_share_enabled) + .and_return(true) + end + + it 'updates identity email_address to be nil' do + identity = user.identities.find_by(service_provider: service_provider.issuer) + action + identity.reload + expect(identity.email_address_id).to eq(nil) + end + end + + context 'with SP requesting no emails' do + let(:verified_attributes) { %w[first_name last_name] } + let(:shared_email_address) do + create( + :email_address, + email: 'shared2@email.com', + user: user, + last_sign_in_at: 1.hour.ago, + ) + end + let!(:identity) do + create( + :service_provider_identity, + user: user, + session_uuid: SecureRandom.uuid, + service_provider: service_provider.issuer, + verified_attributes: verified_attributes, + email_address_id: shared_email_address.id, + ) + end + before do + allow(IdentityConfig.store).to receive(:feature_select_email_to_share_enabled) + .and_return(true) + end + + it 'updates identity email_address to be nil' do + identity = user.identities.find_by(service_provider: service_provider.issuer) + action + identity.reload + expect(identity.email_address_id).to eq(nil) + end + end end context 'user is not signed in' do diff --git a/spec/controllers/openid_connect/logout_controller_spec.rb b/spec/controllers/openid_connect/logout_controller_spec.rb index 1360f3fd8ed..fca78f5b5e2 100644 --- a/spec/controllers/openid_connect/logout_controller_spec.rb +++ b/spec/controllers/openid_connect/logout_controller_spec.rb @@ -199,9 +199,6 @@ action - errors = { - redirect_uri: [t('openid_connect.authorization.errors.redirect_uri_no_match')], - } expect(@analytics).to have_logged_event( 'OIDC Logout Requested', hash_including( @@ -209,8 +206,9 @@ client_id: service_provider.issuer, client_id_parameter_present: false, id_token_hint_parameter_present: true, - errors: errors, - error_details: hash_including(*errors.keys), + error_details: { + redirect_uri: { redirect_uri_no_match: true }, + }, sp_initiated: true, oidc: true, ), @@ -243,7 +241,6 @@ success: false, client_id_parameter_present: false, id_token_hint_parameter_present: true, - errors: hash_including(*errors_keys), error_details: hash_including(*errors_keys), sp_initiated: true, oidc: true, @@ -362,9 +359,6 @@ action - errors = { - redirect_uri: [t('openid_connect.authorization.errors.redirect_uri_no_match')], - } expect(@analytics).to have_logged_event( 'OIDC Logout Requested', hash_including( @@ -372,8 +366,9 @@ client_id: service_provider.issuer, client_id_parameter_present: true, id_token_hint_parameter_present: false, - errors: errors, - error_details: hash_including(*errors.keys), + error_details: { + redirect_uri: { redirect_uri_no_match: true }, + }, sp_initiated: true, oidc: true, ), @@ -498,9 +493,6 @@ action - errors = { - id_token_hint: [t('openid_connect.logout.errors.id_token_hint_present')], - } expect(@analytics).to have_logged_event( 'OIDC Logout Requested', hash_including( @@ -508,8 +500,9 @@ client_id: service_provider.issuer, client_id_parameter_present: true, id_token_hint_parameter_present: true, - errors: errors, - error_details: hash_including(*errors.keys), + error_details: { + id_token_hint: { present: true }, + }, sp_initiated: true, oidc: true, ), @@ -549,9 +542,6 @@ action - errors = { - redirect_uri: [t('openid_connect.authorization.errors.redirect_uri_no_match')], - } expect(@analytics).to have_logged_event( 'OIDC Logout Requested', hash_including( @@ -559,8 +549,9 @@ client_id: service_provider.issuer, client_id_parameter_present: true, id_token_hint_parameter_present: false, - errors: errors, - error_details: hash_including(*errors.keys), + error_details: { + redirect_uri: { redirect_uri_no_match: true }, + }, sp_initiated: true, oidc: true, ), diff --git a/spec/controllers/openid_connect/token_controller_spec.rb b/spec/controllers/openid_connect/token_controller_spec.rb index e4212c73a53..3ee7c29ecea 100644 --- a/spec/controllers/openid_connect/token_controller_spec.rb +++ b/spec/controllers/openid_connect/token_controller_spec.rb @@ -93,7 +93,6 @@ success: false, client_id: client_id, user_id: user.uuid, - errors: hash_including(:grant_type), code_digest: kind_of(String), code_verifier_present: false, error_details: hash_including(:grant_type), diff --git a/spec/controllers/openid_connect/user_info_controller_spec.rb b/spec/controllers/openid_connect/user_info_controller_spec.rb index a1d90ad6952..3cf76a1227d 100644 --- a/spec/controllers/openid_connect/user_info_controller_spec.rb +++ b/spec/controllers/openid_connect/user_info_controller_spec.rb @@ -25,7 +25,6 @@ expect(@analytics).to have_logged_event( 'OpenID Connect: bearer token authentication', success: false, - errors: hash_including(:access_token), error_details: hash_including(:access_token), ) @@ -59,7 +58,6 @@ expect(@analytics).to have_logged_event( 'OpenID Connect: bearer token authentication', success: false, - errors: hash_including(:access_token), error_details: hash_including(:access_token), ) @@ -92,7 +90,6 @@ expect(@analytics).to have_logged_event( 'OpenID Connect: bearer token authentication', success: false, - errors: hash_including(:access_token), error_details: hash_including(:access_token), ) @@ -129,7 +126,6 @@ expect(@analytics).to have_logged_event( 'OpenID Connect: bearer token authentication', success: false, - errors: { access_token: [t('openid_connect.user_info.errors.not_found')] }, error_details: { access_token: { not_found: true } }, ) end diff --git a/spec/controllers/risc/security_events_controller_spec.rb b/spec/controllers/risc/security_events_controller_spec.rb index 2a9ed0bdb79..ebeee405ab4 100644 --- a/spec/controllers/risc/security_events_controller_spec.rb +++ b/spec/controllers/risc/security_events_controller_spec.rb @@ -85,7 +85,6 @@ client_id: service_provider.issuer, event_type: event_type, error_code: SecurityEventForm::ErrorCodes::JWT_AUD, - errors: kind_of(Hash), error_details: kind_of(Hash), jti: jti, success: false, diff --git a/spec/controllers/saml_idp_controller_spec.rb b/spec/controllers/saml_idp_controller_spec.rb index 27875bd504b..f9f1d7fabdd 100644 --- a/spec/controllers/saml_idp_controller_spec.rb +++ b/spec/controllers/saml_idp_controller_spec.rb @@ -1096,7 +1096,6 @@ def name_id_version(format_urn) 'SAML Auth', hash_including( success: false, - errors: { authn_context: [t('errors.messages.unauthorized_authn_context')] }, error_details: { authn_context: { unauthorized_authn_context: true } }, nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT, authn_context: [unknown_value], @@ -1374,7 +1373,6 @@ def name_id_version(format_urn) 'SAML Auth', hash_including( success: false, - errors: { service_provider: [t('errors.messages.unauthorized_service_provider')] }, error_details: { service_provider: { unauthorized_service_provider: true } }, nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT, authn_context: request_authn_contexts, @@ -1421,10 +1419,6 @@ def name_id_version(format_urn) 'SAML Auth', hash_including( success: false, - errors: { - service_provider: [t('errors.messages.unauthorized_service_provider')], - authn_context: [t('errors.messages.unauthorized_authn_context')], - }, error_details: { authn_context: { unauthorized_authn_context: true }, service_provider: { unauthorized_service_provider: true }, @@ -1486,7 +1480,6 @@ def name_id_version(format_urn) 'SAML Auth', hash_including( success: false, - errors: { service_provider: [t('errors.messages.no_cert_registered')] }, error_details: { service_provider: { no_cert_registered: true } }, ), ) @@ -1567,6 +1560,78 @@ def name_id_version(format_urn) end end + context 'with shared email feature turned on' do + let(:user) { create(:user, :fully_registered) } + let(:service_provider) { build(:service_provider, issuer: saml_settings.issuer) } + + before do + allow(IdentityConfig.store).to receive(:feature_select_email_to_share_enabled) + .and_return(true) + stub_sign_in(user) + session[:sign_in_flow] = :sign_in + end + + context 'with SP requesting a single email' do + let(:verified_attributes) { %w[email] } + let(:shared_email_address) do + create( + :email_address, + email: 'shared2@email.com', + user: user, + last_sign_in_at: 1.hour.ago, + ) + end + let!(:identity) do + create( + :service_provider_identity, + user: user, + session_uuid: SecureRandom.uuid, + service_provider: service_provider.issuer, + verified_attributes: verified_attributes, + ) + end + before do + controller.user_session[:selected_email_id_for_linked_identity] = shared_email_address.id + end + + it 'updates identity to be the value in session' do + identity = user.identities.find_by(service_provider: service_provider.issuer) + saml_get_auth(saml_settings) + identity.reload + expect(identity.email_address_id).to eq(shared_email_address.id) + end + end + + context 'with SP requesting a single email and all emails' do + let(:verified_attributes) { %w[email all_emails] } + let(:shared_email_address) do + create( + :email_address, + email: 'shared2@email.com', + user: user, + last_sign_in_at: 1.hour.ago, + ) + end + let!(:identity) do + create( + :service_provider_identity, + user: user, + session_uuid: SecureRandom.uuid, + service_provider: service_provider.issuer, + verified_attributes: verified_attributes, + email_address_id: shared_email_address.id, + ) + end + + it 'updates identity email_address to be nil' do + identity = user.identities.find_by(service_provider: service_provider.issuer) + saml_get_auth(saml_settings) + identity.reload + expect(identity.email_address_id).to eq(nil) + end + end + end + context 'POST to auth correctly stores SP in session' do let(:acr_values) do Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF + @@ -1912,7 +1977,6 @@ def name_id_version(format_urn) 'SAML Auth', hash_including( success: false, - errors: { service_provider: ['We cannot detect a certificate in your request.'] }, error_details: { service_provider: { blank_cert_element_req: true } }, nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT, authn_context: [Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF], diff --git a/spec/controllers/sign_up/email_confirmations_controller_spec.rb b/spec/controllers/sign_up/email_confirmations_controller_spec.rb index 56ee0371e5b..2874847d7ce 100644 --- a/spec/controllers/sign_up/email_confirmations_controller_spec.rb +++ b/spec/controllers/sign_up/email_confirmations_controller_spec.rb @@ -6,7 +6,6 @@ { success: false, error_details: { confirmation_token: { not_found: true } }, - errors: { confirmation_token: ['not found'] }, } end @@ -87,7 +86,6 @@ expect(@analytics).to have_logged_event( 'User Registration: Email Confirmation', success: false, - errors: { confirmation_token: [t('errors.messages.expired')] }, error_details: { confirmation_token: { expired: true } }, user_id: email_address.user.uuid, ) @@ -110,7 +108,6 @@ expect(@analytics).to have_logged_event( 'User Registration: Email Confirmation', success: false, - errors: { confirmation_token: [t('errors.messages.expired')] }, error_details: { confirmation_token: { expired: true } }, user_id: user.uuid, ) diff --git a/spec/controllers/sign_up/passwords_controller_spec.rb b/spec/controllers/sign_up/passwords_controller_spec.rb index b90c747a195..0841c3dda46 100644 --- a/spec/controllers/sign_up/passwords_controller_spec.rb +++ b/spec/controllers/sign_up/passwords_controller_spec.rb @@ -115,14 +115,6 @@ expect(@analytics).to have_logged_event( 'Password Creation', success: false, - errors: { - password: [ - t('errors.attributes.password.too_short', count: Devise.password_length.first), - ], - password_confirmation: [ - t('errors.messages.too_short', count: Devise.password_length.first), - ], - }, error_details: { password: { too_short: true }, password_confirmation: { too_short: true }, @@ -143,9 +135,6 @@ expect(@analytics).to have_logged_event( 'Password Creation', success: false, - errors: { - password_confirmation: [t('errors.messages.password_mismatch')], - }, error_details: { password_confirmation: { mismatch: true }, }, diff --git a/spec/controllers/sign_up/registrations_controller_spec.rb b/spec/controllers/sign_up/registrations_controller_spec.rb index e6156bf038b..48886e11726 100644 --- a/spec/controllers/sign_up/registrations_controller_spec.rb +++ b/spec/controllers/sign_up/registrations_controller_spec.rb @@ -144,7 +144,6 @@ 'User Registration: Email Submitted', success: false, rate_limited: false, - errors: { email: [t('valid_email.validations.email.invalid')] }, error_details: { email: { invalid: true } }, email_already_exists: false, user_id: 'anonymous-uuid', diff --git a/spec/controllers/sign_up/select_email_controller_spec.rb b/spec/controllers/sign_up/select_email_controller_spec.rb index 7eefddb0368..5a6b7826241 100644 --- a/spec/controllers/sign_up/select_email_controller_spec.rb +++ b/spec/controllers/sign_up/select_email_controller_spec.rb @@ -2,7 +2,9 @@ RSpec.describe SignUp::SelectEmailController do let(:user) { create(:user, :with_multiple_emails) } - let(:sp) { create(:service_provider) } + let(:sp) do + create(:service_provider) + end before do stub_sign_in(user) @@ -75,8 +77,8 @@ end describe '#create' do - let(:selected_email) { user.confirmed_email_addresses.sample } - let(:params) { { select_email_form: { selected_email_id: selected_email.id } } } + let(:selected_email_id) { user.confirmed_email_addresses.sample.id } + let(:params) { { select_email_form: { selected_email_id: selected_email_id } } } subject(:response) { post :create, params: params } @@ -85,7 +87,7 @@ expect( controller.user_session[:selected_email_id_for_linked_identity], - ).to eq(selected_email.id.to_s) + ).to eq(selected_email_id.to_s) end it 'logs analytics event' do @@ -97,13 +99,13 @@ :sp_select_email_submitted, success: true, needs_completion_screen_reason: :new_attributes, - selected_email_id: selected_email.id, + selected_email_id: selected_email_id, ) end context 'with a corrupted email selected_email_id form' do let(:other_user) { create(:user) } - let(:selected_email) { other_user.confirmed_email_addresses.sample } + let(:selected_email_id) { other_user.confirmed_email_addresses.sample.id } it 'rejects email not belonging to the user' do expect(response).to redirect_to(sign_up_select_email_path) @@ -122,7 +124,7 @@ success: false, error_details: { selected_email_id: { not_found: true } }, needs_completion_screen_reason: :new_attributes, - selected_email_id: selected_email.id, + selected_email_id: selected_email_id, ) end end diff --git a/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb index 34b08cf63e3..33667857dae 100644 --- a/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb @@ -212,7 +212,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication', success: false, - errors: { personal_key: [t('errors.messages.personal_key_incorrect')] }, error_details: { personal_key: { personal_key_incorrect: true } }, enabled_mfa_methods_count: 1, multi_factor_auth_method: 'personal-key', diff --git a/spec/controllers/users/edit_phone_controller_spec.rb b/spec/controllers/users/edit_phone_controller_spec.rb index a1f25d7748d..fe1af0ca074 100644 --- a/spec/controllers/users/edit_phone_controller_spec.rb +++ b/spec/controllers/users/edit_phone_controller_spec.rb @@ -42,7 +42,6 @@ expect(@analytics).to have_logged_event( 'Phone Number Change: Form submitted', success: false, - errors: hash_including(:delivery_preference), error_details: { delivery_preference: { inclusion: true } }, delivery_preference: 'noise', make_default_number: true, diff --git a/spec/controllers/users/passwords_controller_spec.rb b/spec/controllers/users/passwords_controller_spec.rb index 73701202663..824c961daa7 100644 --- a/spec/controllers/users/passwords_controller_spec.rb +++ b/spec/controllers/users/passwords_controller_spec.rb @@ -216,18 +216,6 @@ expect(@analytics).to have_logged_event( 'Password Changed', success: false, - errors: { - password: [ - t( - 'errors.attributes.password.too_short.other', - count: Devise.password_length.first, - ), - ], - password_confirmation: [t( - 'errors.messages.too_short.other', - count: Devise.password_length.first, - )], - }, error_details: { password: { too_short: true }, password_confirmation: { too_short: true }, @@ -254,18 +242,6 @@ expect(@analytics).to have_logged_event( 'Password Changed', success: false, - errors: { - password: [ - t( - 'errors.attributes.password.too_short.other', - count: Devise.password_length.first, - ), - ], - password_confirmation: [t( - 'errors.messages.too_short.other', - count: Devise.password_length.first, - )], - }, error_details: { password: { too_short: true }, password_confirmation: { too_short: true }, @@ -302,9 +278,6 @@ expect(@analytics).to have_logged_event( 'Password Changed', success: false, - errors: { - password_confirmation: [t('errors.messages.password_mismatch')], - }, error_details: { password_confirmation: { mismatch: true }, }, diff --git a/spec/controllers/users/phone_setup_controller_spec.rb b/spec/controllers/users/phone_setup_controller_spec.rb index b1e1e60e18e..2d85afe5982 100644 --- a/spec/controllers/users/phone_setup_controller_spec.rb +++ b/spec/controllers/users/phone_setup_controller_spec.rb @@ -71,12 +71,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication: phone setup', success: false, - errors: { - phone: [ - t('errors.messages.improbable_phone'), - t('two_factor_authentication.otp_delivery_preference.voice_unsupported', location: ''), - ], - }, error_details: { phone: { improbable_phone: true, diff --git a/spec/controllers/users/reset_passwords_controller_spec.rb b/spec/controllers/users/reset_passwords_controller_spec.rb index 2258371d997..5bec6a79ddf 100644 --- a/spec/controllers/users/reset_passwords_controller_spec.rb +++ b/spec/controllers/users/reset_passwords_controller_spec.rb @@ -32,7 +32,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Token Submitted', success: false, - errors: { user: ['invalid_token'] }, error_details: { user: { blank: true } }, ) expect(response).to redirect_to new_user_password_path @@ -64,7 +63,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Token Submitted', success: false, - errors: { user: ['invalid_token'] }, error_details: { user: { blank: true } }, ) expect(response).to redirect_to new_user_password_path @@ -86,7 +84,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Token Submitted', success: false, - errors: { user: ['token_expired'] }, error_details: { user: { token_expired: true } }, user_id: '123', ) @@ -170,14 +167,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Password Submitted', success: false, - errors: { - password: [password_error_message], - password_confirmation: [t( - 'errors.messages.too_short.other', - count: Devise.password_length.first, - )], - reset_password_token: ['token_expired'], - }, error_details: { password: { too_short: true }, password_confirmation: { too_short: true }, @@ -219,13 +208,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Password Submitted', success: false, - errors: { - password: [password_error_message], - password_confirmation: [t( - 'errors.messages.too_short.other', - count: Devise.password_length.first, - )], - }, error_details: { password: { too_short: true }, password_confirmation: { too_short: true }, @@ -266,9 +248,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Password Submitted', success: false, - errors: { - password_confirmation: [t('errors.messages.password_mismatch')], - }, error_details: { password_confirmation: { mismatch: true }, }, @@ -557,7 +536,6 @@ expect(@analytics).to have_logged_event( 'Password Reset: Email Submitted', success: false, - errors: { email: [t('valid_email.validations.email.invalid')] }, error_details: { email: { invalid: true } }, user_id: 'nonexistent-uuid', confirmed: false, diff --git a/spec/controllers/users/totp_setup_controller_spec.rb b/spec/controllers/users/totp_setup_controller_spec.rb index 1dee71f1f2c..69e551f0855 100644 --- a/spec/controllers/users/totp_setup_controller_spec.rb +++ b/spec/controllers/users/totp_setup_controller_spec.rb @@ -218,7 +218,6 @@ 'Multi-Factor Authentication Setup', success: false, error_details: { name: { blank: true } }, - errors: { name: [t('errors.messages.blank')] }, totp_secret_present: true, multi_factor_auth_method: 'totp', enabled_mfa_methods_count: 1, diff --git a/spec/controllers/users/verify_personal_key_controller_spec.rb b/spec/controllers/users/verify_personal_key_controller_spec.rb index f54ea385343..f87f18795b1 100644 --- a/spec/controllers/users/verify_personal_key_controller_spec.rb +++ b/spec/controllers/users/verify_personal_key_controller_spec.rb @@ -133,7 +133,6 @@ expect(@analytics).to have_logged_event( 'Personal key reactivation: Personal key form submitted', - errors: { personal_key: ['Please fill in this field.', error_text] }, error_details: { personal_key: { blank: true, personal_key: true } }, success: false, ) diff --git a/spec/controllers/users/webauthn_setup_controller_spec.rb b/spec/controllers/users/webauthn_setup_controller_spec.rb index afa5fc4a37d..7ba85ab0485 100644 --- a/spec/controllers/users/webauthn_setup_controller_spec.rb +++ b/spec/controllers/users/webauthn_setup_controller_spec.rb @@ -467,9 +467,6 @@ expect(@analytics).to have_logged_event( 'Multi-Factor Authentication Setup', enabled_mfa_methods_count: 0, - errors: { - attestation_object: [I18n.t('errors.webauthn_platform_setup.general_error')], - }, error_details: { attestation_object: { invalid: true } }, in_account_creation_flow: false, mfa_method_counts: {}, diff --git a/spec/features/account_connected_apps_spec.rb b/spec/features/account_connected_apps_spec.rb index c37ad9cd25f..5538f2d20e8 100644 --- a/spec/features/account_connected_apps_spec.rb +++ b/spec/features/account_connected_apps_spec.rb @@ -18,7 +18,7 @@ user: user, created_at: Time.zone.now - 80.days, service_provider: 'http://localhost:3000', - verified_attributes: ['email'], + verified_attributes: %w[email], ) end let(:identity_without_link) do diff --git a/spec/features/account_reset/cancel_request_spec.rb b/spec/features/account_reset/cancel_request_spec.rb index 7775df40263..dfe5daf6a9d 100644 --- a/spec/features/account_reset/cancel_request_spec.rb +++ b/spec/features/account_reset/cancel_request_spec.rb @@ -20,7 +20,7 @@ click_button t('account_reset.request.yes_continue') travel_to(Time.zone.now + 2.days + 1) do - AccountReset::GrantRequestsAndSendEmails.new.perform(Time.zone.today) + GrantAccountResetRequestsAndSendEmailsJob.new.perform(Time.zone.today) open_last_email click_email_link_matching(/cancel\?token/) click_button t('account_reset.cancel_request.cancel_button') diff --git a/spec/features/account_reset/delete_account_spec.rb b/spec/features/account_reset/delete_account_spec.rb index d421151967f..cc6d07bfd91 100644 --- a/spec/features/account_reset/delete_account_spec.rb +++ b/spec/features/account_reset/delete_account_spec.rb @@ -50,7 +50,7 @@ reset_email travel_to(Time.zone.now + 2.days + 2) do - AccountReset::GrantRequestsAndSendEmails.new.perform(Time.zone.today) + GrantAccountResetRequestsAndSendEmailsJob.new.perform(Time.zone.today) open_last_email click_email_link_matching(/delete_account\?token/) @@ -133,7 +133,7 @@ }, ) - AccountReset::GrantRequestsAndSendEmails.new.perform(Time.zone.today) + GrantAccountResetRequestsAndSendEmailsJob.new.perform(Time.zone.today) open_last_email click_email_link_matching(/delete_account\?token/) diff --git a/spec/features/event_disavowal_spec.rb b/spec/features/event_disavowal_spec.rb index 637498c3243..c873ae70b3b 100644 --- a/spec/features/event_disavowal_spec.rb +++ b/spec/features/event_disavowal_spec.rb @@ -16,7 +16,7 @@ end Capybara.reset_session! - CreateNewDeviceAlert.new.perform(Time.zone.now) + CreateNewDeviceAlertJob.new.perform(Time.zone.now) disavow_last_action_and_reset_password end @@ -26,7 +26,7 @@ sign_in_user(user) end - CreateNewDeviceAlert.new.perform(Time.zone.now) + CreateNewDeviceAlertJob.new.perform(Time.zone.now) expect_delivered_email_count(1) expect_delivered_email( diff --git a/spec/features/idv/get_proofing_results_job_scenarios_spec.rb b/spec/features/idv/get_proofing_results_job_scenarios_spec.rb index 5b8dd20a4c5..b157390c519 100644 --- a/spec/features/idv/get_proofing_results_job_scenarios_spec.rb +++ b/spec/features/idv/get_proofing_results_job_scenarios_spec.rb @@ -2,6 +2,7 @@ require 'axe-rspec' RSpec.feature 'GetUspsProofingResultsJob Scenarios', js: true do + include PersonalKeyHelper include OidcAuthHelper include UspsIppHelper include ActiveJob::TestHelper @@ -14,9 +15,24 @@ ActiveJob::Base.queue_adapter = :test end + let(:pii) do + Pii::Attributes.new_from_hash( + { + ssn: '666-66-1234', + dob: '1920-01-01', + first_name: 'solaire', + }, + ) + end + let(:initial_password) { 'p@assword!' } + feature 'before/after password reset:' do background do - @user = create(:user, :with_phone, :with_pending_in_person_enrollment) + @user = create( + :user, :with_phone, :with_pending_in_person_enrollment, password: initial_password + ) + @personal_key = @user.pending_in_person_enrollment.profile.encrypt_pii(pii, initial_password) + @user.pending_in_person_enrollment.profile.save! @new_password = '$alty pickles' end @@ -39,29 +55,36 @@ expect(@user.in_person_enrollments.first).to have_attributes( status: 'pending', ) - # And the user has a Profile that is deactivated pending in person verification + # And the user has a Profile that is deactivated with reason "password_reset" expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, - deactivation_reason: nil, + deactivation_reason: 'password_reset', in_person_verification_pending_at: be_kind_of(Time), ) # When the user logs in login(@user, @new_password) - # Then the user is taken to the /verify/welcome page - expect(page).to have_current_path(idv_welcome_path) - # And the user has an InPersonEnrollment with status "cancelled" + # Then the user is taken to the /reactivate/account path + expect(page).to have_current_path(reactivate_account_path) + + # And the user has an InPersonEnrollment with status "pending" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'cancelled', + status: 'pending', ) - # And the user has a Profile that is deactivated with reason "encryption_error" + # And the user has a Profile that is deactivated with reason "password_reset" expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, - deactivation_reason: 'encryption_error', + deactivation_reason: 'password_reset', in_person_verification_pending_at: be_kind_of(Time), ) + # When the user reactivates their profile with a personal key + reactivate_profile(@new_password, @personal_key) + + # Then the user is taken to the /verify/in_person/ready_to_verify path + expect(page).to have_current_path(idv_in_person_ready_to_verify_path) + # When the user logs out logout(@user) # And the user visits USPS to complete their enrollment @@ -70,32 +93,22 @@ # And GetUspsProofingResultsJob is performed perform_get_usps_proofing_results_job(@user) - # Then the user has an InPersonEnrollment with status "cancelled" + # Then the user has an InPersonEnrollment with status "passed" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'cancelled', + status: 'passed', ) - # And the user has a Profile that is deactivated with reason "encryption_error" + # And the user has a Profile that is activated expect(@user.in_person_enrollments.first.profile).to have_attributes( - active: false, - deactivation_reason: 'encryption_error', - in_person_verification_pending_at: be_kind_of(Time), + active: true, + deactivation_reason: nil, + in_person_verification_pending_at: nil, ) # When the user logs in login(@user, @new_password) - # Then the user is taken to the /verify/welcome page - expect(page).to have_current_path(idv_welcome_path) - # And the user has an InPersonEnrollment with status "cancelled" - expect(@user.in_person_enrollments.first).to have_attributes( - status: 'cancelled', - ) - # And the user has a Profile that is deactivated with reason "encryption_error" - expect(@user.in_person_enrollments.first.profile).to have_attributes( - active: false, - deactivation_reason: 'encryption_error', - in_person_verification_pending_at: be_kind_of(Time), - ) + # Then the user is taken to the /sign_up/completed path + expect(page).to have_current_path(sign_up_completed_path) end ['failed', 'cancelled', 'expired'].each do |status| @@ -118,29 +131,36 @@ expect(@user.in_person_enrollments.first).to have_attributes( status: 'pending', ) - # And the user has a deactivated profile due to in person verification + # And the user has a Profile that is deactivated with reason "password_reset" expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, - deactivation_reason: nil, + deactivation_reason: 'password_reset', in_person_verification_pending_at: be_kind_of(Time), ) # When the user logs in login(@user, @new_password) - # Then the user is taken to the /verify/welcome page - expect(page).to have_current_path(idv_welcome_path) - # And the user has an InPersonEnrollment with status "cancelled" + # Then the user is taken to the /reactivate/account path + expect(page).to have_current_path(reactivate_account_path) + + # And the user has an InPersonEnrollment with status "pending" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'cancelled', + status: 'pending', ) - # And the user has a Profile that is deactivated with reason "encryption_error" + # And the user has a Profile that is deactivated with reason "password_reset" expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, - deactivation_reason: 'encryption_error', + deactivation_reason: 'password_reset', in_person_verification_pending_at: be_kind_of(Time), ) + # When the user reactivates their profile with a personal key + reactivate_profile(@new_password, @personal_key) + + # Then the user is taken to the /verify/in_person/ready_to_verify path + expect(page).to have_current_path(idv_in_person_ready_to_verify_path) + # When the user logs out logout(@user) # And the user visits USPS to complete their enrollment @@ -149,15 +169,15 @@ # And GetUspsProofingResultsJob is performed perform_get_usps_proofing_results_job(@user) - # Then the user has an InPersonEnrollment with status "cancelled" + # Then the user has an InPersonEnrollment with status "passed" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'cancelled', + status: status, ) - # And the user has a Profile that is deactivated with reason "encryption_error" + # And the user has a Profile that is deactivated expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, - deactivation_reason: 'encryption_error', - in_person_verification_pending_at: be_kind_of(Time), + deactivation_reason: 'verification_cancelled', + in_person_verification_pending_at: nil, ) # When the user logs in @@ -165,16 +185,6 @@ # Then the user is taken to the /verify/welcome page expect(page).to have_current_path(idv_welcome_path) - # And the user has an InPersonEnrollment with status "cancelled" - expect(@user.in_person_enrollments.first).to have_attributes( - status: 'cancelled', - ) - # And the user has a Profile that is deactivated with reason "encryption_error" - expect(@user.in_person_enrollments.first.profile).to have_attributes( - active: false, - deactivation_reason: 'encryption_error', - in_person_verification_pending_at: be_kind_of(Time), - ) end end @@ -197,10 +207,10 @@ expect(@user.in_person_enrollments.first).to have_attributes( status: 'pending', ) - # And the user has a deactivated profile due to in person verification + # And the user has a Profile that is deactivated with reason "password_reset" expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, - deactivation_reason: nil, + deactivation_reason: 'password_reset', in_person_verification_pending_at: be_kind_of(Time), ) @@ -210,35 +220,71 @@ # And GetUspsProofingResultsJob is performed perform_get_usps_proofing_results_job(@user) - # Then the user has an InPersonEnrollment with status "passed" + # Then the user has an InPersonEnrollment with status "pending" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'passed', + status: 'pending', ) - # And the user has a Profile that is active + # And the user has a Profile that is deactivated with reason "password_reset" expect(@user.in_person_enrollments.first.profile).to have_attributes( - active: true, - deactivation_reason: nil, - in_person_verification_pending_at: nil, + active: false, + deactivation_reason: 'password_reset', + in_person_verification_pending_at: be_kind_of(Time), ) # When the user logs in login(@user, @new_password) - # Then the user is taken to the /verify/welcome page - expect(page).to have_current_path(idv_welcome_path) - # And the user has an InPersonEnrollment with status "passed" + # Then the user is taken to the /reactivate/account path + expect(page).to have_current_path(reactivate_account_path) + + # And the user has an InPersonEnrollment with status "pending" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'passed', + status: 'pending', + ) + # And the user has a Profile that is deactivated with reason "password_reset" + expect(@user.in_person_enrollments.first.profile).to have_attributes( + active: false, + deactivation_reason: 'password_reset', + in_person_verification_pending_at: be_kind_of(Time), ) - # And the user has a Profile that is deactivated with reason "encryption_error" + + # When the user reactivates their profile with a personal key + reactivate_profile(@new_password, @personal_key) + + # Then the user is taken to the /verify/in_person/ready_to_verify path + expect(page).to have_current_path(idv_in_person_ready_to_verify_path) + + # And the user has an InPersonEnrollment with status "pending" + expect(@user.in_person_enrollments.first).to have_attributes( + status: 'pending', + ) + # And the user has a Profile that is deactivated with reason "password_reset" expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, - deactivation_reason: 'encryption_error', + deactivation_reason: nil, + in_person_verification_pending_at: be_kind_of(Time), + ) + + # When the enrollment is ready to be picked by the GetUspsProofingResultsJob + @user.in_person_enrollments.first.update!(last_batch_claimed_at: nil) + # And USPS enrollment "passed" + stub_request_passed_proofing_results + # And GetUspsProofingResultsJob is performed + perform_get_usps_proofing_results_job(@user) + + # Then the user has an InPersonEnrollment with status "passed" + expect(@user.in_person_enrollments.first).to have_attributes( + status: 'passed', + ) + # And the user has a Profile that is activated + expect(@user.in_person_enrollments.first.profile).to have_attributes( + active: true, + deactivation_reason: nil, in_person_verification_pending_at: nil, ) end - ['failed', 'cancelled', 'expired'].each do |status| + ['failed'].each do |status| scenario "User resets password without logging in before USPS proofing \"#{status}\"" do # Given the user has an InPersonEnrollment with status "pending" expect(@user.in_person_enrollments.first).to have_attributes( @@ -258,10 +304,10 @@ expect(@user.in_person_enrollments.first).to have_attributes( status: 'pending', ) - # And the user has a Profile that is deactivated pending in person verification + # And the user has a Profile that is deactivated with reason "password_reset" expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, - deactivation_reason: nil, + deactivation_reason: 'password_reset', in_person_verification_pending_at: be_kind_of(Time), ) @@ -271,7 +317,59 @@ # And GetUspsProofingResultsJob is performed perform_get_usps_proofing_results_job(@user) - # Then the user has an InPersonEnrollment with status "failed|cancelled|expired" + # Then the user has an InPersonEnrollment with status "pending" + expect(@user.in_person_enrollments.first).to have_attributes( + status: 'pending', + ) + # And the user has a Profile that is deactivated with reason "password_reset" + expect(@user.in_person_enrollments.first.profile).to have_attributes( + active: false, + deactivation_reason: 'password_reset', + in_person_verification_pending_at: be_kind_of(Time), + ) + + # When the user logs in + login(@user, @new_password) + + # Then the user is taken to the /reactivate/account path + expect(page).to have_current_path(reactivate_account_path) + + # And the user has an InPersonEnrollment with status "pending" + expect(@user.in_person_enrollments.first).to have_attributes( + status: 'pending', + ) + # And the user has a Profile that is deactivated with reason "password_reset" + expect(@user.in_person_enrollments.first.profile).to have_attributes( + active: false, + deactivation_reason: 'password_reset', + in_person_verification_pending_at: be_kind_of(Time), + ) + + # When the user reactivates their profile with a personal key + reactivate_profile(@new_password, @personal_key) + + # Then the user is taken to the /verify/in_person/ready_to_verify path + expect(page).to have_current_path(idv_in_person_ready_to_verify_path) + + # And the user has an InPersonEnrollment with status "pending" + expect(@user.in_person_enrollments.first).to have_attributes( + status: 'pending', + ) + # And the user has a Profile that is deactivated with reason "password_reset" + expect(@user.in_person_enrollments.first.profile).to have_attributes( + active: false, + deactivation_reason: nil, + in_person_verification_pending_at: be_kind_of(Time), + ) + + # When the enrollment is ready to be picked by the GetUspsProofingResultsJob + @user.in_person_enrollments.first.update!(last_batch_claimed_at: nil) + # And USPS enrollment "failed|cancelled|expired" + stub_request_proofing_results(status, @user.in_person_enrollments.first.enrollment_code) + # And GetUspsProofingResultsJob is performed + perform_get_usps_proofing_results_job(@user) + + # And the user has an InPersonEnrollment with status "pending" expect(@user.in_person_enrollments.first).to have_attributes( status: status, ) @@ -281,20 +379,67 @@ deactivation_reason: 'verification_cancelled', in_person_verification_pending_at: nil, ) + end + end + + ['cancelled', 'expired'].each do |status| + scenario "User resets password without logging in before USPS proofing \"#{status}\"" do + # Given the user has an InPersonEnrollment with status "pending" + expect(@user.in_person_enrollments.first).to have_attributes( + status: 'pending', + ) + # And the user has a Profile that is deactivated pending in person verification + expect(@user.in_person_enrollments.first.profile).to have_attributes( + active: false, + deactivation_reason: nil, + in_person_verification_pending_at: be_kind_of(Time), + ) + + # When the user resets their password + reset_password(@user, @new_password) + + # Then the user has an InPersonEnrollment with status "pending" + expect(@user.in_person_enrollments.first).to have_attributes( + status: 'pending', + ) + # And the user has a Profile that is deactivated with reason "password_reset" + expect(@user.in_person_enrollments.first.profile).to have_attributes( + active: false, + deactivation_reason: 'password_reset', + in_person_verification_pending_at: be_kind_of(Time), + ) + + # And the user visits USPS to complete their enrollment + # And USPS enrollment "failed|cancelled|expired" + stub_request_proofing_results(status, @user.in_person_enrollments.first.enrollment_code) + # And GetUspsProofingResultsJob is performed + perform_get_usps_proofing_results_job(@user) + + # Then the user has an InPersonEnrollment with status "cancelled|expired" + expect(@user.in_person_enrollments.first).to have_attributes( + status: status, + ) + # And the user has a Profile that is deactivated with reason "password_reset" + expect(@user.in_person_enrollments.first.profile).to have_attributes( + active: false, + deactivation_reason: 'password_reset', + in_person_verification_pending_at: nil, + ) # When the user logs in login(@user, @new_password) - # Then the user is taken to the /verify/welcome page + # Then the user is taken to the /verify/welcome path expect(page).to have_current_path(idv_welcome_path) - # And the user has an InPersonEnrollment with status "failed|cancelled|expired" + + # Then the user has an InPersonEnrollment with status "cancelled|expired" expect(@user.in_person_enrollments.first).to have_attributes( status: status, ) - # And the user has a Profile that is deactivated with reason "verification_cancelled" + # And the user has a Profile that is deactivated with reason "password_reset" expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, - deactivation_reason: 'verification_cancelled', + deactivation_reason: 'password_reset', in_person_verification_pending_at: nil, ) end @@ -348,8 +493,8 @@ # Then the user is taken to the /account/reactivate/start page expect(page).to have_current_path(reactivate_account_path) - # When the user attempts to reactivate account without their personal key - account_reactivation_with_personal_key(@user, @new_password) + # When the user reactivates their profile with a personal key + reactivate_profile(@new_password, @personal_key) # Then the user is taken to the /sign_up/completed page expect(page).to have_current_path(sign_up_completed_path) @@ -496,7 +641,6 @@ User resets password and logs in before USPS proofing "passed" with fraud review pending EOS @user.in_person_enrollments.first.profile.update( - fraud_review_pending_at: 1.day.ago, fraud_pending_reason: 'threatmetrix_review', proofing_components: { threatmetrix_review_status: 'review' }, ) @@ -512,7 +656,6 @@ deactivation_reason: nil, in_person_verification_pending_at: be_kind_of(Time), fraud_pending_reason: 'threatmetrix_review', - fraud_review_pending_at: be_kind_of(Time), ) # When the user resets their password @@ -526,29 +669,33 @@ # fraud review expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, - deactivation_reason: nil, + deactivation_reason: 'password_reset', in_person_verification_pending_at: be_kind_of(Time), fraud_pending_reason: 'threatmetrix_review', - fraud_review_pending_at: be_kind_of(Time), ) # When the user logs in login(@user, @new_password) - # Then the user is taken to the /verify/welcome page - expect(page).to have_current_path(idv_welcome_path) - # And the user has an InPersonEnrollment with status "cancelled" + # Then the user is taken to the /reactivate/account path + expect(page).to have_current_path(reactivate_account_path) + + # When the user reactivates their profile with a personal key + reactivate_profile(@new_password, @personal_key) + + # Then the user is taken to the /verify/in_person/ready_to_verify + expect(page).to have_current_path(idv_in_person_ready_to_verify_path) + # And the user has an InPersonEnrollment with status "pending" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'cancelled', + status: 'pending', ) # And the user has a Profile that is deactivated with reason "encryption_error" and # pending in person verification and fraud review expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, - deactivation_reason: 'encryption_error', + deactivation_reason: nil, in_person_verification_pending_at: be_kind_of(Time), fraud_pending_reason: 'threatmetrix_review', - fraud_review_pending_at: be_kind_of(Time), ) # When the user logs out @@ -561,14 +708,15 @@ # Then the user has an InPersonEnrollment with status "cancelled" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'cancelled', + status: 'passed', ) + # And the user has a Profile that is deactivated with reason "encryption_error" and # pending fraud review expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, - deactivation_reason: 'encryption_error', - in_person_verification_pending_at: be_kind_of(Time), + deactivation_reason: nil, + in_person_verification_pending_at: nil, fraud_pending_reason: 'threatmetrix_review', fraud_review_pending_at: be_kind_of(Time), ) @@ -576,18 +724,17 @@ # When the user logs in login(@user, @new_password) - # Then the user is taken to the /verify/welcome page - expect(page).to have_current_path(idv_welcome_path) + # Then the user is taken to the /verify/please_call page + expect(page).to have_current_path(idv_please_call_path) # And the user has an InPersonEnrollment with status "cancelled" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'cancelled', + status: 'passed', ) - # And the user has a Profile that is deactivated with reason "encryption_error" and - # pending fraud review + # And the user has a Profile that is pending fraud review expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, - deactivation_reason: 'encryption_error', - in_person_verification_pending_at: be_kind_of(Time), + deactivation_reason: nil, + in_person_verification_pending_at: nil, fraud_pending_reason: 'threatmetrix_review', fraud_review_pending_at: be_kind_of(Time), ) @@ -597,7 +744,6 @@ User resets password without logging in before USPS proofing "passed" with fraud review pending EOS @user.in_person_enrollments.first.profile.update( - fraud_review_pending_at: 1.day.ago, fraud_pending_reason: 'threatmetrix_review', proofing_components: { threatmetrix_review_status: 'review' }, ) @@ -613,7 +759,6 @@ deactivation_reason: nil, in_person_verification_pending_at: be_kind_of(Time), fraud_pending_reason: 'threatmetrix_review', - fraud_review_pending_at: be_kind_of(Time), ) # When the user resets their password @@ -627,10 +772,9 @@ # fraud review expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, - deactivation_reason: nil, + deactivation_reason: 'password_reset', in_person_verification_pending_at: be_kind_of(Time), fraud_pending_reason: 'threatmetrix_review', - fraud_review_pending_at: be_kind_of(Time), ) # And the user visits USPS to complete their enrollment @@ -641,35 +785,66 @@ # Then the user has an InPersonEnrollment with status "passed" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'passed', + status: 'pending', ) - # And the user has a Profile that is deactivated pending fraud review + # And the user has a Profile that is deactivated pending in person verification and + # fraud review expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, - deactivation_reason: nil, - in_person_verification_pending_at: nil, + deactivation_reason: 'password_reset', + in_person_verification_pending_at: be_kind_of(Time), fraud_pending_reason: 'threatmetrix_review', - fraud_review_pending_at: be_kind_of(Time), ) # When the user logs in login(@user, @new_password) - # Then the user is taken to the /verify/welcome page - expect(page).to have_current_path(idv_welcome_path) - # And the user has an InPersonEnrollment with status "passed" + # Then the user is taken to the /reactivate/account path + expect(page).to have_current_path(reactivate_account_path) + + # When the user reactivates their profile with a personal key + reactivate_profile(@new_password, @personal_key) + + # Then the user is taken to the /verify/in_person/ready_to_verify + expect(page).to have_current_path(idv_in_person_ready_to_verify_path) + # And the user has an InPersonEnrollment with status "pending" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'passed', + status: 'pending', ) # And the user has a Profile that is deactivated with reason "encryption_error" and - # pending fraud review + # pending in person verification and fraud review + expect(@user.in_person_enrollments.first.profile).to have_attributes( + active: false, + deactivation_reason: nil, + in_person_verification_pending_at: be_kind_of(Time), + fraud_pending_reason: 'threatmetrix_review', + ) + + # When the enrollment is ready to be picked by the GetUspsProofingResultsJob + @user.in_person_enrollments.first.update!(last_batch_claimed_at: nil) + # And USPS enrollment "passed" + stub_request_passed_proofing_results + # And GetUspsProofingResultsJob is performed + perform_get_usps_proofing_results_job(@user) + + # Then the user has an InPersonEnrollment with status "cancelled" + expect(@user.in_person_enrollments.first).to have_attributes( + status: 'passed', + ) + # And the user has a Profile that is pending fraud review expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, - deactivation_reason: 'encryption_error', + deactivation_reason: nil, in_person_verification_pending_at: nil, fraud_pending_reason: 'threatmetrix_review', fraud_review_pending_at: be_kind_of(Time), ) + + # When the page is refreshed + page.refresh + + # Then the user is taken to the /verify/please_call page + expect(page).to have_current_path(idv_please_call_path) end end @@ -692,17 +867,6 @@ def account_reactivation_without_personal_key click_on t('forms.buttons.continue') end - def account_reactivation_with_personal_key(user, password) - personal_key = user.personal_key.gsub(/\W/, '') - click_on t('links.account.reactivate.with_key') - fill_in t('forms.personal_key.confirmation_label'), with: personal_key - click_on t('forms.buttons.continue') - fill_in t('idv.form.password'), with: password - click_on t('forms.buttons.continue') - check t('forms.personal_key.required_checkbox') - click_on t('forms.buttons.continue') - end - def login(user, password) user.password = password sign_in_live_with_2fa(user) @@ -736,4 +900,24 @@ def stub_request_proofing_results(status, enrollment_code) throw "Status: #{status} not configured" end end + + def reactivate_profile(password, personal_key) + click_on t('links.account.reactivate.with_key') + + expect(page).to have_current_path verify_personal_key_path + + fill_in 'personal_key', with: personal_key + click_continue + + expect(page).to have_current_path verify_password_path + + fill_in 'Password', with: password + click_continue + + expect(page).to have_current_path(manage_personal_key_path) + click_continue + + check t('forms.personal_key.required_checkbox') + click_continue + end end diff --git a/spec/features/idv/pending_profile_password_reset_spec.rb b/spec/features/idv/pending_profile_password_reset_spec.rb index e05499d4111..84c6e330d39 100644 --- a/spec/features/idv/pending_profile_password_reset_spec.rb +++ b/spec/features/idv/pending_profile_password_reset_spec.rb @@ -31,7 +31,7 @@ expect(user.reload.active_or_pending_profile).to be_nil end - scenario 'while in-person pending requires the user to reproof' do + scenario 'while in-person pending prompts user for personal key' do user = create(:user, :with_phone, :with_pending_in_person_enrollment) visit_idp_from_ial2_oidc_sp @@ -49,8 +49,7 @@ user.password = new_password sign_in_live_with_2fa(user) - expect(page).to have_content(t('doc_auth.headings.welcome', sp_name: sp_name)) - expect(page).to have_current_path(idv_welcome_path) + expect(page).to have_current_path(reactivate_account_path) expect(user.reload.active_or_pending_profile).to be_nil end diff --git a/spec/features/new_device_tracking_spec.rb b/spec/features/new_device_tracking_spec.rb index d35f6e14f39..bd9c70ec4a0 100644 --- a/spec/features/new_device_tracking_spec.rb +++ b/spec/features/new_device_tracking_spec.rb @@ -27,7 +27,7 @@ # Notified after expired delay for successful email password, but incomplete MFA travel_to 6.minutes.from_now do - CreateNewDeviceAlert.new.perform(Time.zone.now) + CreateNewDeviceAlertJob.new.perform(Time.zone.now) open_last_email email_page = Capybara::Node::Simple.new(current_email.default_part_body) expect(email_page).to have_css( @@ -47,7 +47,7 @@ # Notified after session expired, user returned for another successful email password, no MFA travel_to 22.minutes.from_now do - CreateNewDeviceAlert.new.perform(Time.zone.now) + CreateNewDeviceAlertJob.new.perform(Time.zone.now) open_last_email email_page = Capybara::Node::Simple.new(current_email.default_part_body) expect(email_page).to have_css( diff --git a/spec/features/openid_connect/authorization_confirmation_spec.rb b/spec/features/openid_connect/authorization_confirmation_spec.rb index 8986fec0c5e..3212770fd1a 100644 --- a/spec/features/openid_connect/authorization_confirmation_spec.rb +++ b/spec/features/openid_connect/authorization_confirmation_spec.rb @@ -82,6 +82,40 @@ def create_user_and_remember_device it_behaves_like 'signing in with a different email prompts with the shared email' end + + context 'with requested attributes contains only email' do + it ' creates an identity with proper email_address_id' do + user = user_with_2fa + + sign_in_oidc_user(user) + check t('forms.messages.remember_device') + fill_in_code_with_last_phone_otp + click_submit_default + click_agree_and_continue + identity = user.identities.find_by(service_provider: OidcAuthHelper::OIDC_IAL1_ISSUER) + email_id = user.email_addresses.first.id + expect(identity.email_address_id).to eq(email_id) + end + end + + context 'with requested attributes contains is emails and all_emails' do + it 'creates an identity with no email_address_id saved' do + user = user_with_2fa + + params = ial1_params + params[:scope] = 'openid email all_emails' + oidc_path = openid_connect_authorize_path params + visit oidc_path + fill_in_credentials_and_submit(user.email, user.password) + click_submit_default + check t('forms.messages.remember_device') + fill_in_code_with_last_phone_otp + click_submit_default + click_agree_and_continue + identity = user.identities.find_by(service_provider: OidcAuthHelper::OIDC_IAL1_ISSUER) + expect(identity.email_address_id).to eq(nil) + end + end end context 'when email sharing feature is disabled' do diff --git a/spec/features/saml/authorization_confirmation_spec.rb b/spec/features/saml/authorization_confirmation_spec.rb index 34c59fb6515..553820bd0ab 100644 --- a/spec/features/saml/authorization_confirmation_spec.rb +++ b/spec/features/saml/authorization_confirmation_spec.rb @@ -52,6 +52,46 @@ def create_user_and_remember_device continue_as(shared_email) expect(current_url).to eq(complete_saml_url) end + + context 'with requested attributes contains only email' do + it ' creates an identity with proper email_address_id' do + user = user_with_2fa + + sign_in_user(user) + check t('forms.messages.remember_device') + fill_in_code_with_last_phone_otp + click_submit_default + visit request_url + click_agree_and_continue + click_submit_default + visit sign_out_url + identity = user.identities.find_by(service_provider: SamlAuthHelper::SP_ISSUER) + email_id = user.email_addresses.first.id + expect(identity.email_address_id).to eq(email_id) + end + end + + context 'with requested attributes contains is emails and all_emails' do + before do + allow_any_instance_of(ServiceProviderIdentity).to receive(:verified_attributes) + .and_return(%w[email all_emails]) + end + it 'creates an identity with no email_address_id saved' do + user = user_with_2fa + + sign_in_user(user) + check t('forms.messages.remember_device') + fill_in_code_with_last_phone_otp + click_submit_default + visit request_url + click_agree_and_continue + click_submit_default + visit sign_out_url + + identity = user.identities.find_by(service_provider: SamlAuthHelper::SP_ISSUER) + expect(identity.email_address_id).to eq(nil) + end + end end context 'when email sharing feature is disabled' do diff --git a/spec/features/two_factor_authentication/sign_in_spec.rb b/spec/features/two_factor_authentication/sign_in_spec.rb index 075dc608440..85586b025c8 100644 --- a/spec/features/two_factor_authentication/sign_in_spec.rb +++ b/spec/features/two_factor_authentication/sign_in_spec.rb @@ -104,7 +104,7 @@ reset_email travel_to (IdentityConfig.store.account_reset_wait_period_days + 1).days.from_now do - AccountReset::GrantRequestsAndSendEmails.new.perform(Time.zone.today) + GrantAccountResetRequestsAndSendEmailsJob.new.perform(Time.zone.today) open_last_email click_email_link_matching(/delete_account\?token/) click_button t('account_reset.request.yes_continue') diff --git a/spec/forms/openid_connect_authorize_form_spec.rb b/spec/forms/openid_connect_authorize_form_spec.rb index 75db077c746..a8d2b9faaf5 100644 --- a/spec/forms/openid_connect_authorize_form_spec.rb +++ b/spec/forms/openid_connect_authorize_form_spec.rb @@ -66,7 +66,7 @@ it 'is unsuccessful and has error messages' do expect(result.to_h).to eq( success: false, - errors: { response_type: ['is not included in the list'] }, + errors: nil, error_details: { response_type: { inclusion: true } }, client_id: client_id, prompt: 'select_account', diff --git a/spec/forms/openid_connect_token_form_spec.rb b/spec/forms/openid_connect_token_form_spec.rb index 9283dbc7218..19e26d0e068 100644 --- a/spec/forms/openid_connect_token_form_spec.rb +++ b/spec/forms/openid_connect_token_form_spec.rb @@ -395,7 +395,7 @@ expect(submission.to_h).to include( success: false, - errors: form.errors.messages, + errors: nil, error_details: hash_including(*form.errors.attribute_names), client_id: nil, user_id: nil, @@ -412,7 +412,7 @@ expect(submission.to_h).to include( success: false, - errors: form.errors.messages, + errors: nil, error_details: hash_including(:grant_type), client_id: client_id, user_id: user.uuid, diff --git a/spec/forms/otp_delivery_selection_form_spec.rb b/spec/forms/otp_delivery_selection_form_spec.rb index c589d34ce5e..532b9bdbcc3 100644 --- a/spec/forms/otp_delivery_selection_form_spec.rb +++ b/spec/forms/otp_delivery_selection_form_spec.rb @@ -41,11 +41,6 @@ context 'when the form is invalid' do it 'returns false for success? and includes errors' do - errors = { - otp_delivery_preference: ['is not included in the list'], - phone: ['Please fill in this field.'], - } - extra = { otp_delivery_preference: 'foo', resend: false, @@ -62,8 +57,11 @@ expect(subject.submit(otp_delivery_preference: 'foo').to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { + otp_delivery_preference: { inclusion: true }, + phone: { blank: true }, + }, **extra, ) end diff --git a/spec/forms/password_reset_email_form_spec.rb b/spec/forms/password_reset_email_form_spec.rb index 6b1f24e89aa..2e9e4a3e88e 100644 --- a/spec/forms/password_reset_email_form_spec.rb +++ b/spec/forms/password_reset_email_form_spec.rb @@ -39,12 +39,10 @@ it 'returns hash with properties about the event and the nonexistent user' do subject = PasswordResetEmailForm.new('invalid') - errors = { email: [t('valid_email.validations.email.invalid')] } - expect(subject.submit.to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { email: { invalid: true } }, user_id: 'nonexistent-uuid', confirmed: false, active_profile: false, @@ -53,12 +51,11 @@ it 'returns false and adds errors to the form object when domain is invalid' do subject = PasswordResetEmailForm.new('test@çà.com') - errors = { email: [t('valid_email.validations.email.invalid')] } expect(subject.submit.to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { email: { domain: true } }, user_id: 'nonexistent-uuid', confirmed: false, active_profile: false, diff --git a/spec/forms/personal_key_form_spec.rb b/spec/forms/personal_key_form_spec.rb index 5a49b7dbc07..0128b60dc02 100644 --- a/spec/forms/personal_key_form_spec.rb +++ b/spec/forms/personal_key_form_spec.rb @@ -21,14 +21,13 @@ context 'when the form is invalid' do it 'returns FormResponse with success: false' do user = create(:user, :fully_registered, personal_key: 'code') - errors = { personal_key: ['Incorrect personal key'] } form = PersonalKeyForm.new(user, 'foo') expect(form.submit.to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { personal_key: { personal_key_incorrect: true } }, ) expect(user.encrypted_recovery_code_digest).to_not be_nil expect(form.personal_key).to be_nil diff --git a/spec/forms/register_user_email_form_spec.rb b/spec/forms/register_user_email_form_spec.rb index 5d8ec17bac6..8f99a8da5d5 100644 --- a/spec/forms/register_user_email_form_spec.rb +++ b/spec/forms/register_user_email_form_spec.rb @@ -280,7 +280,6 @@ context 'when email is invalid' do it 'returns false and adds errors to the form object' do invalid_email = 'invalid_email' - errors = { email: [t('valid_email.validations.email.invalid')] } extra = { email_already_exists: false, @@ -291,16 +290,14 @@ expect(subject.submit(email: invalid_email, terms_accepted: '1').to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { email: { invalid: true } }, **extra, ) expect_delivered_email_count(0) end it 'returns false and adds errors to the form object when domain is invalid' do - errors = { email: [t('valid_email.validations.email.invalid')] } - extra = { email_already_exists: false, rate_limited: false, @@ -310,8 +307,8 @@ expect(subject.submit(email: 'test@çà.com', terms_accepted: '1').to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { email: { domain: true } }, **extra, ) expect_delivered_email_count(0) @@ -321,7 +318,6 @@ blocked_domain = 'blocked.com' blocked_email = 'test@' + blocked_domain email_address = create(:email_address, email: blocked_email) - errors = { email: [t('valid_email.validations.email.invalid')] } allow(BanDisposableEmailValidator).to receive(:config).and_return([blocked_domain]) extra = { @@ -333,8 +329,8 @@ expect(subject.submit(email: blocked_email, terms_accepted: '1').to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { email: { t('valid_email.validations.email.invalid') => true } }, **extra, ) expect_delivered_email_count(0) @@ -344,13 +340,12 @@ blocked_domain = 'blocked.com' blocked_email = 'test@sub.' + blocked_domain - errors = { email: [t('valid_email.validations.email.invalid')] } expect(BanDisposableEmailValidator).to receive(:config).and_return([blocked_domain]) expect(subject.submit(email: blocked_email, terms_accepted: '1').to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { email: { t('valid_email.validations.email.invalid') => true } }, email_already_exists: false, rate_limited: false, user_id: 'anonymous-uuid', @@ -389,7 +384,7 @@ expect(result.to_h).to eq( success: false, - errors: { terms_accepted: [t('errors.registration.terms')] }, + errors: nil, error_details: { terms_accepted: { terms: true } }, email_already_exists: false, rate_limited: false, diff --git a/spec/forms/reset_password_form_spec.rb b/spec/forms/reset_password_form_spec.rb index f79924ccc1e..ecf02aee0fb 100644 --- a/spec/forms/reset_password_form_spec.rb +++ b/spec/forms/reset_password_form_spec.rb @@ -18,179 +18,443 @@ describe '#submit' do subject(:result) { form.submit(params) } - context 'when the password is valid but the token has expired' do + context 'when pending in person password reset enabled' do before do - allow(user).to receive(:reset_password_period_valid?).and_return(false) + allow(FeatureManagement).to receive( + :pending_in_person_password_reset_enabled?, + ).and_return(true) end - it 'returns a hash with errors' do - expect(result.to_h).to eq( - success: false, - errors: { reset_password_token: ['token_expired'] }, - error_details: { reset_password_token: { token_expired: true } }, - user_id: '123', - profile_deactivated: false, - pending_profile_invalidated: false, - pending_profile_pending_reasons: '', - ) + context 'when the password is valid but the token has expired' do + before do + allow(user).to receive(:reset_password_period_valid?).and_return(false) + end + + it 'returns a hash with errors' do + expect(result.to_h).to eq( + success: false, + errors: nil, + error_details: { reset_password_token: { token_expired: true } }, + user_id: '123', + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: '', + ) + end end - end - context 'when the password is invalid and token is valid' do - let(:password) { 'invalid' } + context 'when the password is invalid and token is valid' do + let(:password) { 'invalid' } - before do - allow(user).to receive(:reset_password_period_valid?).and_return(true) - end - - it 'returns a hash with errors' do - expect(result.to_h).to eq( - success: false, - errors: { - password: - ["Password must be at least #{Devise.password_length.first} characters long"], - password_confirmation: [I18n.t( - 'errors.messages.too_short', - count: Devise.password_length.first, - )], - }, - error_details: { - password: { too_short: true }, - password_confirmation: { too_short: true }, - }, - user_id: '123', - profile_deactivated: false, - pending_profile_invalidated: false, - pending_profile_pending_reasons: '', - ) + before do + allow(user).to receive(:reset_password_period_valid?).and_return(true) + end + + it 'returns a hash with errors' do + expect(result.to_h).to eq( + success: false, + errors: nil, + error_details: { + password: { too_short: true }, + password_confirmation: { too_short: true }, + }, + user_id: '123', + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: '', + ) + end + end + + context 'when both the password and token are valid' do + before do + allow(user).to receive(:reset_password_period_valid?).and_return(true) + end + + it 'sets the user password to the submitted password' do + expect { result }.to change { user.reload.encrypted_password_digest } + + expect(result.to_h).to eq( + success: true, + errors: nil, + user_id: '123', + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: '', + ) + end + end + + context 'when both the password and token are invalid' do + let(:password) { 'short' } + + before do + allow(user).to receive(:reset_password_period_valid?).and_return(false) + end + + it 'returns a hash with errors' do + expect(result.to_h).to eq( + success: false, + errors: nil, + error_details: { + password: { too_short: true }, + password_confirmation: { too_short: true }, + reset_password_token: { token_expired: true }, + }, + user_id: '123', + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: '', + ) + end + end + + context 'when the user does not exist in the db' do + let(:user) { User.new } + + it 'returns a hash with errors' do + expect(result.to_h).to eq( + success: false, + errors: nil, + error_details: { reset_password_token: { invalid_token: true } }, + user_id: nil, + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: '', + ) + end + end + + context 'when the user has an active profile' do + let(:user) { create(:user, :proofed, reset_password_sent_at: Time.zone.now) } + + it 'deactivates the profile' do + expect(result.success?).to eq(true) + expect(result.extra[:profile_deactivated]).to eq(true) + expect(user.profiles.any?(&:active?)).to eq(false) + end end + + context 'when the user does not have an active profile' do + let(:user) { create(:user, reset_password_sent_at: Time.zone.now) } + + it 'includes that the profile was not deactivated in the form response' do + expect(result.success?).to eq(true) + expect(result.extra[:profile_deactivated]).to eq(false) + end + end + + context 'when the user has a pending profile' do + context 'when the profile is pending gpo verification' do + let!(:user) { create(:user, reset_password_sent_at: Time.zone.now) } + let!(:profile) do + create(:profile, :verify_by_mail_pending, user: user) + end + + before do + @result = form.submit(params) + profile.reload + end + + it 'includes that the profile was not deactivated in the form response' do + expect(result.success?).to eq(true) + expect(result.extra[:pending_profile_invalidated]).to eq(true) + expect(result.extra[:pending_profile_pending_reasons]).to eq( + 'gpo_verification_pending', + ) + end + end + + context 'when the profile is pending in person verification' do + let!(:user) { create(:user, reset_password_sent_at: Time.zone.now) } + let!(:profile) { create(:profile, :in_person_verification_pending, user: user) } + + before do + @result = form.submit(params) + profile.reload + end + + it 'returns a successful response' do + expect(@result.success?).to eq(true) + end + + it 'includes that the profile was not deactivated in the form response' do + expect(@result.extra).to include( + user_id: user.uuid, + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: 'in_person_verification_pending', + ) + end + + it 'updates the profile to have a "password reset" deactivation reason' do + expect(profile.deactivation_reason).to eq('password_reset') + end + end + + context 'when the user has an active and a pending in-person verification profile' do + let!(:user) { create(:user, reset_password_sent_at: Time.zone.now) } + let!(:pending_profile) { create(:profile, :in_person_verification_pending, user: user) } + let!(:active_profile) { create(:profile, :active, user: user) } + + before do + @result = form.submit(params) + pending_profile.reload + active_profile.reload + end + + it 'returns a successful response' do + expect(@result.success?).to eq(true) + end + + it 'includes that the profile was not deactivated in the form response' do + expect(@result.extra).to include( + user_id: user.uuid, + profile_deactivated: true, + pending_profile_invalidated: false, + pending_profile_pending_reasons: '', + ) + end + + it 'updates the pending profile to have a "password reset" deactivation reason' do + expect(pending_profile.deactivation_reason).to eq('password_reset') + end + + it 'does not update the active profile to have a "password reset" deactivation reason' do + expect(active_profile.deactivation_reason).to be_nil + end + end + end + + context 'when the user does not have a pending profile' do + let(:user) { create(:user, reset_password_sent_at: Time.zone.now) } + + it 'includes that the profile was not deactivated in the form response' do + expect(result.success?).to eq(true) + expect(result.extra[:pending_profile_invalidated]).to eq(false) + expect(result.extra[:pending_profile_pending_reasons]).to eq('') + end + end + + context 'when the unconfirmed email address has been confirmed by another account' do + let(:user) { create(:user, :unconfirmed, reset_password_sent_at: Time.zone.now) } + + before do + create( + :user, + email_addresses: [create(:email_address, email: user.email_addresses.first.email)], + ) + end + + it 'does not raise an error and is not successful' do + expect(result.success?).to eq(false) + expect(result.errors).to eq({ reset_password_token: ['token_expired'] }) + end + end + + it_behaves_like 'strong password', 'ResetPasswordForm' end - context 'when both the password and token are valid' do + context 'when pending in person password reset disabled' do before do - allow(user).to receive(:reset_password_period_valid?).and_return(true) + allow(FeatureManagement).to receive( + :pending_in_person_password_reset_enabled?, + ).and_return(false) end - it 'sets the user password to the submitted password' do - expect { result }.to change { user.reload.encrypted_password_digest } + context 'when the password is valid but the token has expired' do + before do + allow(user).to receive(:reset_password_period_valid?).and_return(false) + end - expect(result.to_h).to eq( - success: true, - errors: nil, - user_id: '123', - profile_deactivated: false, - pending_profile_invalidated: false, - pending_profile_pending_reasons: '', - ) + it 'returns a hash with errors' do + expect(result.to_h).to eq( + success: false, + errors: nil, + error_details: { reset_password_token: { token_expired: true } }, + user_id: '123', + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: '', + ) + end end - end - context 'when both the password and token are invalid' do - let(:password) { 'short' } + context 'when the password is invalid and token is valid' do + let(:password) { 'invalid' } - before do - allow(user).to receive(:reset_password_period_valid?).and_return(false) - end - - it 'returns a hash with errors' do - expect(result.to_h).to eq( - success: false, - errors: { - password: [ - t('errors.attributes.password.too_short.other', count: Devise.password_length.first), - ], - password_confirmation: [ - t('errors.messages.too_short', count: Devise.password_length.first), - ], - reset_password_token: ['token_expired'], - }, - error_details: { - password: { too_short: true }, - password_confirmation: { too_short: true }, - reset_password_token: { token_expired: true }, - }, - user_id: '123', - profile_deactivated: false, - pending_profile_invalidated: false, - pending_profile_pending_reasons: '', - ) + before do + allow(user).to receive(:reset_password_period_valid?).and_return(true) + end + + it 'returns a hash with errors' do + expect(result.to_h).to eq( + success: false, + errors: nil, + error_details: { + password: { too_short: true }, + password_confirmation: { too_short: true }, + }, + user_id: '123', + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: '', + ) + end end - end - context 'when the user does not exist in the db' do - let(:user) { User.new } + context 'when both the password and token are valid' do + before do + allow(user).to receive(:reset_password_period_valid?).and_return(true) + end - it 'returns a hash with errors' do - expect(result.to_h).to eq( - success: false, - errors: { reset_password_token: ['invalid_token'] }, - error_details: { reset_password_token: { invalid_token: true } }, - user_id: nil, - profile_deactivated: false, - pending_profile_invalidated: false, - pending_profile_pending_reasons: '', - ) + it 'sets the user password to the submitted password' do + expect { result }.to change { user.reload.encrypted_password_digest } + + expect(result.to_h).to eq( + success: true, + errors: nil, + user_id: '123', + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: '', + ) + end end - end - context 'when the user has an active profile' do - let(:user) { create(:user, :proofed, reset_password_sent_at: Time.zone.now) } + context 'when both the password and token are invalid' do + let(:password) { 'short' } - it 'deactivates the profile' do - expect(result.success?).to eq(true) - expect(result.extra[:profile_deactivated]).to eq(true) - expect(user.profiles.any?(&:active?)).to eq(false) + before do + allow(user).to receive(:reset_password_period_valid?).and_return(false) + end + + it 'returns a hash with errors' do + expect(result.to_h).to eq( + success: false, + errors: nil, + error_details: { + password: { too_short: true }, + password_confirmation: { too_short: true }, + reset_password_token: { token_expired: true }, + }, + user_id: '123', + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: '', + ) + end end - end - context 'when the user does not have an active profile' do - let(:user) { create(:user, reset_password_sent_at: Time.zone.now) } + context 'when the user does not exist in the db' do + let(:user) { User.new } - it 'includes that the profile was not deactivated in the form response' do - expect(result.success?).to eq(true) - expect(result.extra[:profile_deactivated]).to eq(false) + it 'returns a hash with errors' do + expect(result.to_h).to eq( + success: false, + errors: nil, + error_details: { reset_password_token: { invalid_token: true } }, + user_id: nil, + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: '', + ) + end end - end - context 'when the user has a pending profile' do - let(:profile) { create(:profile, :verify_by_mail_pending, :in_person_verification_pending) } - let(:user) { create(:user, reset_password_sent_at: Time.zone.now, profiles: [profile]) } + context 'when the user has an active profile' do + let(:user) { create(:user, :proofed, reset_password_sent_at: Time.zone.now) } - it 'includes that the profile was not deactivated in the form response' do - expect(result.success?).to eq(true) - expect(result.extra[:pending_profile_invalidated]).to eq(true) - expect(result.extra[:pending_profile_pending_reasons]).to eq( - 'gpo_verification_pending,in_person_verification_pending', - ) + it 'deactivates the profile' do + expect(result.success?).to eq(true) + expect(result.extra[:profile_deactivated]).to eq(true) + expect(user.profiles.any?(&:active?)).to eq(false) + end end - end - context 'when the user does not have a pending profile' do - let(:user) { create(:user, reset_password_sent_at: Time.zone.now) } + context 'when the user does not have an active profile' do + let(:user) { create(:user, reset_password_sent_at: Time.zone.now) } - it 'includes that the profile was not deactivated in the form response' do - expect(result.success?).to eq(true) - expect(result.extra[:pending_profile_invalidated]).to eq(false) - expect(result.extra[:pending_profile_pending_reasons]).to eq('') + it 'includes that the profile was not deactivated in the form response' do + expect(result.success?).to eq(true) + expect(result.extra[:profile_deactivated]).to eq(false) + end end - end - context 'when the unconfirmed email address has been confirmed by another account' do - let(:user) { create(:user, :unconfirmed, reset_password_sent_at: Time.zone.now) } + context 'when the user has a pending profile' do + context 'when the profile is pending gpo verification' do + let!(:user) { create(:user, reset_password_sent_at: Time.zone.now) } + let!(:profile) do + create(:profile, :verify_by_mail_pending, :in_person_verification_pending, user: user) + end - before do - create( - :user, - email_addresses: [create(:email_address, email: user.email_addresses.first.email)], - ) + before do + @result = form.submit(params) + profile.reload + end + + it 'includes that the profile was not deactivated in the form response' do + expect(result.success?).to eq(true) + expect(result.extra[:pending_profile_invalidated]).to eq(true) + expect(result.extra[:pending_profile_pending_reasons]).to eq( + 'gpo_verification_pending,in_person_verification_pending', + ) + end + end + + context 'when the profile is pending in person verification' do + let!(:user) { create(:user, reset_password_sent_at: Time.zone.now) } + let!(:profile) { create(:profile, :in_person_verification_pending, user: user) } + + before do + @result = form.submit(params) + profile.reload + end + + it 'returns a successful response' do + expect(@result.success?).to eq(true) + end + + it 'includes that the profile was not deactivated in the form response' do + expect(@result.extra).to include( + pending_profile_invalidated: true, + pending_profile_pending_reasons: 'in_person_verification_pending', + ) + end + + it 'does not update the profile to have a "password reset" deactivation reason' do + expect(profile.deactivation_reason).to be_nil + end + end end - it 'does not raise an error and is not successful' do - expect(result.success?).to eq(false) - expect(result.errors).to eq({ reset_password_token: ['token_expired'] }) + context 'when the user does not have a pending profile' do + let(:user) { create(:user, reset_password_sent_at: Time.zone.now) } + + it 'includes that the profile was not deactivated in the form response' do + expect(result.success?).to eq(true) + expect(result.extra[:pending_profile_invalidated]).to eq(false) + expect(result.extra[:pending_profile_pending_reasons]).to eq('') + end end - end - it_behaves_like 'strong password', 'ResetPasswordForm' + context 'when the unconfirmed email address has been confirmed by another account' do + let(:user) { create(:user, :unconfirmed, reset_password_sent_at: Time.zone.now) } + + before do + create( + :user, + email_addresses: [create(:email_address, email: user.email_addresses.first.email)], + ) + end + + it 'does not raise an error and is not successful' do + expect(result.success?).to eq(false) + expect(result.errors).to eq({ reset_password_token: ['token_expired'] }) + end + end + + it_behaves_like 'strong password', 'ResetPasswordForm' + end end end diff --git a/spec/forms/select_email_form_spec.rb b/spec/forms/select_email_form_spec.rb index cc63aaabd3c..da74cc1ff3e 100644 --- a/spec/forms/select_email_form_spec.rb +++ b/spec/forms/select_email_form_spec.rb @@ -18,7 +18,9 @@ end context 'with associated identity' do - let(:identity) { create(:service_provider_identity, :consented, user:) } + let(:identity) do + create(:service_provider_identity, :consented, user:, verified_attributes: %w[email]) + end it 'updates linked email address' do expect { response }.to change { identity.reload.email_address_id } diff --git a/spec/forms/totp_setup_form_spec.rb b/spec/forms/totp_setup_form_spec.rb index aae42abd1f1..cdd0ebce540 100644 --- a/spec/forms/totp_setup_form_spec.rb +++ b/spec/forms/totp_setup_form_spec.rb @@ -80,8 +80,8 @@ expect(form.submit.to_h).to include( success: false, + errors: nil, error_details: { name: { blank: true } }, - errors: { name: [t('errors.messages.blank')] }, ) expect(user.auth_app_configurations.any?).to eq false end @@ -95,8 +95,8 @@ expect(form2.submit.to_h).to include( success: false, + errors: nil, error_details: { name: { unique_name: true } }, - errors: { name: [t('errors.piv_cac_setup.unique_name')] }, ) end end diff --git a/spec/forms/two_factor_login_options_form_spec.rb b/spec/forms/two_factor_login_options_form_spec.rb index 6c2edbfae1e..fbfab8b0b2b 100644 --- a/spec/forms/two_factor_login_options_form_spec.rb +++ b/spec/forms/two_factor_login_options_form_spec.rb @@ -27,10 +27,6 @@ context 'when the form is invalid' do it 'returns false for success? and includes errors' do - errors = { - selection: ['is not included in the list'], - } - extra = { selection: 'foo', enabled_mfa_methods_count: 1, @@ -40,8 +36,8 @@ expect(subject.submit(selection: 'foo').to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { selection: { inclusion: true } }, **extra, ) end diff --git a/spec/forms/update_user_password_form_spec.rb b/spec/forms/update_user_password_form_spec.rb index 1955390f085..bdd4fd7705a 100644 --- a/spec/forms/update_user_password_form_spec.rb +++ b/spec/forms/update_user_password_form_spec.rb @@ -22,17 +22,6 @@ let(:password) { 'invalid' } it 'returns FormResponse with success: false and does not do anything else' do - errors = { - password: [t( - 'errors.attributes.password.too_short.other', - count: Devise.password_length.first, - )], - password_confirmation: [I18n.t( - 'errors.messages.too_short', - count: Devise.password_length.first, - )], - } - expect(UserProfilesEncryptor).not_to receive(:new) user.save! @@ -43,7 +32,7 @@ expect(result).to include( success: false, - errors: errors, + errors: nil, error_details: hash_including(:password, :password_confirmation), ) end diff --git a/spec/forms/webauthn_setup_form_spec.rb b/spec/forms/webauthn_setup_form_spec.rb index 1e78a2a96bd..7a3e4940938 100644 --- a/spec/forms/webauthn_setup_form_spec.rb +++ b/spec/forms/webauthn_setup_form_spec.rb @@ -203,14 +203,7 @@ expect(result.to_h).to eq( success: false, - errors: { - attestation_object: [ - I18n.t( - 'errors.webauthn_setup.general_error_html', - link_html: I18n.t('errors.webauthn_setup.additional_methods_link'), - ), - ], - }, + errors: nil, error_details: { attestation_object: { invalid: true } }, transports: ['usb'], transports_mismatch: false, @@ -257,14 +250,7 @@ expect(result.to_h).to eq( success: false, - errors: { - attestation_object: [ - I18n.t( - 'errors.webauthn_setup.general_error_html', - link_html: I18n.t('errors.webauthn_setup.additional_methods_link'), - ), - ], - }, + errors: nil, error_details: { attestation_object: { invalid: true } }, transports: ['usb'], transports_mismatch: false, diff --git a/spec/forms/webauthn_visit_form_spec.rb b/spec/forms/webauthn_visit_form_spec.rb index c1e1d9c11f7..7efed98e9db 100644 --- a/spec/forms/webauthn_visit_form_spec.rb +++ b/spec/forms/webauthn_visit_form_spec.rb @@ -44,85 +44,61 @@ context 'when there are errors' do it 'returns FormResponse with success: false with InvalidStateError' do params = { error: 'InvalidStateError' } - errors = { InvalidStateError: [I18n.t('errors.webauthn_setup.already_registered')] } expect(subject.submit(params).to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { InvalidStateError: { invalid: true } }, ) end it 'returns FormResponse with success: false with NotSupportedError' do params = { error: 'NotSupportedError' } - errors = { NotSupportedError: [I18n.t('errors.webauthn_setup.not_supported')] } expect(subject.submit(params).to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { NotSupportedError: { invalid: true } }, ) end it 'returns FormResponse with success: false with an unrecognized error' do params = { error: 'foo' } - general_error = t( - 'errors.webauthn_setup.general_error_html', - link_html: link_to( - t('errors.webauthn_setup.additional_methods_link'), - authentication_methods_setup_path, - ), - ) - errors = { - foo: [general_error], - } expect(subject.submit(params).to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + error_details: { foo: { invalid: true } }, ) end context 'with platform authenticator' do it 'returns FormResponse with success: false with InvalidStateError' do params = { error: 'InvalidStateError', platform: 'true' } - errors = { - InvalidStateError: [I18n.t('errors.webauthn_platform_setup.already_registered')], - } expect(subject.submit(params).to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { InvalidStateError: { invalid: true } }, ) end it 'returns FormResponse with success: false with NotSupportedError' do params = { error: 'NotSupportedError', platform: 'true' } - errors = { NotSupportedError: [I18n.t('errors.webauthn_platform_setup.not_supported')] } expect(subject.submit(params).to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { NotSupportedError: { invalid: true } }, ) end it 'returns FormResponse with success: false with an unrecognized error' do params = { error: 'foo', platform: 'true' } - errors = { foo: [I18n.t( - 'errors.webauthn_platform_setup.account_setup_error', - link: link_to( - I18n.t('errors.webauthn_platform_setup.choose_another_method'), - authentication_methods_setup_path, - ), - )] } expect(subject.submit(params).to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { foo: { invalid: true } }, ) end @@ -132,15 +108,11 @@ it 'returns FormResponse with success: false with an unrecognized error' do params = { error: 'foo', platform: 'true' } - errors = { foo: [I18n.t( - 'errors.webauthn_platform_setup.account_setup_error', - link: I18n.t('errors.webauthn_platform_setup.choose_another_method'), - )] } expect(subject.submit(params).to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { foo: { invalid: true } }, ) end end diff --git a/spec/services/create_new_device_alert_spec.rb b/spec/jobs/create_new_device_alert_job_spec.rb similarity index 74% rename from spec/services/create_new_device_alert_spec.rb rename to spec/jobs/create_new_device_alert_job_spec.rb index 3517894a421..0e010739af1 100644 --- a/spec/services/create_new_device_alert_spec.rb +++ b/spec/jobs/create_new_device_alert_job_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe CreateNewDeviceAlert do +RSpec.describe CreateNewDeviceAlertJob do let(:user) { create(:user) } let(:now) { Time.zone.now } @@ -11,26 +11,26 @@ describe '#perform' do it 'sends an email for matching user' do - emails_sent = CreateNewDeviceAlert.new.perform(now) + emails_sent = CreateNewDeviceAlertJob.new.perform(now) expect(emails_sent).to eq(1) - email_sent_again = CreateNewDeviceAlert.new.perform(now) + email_sent_again = CreateNewDeviceAlertJob.new.perform(now) expect(email_sent_again).to eq(0) end it 'resets user sign_in_new_device_at to nil' do - CreateNewDeviceAlert.new.perform(now) + CreateNewDeviceAlertJob.new.perform(now) expect(user.reload.sign_in_new_device_at).to eq(nil) end it 'disregards a user with sign_in_new_device_at set to nil' do user.update! sign_in_new_device_at: nil - emails_sent = CreateNewDeviceAlert.new.perform(now) + emails_sent = CreateNewDeviceAlertJob.new.perform(now) expect(emails_sent).to eq(0) end it 'logs analytics with number of emails sent' do analytics = FakeAnalytics.new - alert = CreateNewDeviceAlert.new + alert = CreateNewDeviceAlertJob.new allow(alert).to receive(:analytics).and_return(analytics) alert.perform(now) diff --git a/spec/jobs/get_usps_proofing_results_job_spec.rb b/spec/jobs/get_usps_proofing_results_job_spec.rb index b22d3ee27e5..bda87a4b899 100644 --- a/spec/jobs/get_usps_proofing_results_job_spec.rb +++ b/spec/jobs/get_usps_proofing_results_job_spec.rb @@ -18,6 +18,7 @@ enrollments_cancelled: 0, enrollments_in_progress: 0, enrollments_passed: 0, + enrollments_skipped: 0, duration_seconds: 0.0, percent_enrollments_errored: 0.0, percent_enrollments_network_error: 0.0, @@ -1372,6 +1373,75 @@ ).and_return(true) end + context 'when the enrollment has a deactivation reason of password_reset' do + let(:deactivation_reason) { 'password_reset' } + let(:in_person_verification_pending_at) do + enrollment.profile.in_person_verification_pending_at + end + + before do + enrollment.profile.update(deactivation_reason: deactivation_reason) + stub_request_passed_proofing_results + allow(analytics).to receive( + :idv_in_person_usps_proofing_results_job_enrollment_skipped, + ) + allow(analytics).to receive(:idv_in_person_usps_proofing_results_job_exception) + subject.perform(current_time) + end + + it 'logs the job started analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_started, + ).with( + enrollments_count: 1, + reprocess_delay_minutes: 5, + job_name: described_class.name, + ) + end + + it 'updates the enrollment status check timestamps' do + expect(enrollment.reload).to have_attributes( + status_check_attempted_at: current_time, + last_batch_claimed_at: current_time, + ) + end + + it 'logs the job enrollment skipped analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_enrollment_skipped, + ).with( + **enrollment_analytics, + minutes_to_completion: nil, + reason: "Profile has a deactivation reason of #{deactivation_reason}", + job_name: described_class.name, + ) + end + + it 'does not cancel the enrollment' do + expect(enrollment.reload).to have_attributes( + status: 'pending', + ) + end + + it "does not update the enrollment's profile" do + expect(enrollment.reload.profile).to have_attributes( + active: false, + deactivation_reason:, + in_person_verification_pending_at:, + ) + end + + it 'logs the job completed analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_completed, + ).with( + **default_job_completion_analytics, + enrollments_checked: 1, + enrollments_skipped: 1, + ) + end + end + context 'when the USPS proofing results is not a hash' do before do stub_request_proofing_results( @@ -2319,7 +2389,7 @@ end context 'when the USPS proofing results has a failed status' do - context 'when he USPS proofing results does not have fraud suspected' do + context 'when the USPS proofing results does not have fraud suspected' do before do response_body[:status] = 'In-person failed' response_body[:failureReason] = 'Address does not match source data.' @@ -2670,61 +2740,132 @@ end context 'when the enrollment has a profile with a deactivation reason' do - let(:deactivation_reason) { 'encryption_error' } + context 'when the profile deactivation reason is "encryption_error"' do + let(:deactivation_reason) { 'encryption_error' } - before do - enrollment.profile.update(deactivation_reason: deactivation_reason) - allow(analytics).to receive(:idv_in_person_usps_proofing_results_job_enrollment_updated) - subject.perform(current_time) - end + before do + enrollment.profile.update(deactivation_reason: deactivation_reason) + allow(analytics).to receive( + :idv_in_person_usps_proofing_results_job_enrollment_updated, + ) + subject.perform(current_time) + end - it 'logs the job started analytic' do - expect(analytics).to have_received( - :idv_in_person_usps_proofing_results_job_started, - ).with( - enrollments_count: 1, - reprocess_delay_minutes: 5, - job_name: described_class.name, - ) - end + it 'logs the job started analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_started, + ).with( + enrollments_count: 1, + reprocess_delay_minutes: 5, + job_name: described_class.name, + ) + end - it 'logs the job enrollment updated analytic' do - expect(analytics).to have_received( - :idv_in_person_usps_proofing_results_job_enrollment_updated, - ).with( - **enrollment_analytics, - response_present: false, - passed: false, - reason: "Profile has a deactivation reason of #{deactivation_reason}", - job_name: described_class.name, - tmx_status: nil, - profile_age_in_seconds: enrollment.profile&.profile_age_in_seconds, - enhanced_ipp: false, - ) - end + it 'logs the job enrollment updated analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_enrollment_updated, + ).with( + **enrollment_analytics, + response_present: false, + passed: false, + reason: "Profile has a deactivation reason of #{deactivation_reason}", + job_name: described_class.name, + tmx_status: nil, + profile_age_in_seconds: enrollment.profile&.profile_age_in_seconds, + enhanced_ipp: false, + ) + end - it 'cancels the enrollment' do - expect(enrollment.reload).to have_attributes( - status: 'cancelled', - ) - end + it 'cancels the enrollment' do + expect(enrollment.reload).to have_attributes( + status: 'cancelled', + ) + end - it "deactivates the enrollment's profile" do - expect(enrollment.reload.profile).to have_attributes( - active: false, - deactivation_reason: 'encryption_error', - in_person_verification_pending_at: nil, - ) + it "deactivates the enrollment's profile" do + expect(enrollment.reload.profile).to have_attributes( + active: false, + deactivation_reason: 'encryption_error', + in_person_verification_pending_at: nil, + ) + end + + it 'logs the job completed analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_completed, + ).with( + **default_job_completion_analytics, + enrollments_checked: 1, + enrollments_cancelled: 1, + ) + end end - it 'logs the job completed analytic' do - expect(analytics).to have_received( - :idv_in_person_usps_proofing_results_job_completed, - ).with( - **default_job_completion_analytics, - enrollments_checked: 1, - enrollments_cancelled: 1, - ) + context 'when the deactivation reason is "password_reset"' do + let(:deactivation_reason) { 'password_reset' } + let(:in_person_verification_pending_at) do + enrollment.profile.in_person_verification_pending_at + end + + before do + enrollment.profile.update(deactivation_reason: deactivation_reason) + allow(InPersonEnrollment).to receive(:needs_usps_status_check).and_return( + InPersonEnrollment.where(id: enrollment.id), + ) + allow(analytics).to receive( + :idv_in_person_usps_proofing_results_job_enrollment_skipped, + ) + stub_request_passed_proofing_results + allow(analytics).to receive( + :idv_in_person_usps_proofing_results_job_enrollment_incomplete, + ) + subject.perform(current_time) + end + + it 'logs the job started analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_started, + ).with( + enrollments_count: 1, + reprocess_delay_minutes: 5, + job_name: described_class.name, + ) + end + + it 'logs the job enrollment skipped analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_enrollment_skipped, + ).with( + **enrollment_analytics, + minutes_to_completion: nil, + reason: "Profile has a deactivation reason of #{deactivation_reason}", + job_name: described_class.name, + ) + end + + it 'does not cancel the enrollment' do + expect(enrollment.reload).to have_attributes( + status: 'pending', + ) + end + + it "does not update the enrollment's profile" do + expect(enrollment.reload.profile).to have_attributes( + active: false, + deactivation_reason:, + in_person_verification_pending_at:, + ) + end + + it 'logs the job completed analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_completed, + ).with( + **default_job_completion_analytics, + enrollments_checked: 1, + enrollments_skipped: 1, + ) + end end end diff --git a/spec/services/account_reset/grant_requests_and_send_emails_spec.rb b/spec/jobs/grant_account_reset_requests_and_send_emails_spec.rb similarity index 77% rename from spec/services/account_reset/grant_requests_and_send_emails_spec.rb rename to spec/jobs/grant_account_reset_requests_and_send_emails_spec.rb index a2f19b6facd..a55503efb25 100644 --- a/spec/services/account_reset/grant_requests_and_send_emails_spec.rb +++ b/spec/jobs/grant_account_reset_requests_and_send_emails_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe AccountReset::GrantRequestsAndSendEmails do +RSpec.describe GrantAccountResetRequestsAndSendEmailsJob do include AccountResetHelper let(:user) { create(:user) } @@ -16,8 +16,8 @@ create_account_reset_request_for(user) end - AccountReset::GrantRequestsAndSendEmails.new.perform(now) - notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now) + GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) + notifications_sent = GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) expect(notifications_sent).to eq(0) end @@ -27,7 +27,7 @@ cancel_request_for(user) end - notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now) + notifications_sent = GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) expect(notifications_sent).to eq(0) end @@ -36,7 +36,7 @@ create_account_reset_request_for(user) end - notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now) + notifications_sent = GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) expect(notifications_sent).to eq(1) end @@ -47,7 +47,7 @@ create_account_reset_request_for(user2) end - notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now) + notifications_sent = GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) expect(notifications_sent).to eq(2) end @@ -65,8 +65,8 @@ create_account_reset_request_for(user) end - AccountReset::GrantRequestsAndSendEmails.new.perform(now) - notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now) + GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) + notifications_sent = GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) expect(notifications_sent).to eq(0) end @@ -76,7 +76,7 @@ cancel_request_for(user) end - notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now) + notifications_sent = GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) expect(notifications_sent).to eq(0) end @@ -85,7 +85,7 @@ create_account_reset_request_for(user) end - notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now) + notifications_sent = GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) expect(notifications_sent).to eq(1) end @@ -96,7 +96,7 @@ create_account_reset_request_for(user2) end - notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now) + notifications_sent = GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) expect(notifications_sent).to eq(2) end @@ -108,7 +108,7 @@ it 'does not send notifications before a request wait period is done' do create_account_reset_request_for(user) - notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now) + notifications_sent = GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) expect(notifications_sent).to eq(0) end @@ -116,7 +116,7 @@ create_account_reset_request_for(user) cancel_request_for(user) - notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now) + notifications_sent = GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) expect(notifications_sent).to eq(0) end end @@ -128,7 +128,7 @@ it 'does not send notifications before a request wait period is done' do create_account_reset_request_for(user) - notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now) + notifications_sent = GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) expect(notifications_sent).to eq(0) end @@ -136,7 +136,7 @@ create_account_reset_request_for(user) cancel_request_for(user) - notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now) + notifications_sent = GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) expect(notifications_sent).to eq(0) end @@ -146,7 +146,7 @@ create_account_reset_request_for(user2) end - notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now) + notifications_sent = GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) expect(notifications_sent).to eq(0) end @@ -160,7 +160,7 @@ it 'does not send notifications before a request wait period is done' do create_account_reset_request_for(user) - notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now) + notifications_sent = GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) expect(notifications_sent).to eq(0) end @@ -168,7 +168,7 @@ create_account_reset_request_for(user) cancel_request_for(user) - notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now) + notifications_sent = GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) expect(notifications_sent).to eq(0) end @@ -178,7 +178,7 @@ create_account_reset_request_for(user2) end - notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now) + notifications_sent = GrantAccountResetRequestsAndSendEmailsJob.new.perform(now) expect(notifications_sent).to eq(2) end diff --git a/spec/lib/feature_management_spec.rb b/spec/lib/feature_management_spec.rb index f5c88b2fe6c..f6d8e62149e 100644 --- a/spec/lib/feature_management_spec.rb +++ b/spec/lib/feature_management_spec.rb @@ -537,4 +537,30 @@ end end end + + describe '#pending_in_person_password_reset_enabled?' do + context 'when feature_pending_in_person_password_enabled is true' do + before do + allow(IdentityConfig.store).to receive( + :feature_pending_in_person_password_reset_enabled, + ).and_return(true) + end + + it 'returns true' do + expect(FeatureManagement.pending_in_person_password_reset_enabled?).to be(true) + end + end + + context 'when feature_pending_in_person_password_enabled is false' do + before do + allow(IdentityConfig.store).to receive( + :feature_pending_in_person_password_reset_enabled, + ).and_return(false) + end + + it 'returns false' do + expect(FeatureManagement.pending_in_person_password_reset_enabled?).to be(false) + end + end + end end diff --git a/spec/models/in_person_enrollment_spec.rb b/spec/models/in_person_enrollment_spec.rb index 290abe0d61b..ae78ceb92a6 100644 --- a/spec/models/in_person_enrollment_spec.rb +++ b/spec/models/in_person_enrollment_spec.rb @@ -562,4 +562,40 @@ end end end + + describe '#cancel' do + context 'when the enrollment has a profile' do + let(:profile) { create(:profile) } + let(:enrollment) do + create(:in_person_enrollment, :pending, user: profile.user, profile: profile) + end + + before do + enrollment.cancel + end + + it 'updates the enrollment status to "cancelled"' do + expect(enrollment.status).to eq('cancelled') + end + + it 'deactivates the profile' do + expect(profile).to have_attributes( + deactivation_reason: 'verification_cancelled', + in_person_verification_pending_at: nil, + ) + end + end + + context 'when the enrollment does not have a profile' do + let(:enrollment) { create(:in_person_enrollment, :establishing) } + + before do + enrollment.cancel + end + + it 'updates the enrollment status to "cancelled"' do + expect(enrollment.status).to eq('cancelled') + end + end + end end diff --git a/spec/models/profile_spec.rb b/spec/models/profile_spec.rb index 1e7f1ffeec1..b8e902f3fbe 100644 --- a/spec/models/profile_spec.rb +++ b/spec/models/profile_spec.rb @@ -696,166 +696,57 @@ end end - describe '#activate_after_password_reset' do - it 'activates a non-verified, non-active, non-activated profile after password reset' do - # TODO: this profile scenario shouldn't be possible - profile = create( - :profile, - :password_reset, - user: user, - ) - - # profile.initiating_service_provider is nil before and after because - # the user is coming from a password reset email - - expect(profile.activated_at).to be_nil # will change but shouldn't - expect(profile.active).to eq(false) # will change but shouldn't - expect(profile.deactivation_reason).to eq 'password_reset' # will change but shouldn't - expect(profile.fraud_review_pending?).to eq(false) - expect(profile.gpo_verification_pending_at).to be_nil - expect(profile.in_person_verification_pending_at).to be_nil - expect(profile.initiating_service_provider).to be_nil - expect(profile.verified_at).to be_nil - - # TODO: this should raise an error - profile.activate_after_password_reset - - expect(profile.activated_at).to be_present # !!! changed - expect(profile.active).to eq(true) # !!! changed - expect(profile.deactivation_reason).to be_nil # !!! changed - expect(profile.fraud_review_pending?).to eq(false) - expect(profile.gpo_verification_pending_at).to be_nil - expect(profile.in_person_verification_pending_at).to be_nil - expect(profile.initiating_service_provider).to be_nil - expect(profile.verified_at).to be_nil - end - - it 'activates a previously verified profile after password reset' do - profile = create( - :profile, - :active, - :password_reset, - user: user, - ) - verified_at = profile.verified_at - - expect(profile.activated_at).to be_present - expect(profile.active).to eq(false) # to change - expect(profile.deactivation_reason).to eq 'password_reset' # to change - expect(profile.fraud_review_pending?).to eq(false) - expect(profile.gpo_verification_pending_at).to be_nil - expect(profile.in_person_verification_pending_at).to be_nil - expect(profile.initiating_service_provider).to be_nil - expect(profile.verified_at).to eq verified_at - - profile.activate_after_password_reset - - expect(profile.activated_at).to be_present - expect(profile.active).to eq(true) # changed - expect(profile.deactivation_reason).to be_nil # changed - expect(profile.fraud_review_pending?).to eq(false) - expect(profile.gpo_verification_pending_at).to be_nil - expect(profile.in_person_verification_pending_at).to be_nil - expect(profile.initiating_service_provider).to be_nil - expect(profile.verified_at).to eq verified_at - end - - # ??? This method still nils out deactivation_reason even though it raises - it 'does not activate a profile if it has a pending reason' do - profile = create( - :profile, - :password_reset, - :fraud_review_pending, - user: user, - ) - - expect(profile.activated_at).to be_nil - expect(profile.active).to eq(false) - expect(profile.deactivation_reason).to eq('password_reset') # to change - expect(profile.fraud_review_pending?).to eq(true) - expect(profile.gpo_verification_pending_at).to be_nil - expect(profile.in_person_verification_pending_at).to be_nil - expect(profile.initiating_service_provider).to be_nil - expect(profile.verified_at).to be_nil - - expect { profile.activate_after_password_reset }.to raise_error( - RuntimeError, - 'Attempting to activate profile with pending reasons: fraud_check_pending', - ) + describe '#clear_password_reset_deactivation_reason' do + context 'when the profile has the password_reset deactivation reason' do + context 'when the profile was previously active' do + subject { create(:profile, :active, user: user) } + + before do + subject.deactivate(:password_reset) + subject.clear_password_reset_deactivation_reason + end - expect(profile.activated_at).to be_nil - expect(profile.active).to eq(false) - expect(profile.deactivation_reason).to be_nil # changed - expect(profile.fraud_review_pending?).to eq(true) - expect(profile.gpo_verification_pending_at).to be_nil - expect(profile.in_person_verification_pending_at).to be_nil - expect(profile.initiating_service_provider).to be_nil - expect(profile.verified_at).to be_nil + it 'removes "password_reset" deactivation reason from the profile' do + expect(subject.deactivation_reason).to be_nil + end - expect(profile).to_not be_active - end + it 'activates the profile' do + expect(subject.active?).to be(true) + end + end - it 'does not activate a profile with non password_reset deactivation_reason' do - profile = create( - :profile, - :encryption_error, - user: user, - ) + context 'when the profile was not previously active' do + subject { create(:profile, :in_person_verification_pending, user: user) } - expect(profile.activated_at).to be_nil - expect(profile.active).to eq(false) - expect(profile.deactivation_reason).to eq 'encryption_error' - expect(profile.fraud_review_pending?).to eq(false) - expect(profile.gpo_verification_pending_at).to be_nil - expect(profile.in_person_verification_pending_at).to be_nil - expect(profile.initiating_service_provider).to be_nil - expect(profile.verified_at).to be_nil + before do + subject.deactivate(:password_reset) + subject.clear_password_reset_deactivation_reason + end - profile.activate_after_password_reset + it 'removes "password_reset" deactivation reason from the profile' do + expect(subject.deactivation_reason).to be_nil + end - expect(profile.activated_at).to be_nil - expect(profile.active).to eq(false) - expect(profile.deactivation_reason).to eq 'encryption_error' - expect(profile.fraud_review_pending?).to eq(false) - expect(profile.gpo_verification_pending_at).to be_nil - expect(profile.in_person_verification_pending_at).to be_nil - expect(profile.initiating_service_provider).to be_nil - expect(profile.verified_at).to be_nil + it 'does not activate the profile' do + expect(subject.active?).to be(false) + end + end end - it 'does not activate a non-verified profile if it encounters a transaction error' do - profile = create( - :profile, - :password_reset, - user: user, - ) - - allow(profile).to receive(:update!).and_raise(RuntimeError) - - expect(profile.activated_at).to be_nil - expect(profile.active).to eq(false) - expect(profile.deactivation_reason).to eq('password_reset') - expect(profile.fraud_review_pending?).to eq(false) - expect(profile.gpo_verification_pending_at).to be_nil - expect(profile.in_person_verification_pending_at).to be_nil - expect(profile.initiating_service_provider).to be_nil - expect(profile.verified_at).to be_nil + context 'when the profile does not have the password_reset deactivation reason' do + subject { create(:profile, :encryption_error, user: user) } - suppress(RuntimeError) do - profile.activate_after_password_reset + before do + subject.clear_password_reset_deactivation_reason end - expect(profile.activated_at).to be_nil - expect(profile.active).to eq(false) - expect(profile.deactivation_reason).to eq('password_reset') - expect(profile.fraud_review_pending?).to eq(false) - expect(profile.gpo_verification_pending_at).to be_nil - expect(profile.in_person_verification_pending_at).to be_nil - expect(profile.initiating_service_provider).to be_nil - expect(profile.verified_at).to be_nil + it 'does not remove the deactivation reason from the profile' do + expect(subject.deactivation_reason).to eq('encryption_error') + end - expect(profile.deactivation_reason).to eq('password_reset') - expect(profile).to_not be_active + it 'does not activate the profile' do + expect(subject.active?).to be(false) + end end end diff --git a/spec/models/service_provider_identity_spec.rb b/spec/models/service_provider_identity_spec.rb index f188c84368f..e2c81d1ad9c 100644 --- a/spec/models/service_provider_identity_spec.rb +++ b/spec/models/service_provider_identity_spec.rb @@ -243,8 +243,15 @@ ) end + let(:service_provider) { create(:service_provider) } + let(:identity) do - create(:service_provider_identity, user: user, session_uuid: SecureRandom.uuid) + create( + :service_provider_identity, + user: user, + session_uuid: SecureRandom.uuid, + service_provider: service_provider.issuer, + ) end context 'when email sharing feature is enabled' do @@ -253,7 +260,7 @@ .and_return(true) end - context 'when an email address for sharing has been set' do + context 'when an email address is set' do before do identity.email_address = shared_email_address end @@ -284,4 +291,67 @@ end end end + + describe '#clear_email_address_id_if_not_supported' do + let(:verified_attributes) { %w[email] } + let!(:shared_email_address) do + create( + :email_address, + email: 'shared@email.com', + user: user, + last_sign_in_at: 1.hour.ago, + ) + end + let(:identity) do + create( + :service_provider_identity, + user: user, + session_uuid: SecureRandom.uuid, + service_provider: service_provider.issuer, + verified_attributes: verified_attributes, + email_address_id: shared_email_address.id, + ) + end + + context 'when user has only email as the verified attribute attribute' do + let(:new_shared_email_address) do + create( + :email_address, + email: 'shared2@email.com', + user: user, + last_sign_in_at: 1.hour.ago, + ) + end + before do + allow(IdentityConfig.store).to receive(:feature_select_email_to_share_enabled) + .and_return(true) + end + + it 'should save the new email properly on update' do + identity.update!(email_address_id: new_shared_email_address.id) + expect(identity.email_address).to eq(new_shared_email_address) + end + end + + context 'when user has all_emails as the verified attribute' do + let(:verified_attributes) { %w[all_emails] } + let(:new_shared_email_address) do + create( + :email_address, + email: 'shared2@email.com', + user: user, + last_sign_in_at: 1.hour.ago, + ) + end + before do + allow(IdentityConfig.store).to receive(:feature_select_email_to_share_enabled) + .and_return(true) + end + + it 'should make the email address to nil' do + identity.update!(email_address_id: new_shared_email_address.id) + expect(identity.email_address).to eq(nil) + end + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8d6a887cd20..8925199792d 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1601,35 +1601,150 @@ def it_should_not_send_survey describe '#password_reset_profile' do let(:user) { create(:user) } - context 'with no profiles' do - it { expect(user.password_reset_profile).to be_nil } - end + context 'when pending in-person password reset is enabled' do + before do + allow(FeatureManagement).to receive( + :pending_in_person_password_reset_enabled?, + ).and_return(true) + end - context 'with an active profile' do - let(:active_profile) do - build(:profile, :active, :verified, activated_at: 1.day.ago, pii: { first_name: 'Jane' }) + context 'with no profiles' do + it { expect(user.password_reset_profile).to be_nil } end - before do - user.profiles << [ - active_profile, - build(:profile, :verified, activated_at: 5.days.ago, pii: { first_name: 'Susan' }), - ] + context 'with an active profile' do + let(:active_profile) do + build(:profile, :active, :verified, activated_at: 1.day.ago, pii: { first_name: 'Jane' }) + end + + before do + user.profiles << [ + active_profile, + build(:profile, :verified, activated_at: 5.days.ago, pii: { first_name: 'Susan' }), + ] + end + + it { expect(user.password_reset_profile).to be_nil } + + context 'when the active profile is deactivated due to password reset' do + before { active_profile.deactivate(:password_reset) } + + it { expect(user.password_reset_profile).to eq(active_profile) } + + context 'with a previously-cancelled pending profile' do + before do + user.profiles << build(:profile, :verification_cancelled) + end + + it { expect(user.password_reset_profile).to eq(active_profile) } + end + end end - it { expect(user.password_reset_profile).to be_nil } + context 'with a pending in person profile' do + let(:pending_in_person_enrollment) { create(:in_person_enrollment, :pending, user: user) } + let(:pending_profile) { pending_in_person_enrollment.profile } - context 'when the active profile is deactivated due to password reset' do - before { active_profile.deactivate(:password_reset) } + context 'when the pending in person profile has a "password_reset deactivation reason"' do + before do + pending_profile.update!(deactivation_reason: 'password_reset') + end - it { expect(user.password_reset_profile).to eq(active_profile) } + it 'returns the pending profile' do + expect(user.password_reset_profile).to eq(pending_profile) + end + end - context 'with a previously-cancelled pending profile' do + context 'when the pending in person profile does not have a deactivation reason' do + it 'returns nil' do + expect(user.password_reset_profile).to be_nil + end + end + end + + context 'with a pending in person and an active profile' do + let(:pending_in_person_enrollment) { create(:in_person_enrollment, :pending, user: user) } + let(:pending_profile) { pending_in_person_enrollment.profile } + let(:active_profile) do + create(:profile, :active, :verified, activated_at: 1.day.ago, pii: { first_name: 'Jane' }) + end + + context 'when the pending in person profile has a "password_reset deactivation reason"' do before do - user.profiles << build(:profile, :verification_cancelled) + pending_profile.update!(deactivation_reason: 'password_reset') end + it 'returns the pending profile' do + expect(user.password_reset_profile).to eq(pending_profile) + end + end + + context 'when the pending in person profile does not have a deactivation reason' do + it 'returns nil' do + expect(user.password_reset_profile).to be_nil + end + end + end + end + + context 'when pending in-person password reset is disabled' do + before do + allow(FeatureManagement).to receive( + :pending_in_person_password_reset_enabled?, + ).and_return(false) + end + + context 'with no profiles' do + it { expect(user.password_reset_profile).to be_nil } + end + + context 'with an active profile' do + let(:active_profile) do + build(:profile, :active, :verified, activated_at: 1.day.ago, pii: { first_name: 'Jane' }) + end + + before do + user.profiles << [ + active_profile, + build(:profile, :verified, activated_at: 5.days.ago, pii: { first_name: 'Susan' }), + ] + end + + it { expect(user.password_reset_profile).to be_nil } + + context 'when the active profile is deactivated due to password reset' do + before { active_profile.deactivate(:password_reset) } + it { expect(user.password_reset_profile).to eq(active_profile) } + + context 'with a previously-cancelled pending profile' do + before do + user.profiles << build(:profile, :verification_cancelled) + end + + it { expect(user.password_reset_profile).to eq(active_profile) } + end + end + end + + context 'with a pending in person profile' do + let(:pending_in_person_enrollment) { create(:in_person_enrollment, :pending, user: user) } + let(:pending_profile) { pending_in_person_enrollment.profile } + + context 'when the pending in person profile has a "password_reset deactivation reason"' do + before do + pending_profile.update!(deactivation_reason: 'password_reset') + end + + it 'returns nil' do + expect(user.password_reset_profile).to be_nil + end + end + + context 'when the pending in person profile does not have a deactivation reason' do + it 'returns nil' do + expect(user.password_reset_profile).to be_nil + end end end end diff --git a/spec/policies/pending_profile_policy_spec.rb b/spec/policies/pending_profile_policy_spec.rb index ec59c3eaee8..775f9393f6b 100644 --- a/spec/policies/pending_profile_policy_spec.rb +++ b/spec/policies/pending_profile_policy_spec.rb @@ -2,17 +2,7 @@ RSpec.describe PendingProfilePolicy do let(:user) { create(:user) } - let(:resolved_authn_context_result) do - AuthnContextResolver.new( - user:, - service_provider: nil, - vtr:, - acr_values:, - ).result - end - let(:vtr) { nil } - let(:acr_values) { nil } - + let(:resolved_authn_context_result) { double(AuthnContextResolver) } subject(:policy) do described_class.new( user: user, @@ -21,72 +11,120 @@ end describe '#user_has_pending_profile?' do - context 'has an active non-facial match profile and facial match comparison is requested' do - let(:idv_level) { :unsupervised_with_selfie } + context 'when user is nil' do + subject(:policy) do + described_class.new( + user: nil, + resolved_authn_context_result: resolved_authn_context_result, + ) + end - before do - create(:profile, :active, :verified, idv_level: :legacy_unsupervised, user: user) - create(:profile, :verify_by_mail_pending, idv_level: idv_level, user: user) + it 'returns false' do + expect(subject.user_has_pending_profile?).to be(false) end + end - context 'with resolved authn context result' do - let(:acr_values) do - [ - Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF, - Saml::Idp::Constants::IAL_VERIFIED_FACIAL_MATCH_REQUIRED_ACR, - ].join(' ') + context 'when facial match is requested' do + context 'when resolved authn context result requires facial match' do + before do + allow(resolved_authn_context_result).to receive(:facial_match?).and_return(true) end - it 'has a usable pending profile' do - expect(policy.user_has_pending_profile?).to eq(true) + [:unsupervised_with_selfie, :in_person].each do |idv_level| + context "when the user has a pending facial match profile with #{idv_level} idv level" do + before do + create(:profile, :verify_by_mail_pending, idv_level: idv_level, user: user) + end + + it 'returns true' do + expect(subject.user_has_pending_profile?).to be(true) + end + end end - context 'with vtr values' do - let(:acr_values) { nil } - let(:vtr) { ['C2.Pb'] } + [:legacy_in_person, :legacy_unsupervised].each do |idv_level| + context "when the user has a pending legacy match profile with #{idv_level} idv level" do + before do + create(:profile, :verify_by_mail_pending, idv_level: idv_level, user: user) + end - it 'has a usable pending profile' do - expect(policy.user_has_pending_profile?).to eq(true) + it 'returns false' do + expect(subject.user_has_pending_profile?).to be(false) + end end end - end - - context 'with facial match comparison requested ACR value' do - let(:acr_values) { Saml::Idp::Constants::IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF } - it 'has a usable pending profile' do - expect(policy.user_has_pending_profile?).to eq(true) + [:in_person, + :unsupervised_with_selfie, + :legacy_in_person, + :legacy_unsupervised].each do |idv_level| + context "user has an active profile with #{idv_level} idv level" do + before do + create(:profile, :active, :verified, idv_level: idv_level, user: user) + end + + it 'returns false' do + expect(subject.user_has_pending_profile?).to eq(false) + end + end end end end - context 'no facial match comparison is requested' do - let(:idv_level) { :legacy_unsupervised } - let(:vtr) { ['C2'] } + context 'when facial match is not requested' do + before do + allow(resolved_authn_context_result).to receive(:facial_match?).and_return(false) + end - context 'user has pending profile' do - before do - create(:profile, :verify_by_mail_pending, idv_level: idv_level, user: user) - end + [:unsupervised_with_selfie].each do |idv_level| + context "when the user has a pending #{idv_level} profile" do + before do + create(:profile, :verify_by_mail_pending, idv_level: idv_level, user: user) + end - it { expect(policy.user_has_pending_profile?).to eq(true) } + it 'returns false' do + expect(subject.user_has_pending_profile?).to eq(false) + end + end end - context 'user has an active profile' do - before do - create(:profile, :active, :verified, idv_level: idv_level, user: user) - end + [:in_person, :legacy_in_person, :legacy_unsupervised].each do |idv_level| + context "when the user has a pending #{idv_level} profile" do + before do + create(:profile, :verify_by_mail_pending, idv_level: idv_level, user: user) + end - it { expect(policy.user_has_pending_profile?).to eq(false) } + it 'returns true' do + expect(subject.user_has_pending_profile?).to eq(true) + end + end end - context 'user has active legacy profile with a pending fraud facial match profile' do - before do - create(:profile, :active, :verified, idv_level: idv_level, user: user) - create(:profile, :fraud_review_pending, idv_level: :unsupervised_with_selfie, user: user) + [:unsupervised_with_selfie].each do |idv_level| + context 'when the user has a pending fraud review profile' do + before do + create(:profile, :fraud_review_pending, idv_level: idv_level, user: user) + end + + it 'returns true' do + expect(subject.user_has_pending_profile?).to eq(true) + end end + end - it { expect(policy.user_has_pending_profile?).to eq(true) } + [:in_person, + :unsupervised_with_selfie, + :legacy_in_person, + :legacy_unsupervised].each do |idv_level| + context "user has an active profile with #{idv_level} idv level" do + before do + create(:profile, :active, :verified, idv_level: idv_level, user: user) + end + + it 'returns false' do + expect(subject.user_has_pending_profile?).to eq(false) + end + end end end end diff --git a/spec/services/form_response_spec.rb b/spec/services/form_response_spec.rb index c18654a5da4..25ddab00b9c 100644 --- a/spec/services/form_response_spec.rb +++ b/spec/services/form_response_spec.rb @@ -179,9 +179,7 @@ response = FormResponse.new(success: false, errors: errors) response_hash = { success: false, - errors: { - email_language: ['Language cannot be blank'], - }, + errors: nil, error_details: { email_language: { blank: true }, }, @@ -197,9 +195,7 @@ response = FormResponse.new(success: false, errors: errors) response_hash = { success: false, - errors: { - email_language: [t('errors.messages.blank')], - }, + errors: nil, error_details: { email_language: { blank: true }, }, @@ -240,9 +236,7 @@ response = FormResponse.new(success: false, errors: errors) response_hash = { success: false, - errors: { - email_language: ['Language cannot be blank'], - }, + errors: nil, error_details: { email_language: { blank: true }, }, diff --git a/spec/services/saml_request_validator_spec.rb b/spec/services/saml_request_validator_spec.rb index 4a5bbf833d6..42619fd20ed 100644 --- a/spec/services/saml_request_validator_spec.rb +++ b/spec/services/saml_request_validator_spec.rb @@ -54,23 +54,12 @@ context 'when it has block_encryption turned on' do before { sp.update!(block_encryption: 'aes256-cbc') } - let(:errors) do - { - service_provider: [t('errors.messages.no_cert_registered')], - } - end - let(:error_details) do - { - service_provider: { - no_cert_registered: true, - }, - } - end it 'returns an error' do expect(response.to_h).to include( - errors:, - error_details:, + success: false, + errors: nil, + error_details: { service_provider: { no_cert_registered: true } }, ) end end @@ -118,14 +107,10 @@ let(:sp) { ServiceProvider.find_by(issuer: 'foo') } it 'returns FormResponse with success: false' do - errors = { - service_provider: [t('errors.messages.unauthorized_service_provider')], - } - expect(response.to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { service_provider: { unauthorized_service_provider: true } }, **extra, ) end @@ -135,14 +120,10 @@ let(:name_id_format) { Saml::Idp::Constants::NAME_ID_FORMAT_EMAIL } it 'returns FormResponse with success: false' do - errors = { - nameid_format: [t('errors.messages.unauthorized_nameid_format')], - } - expect(response.to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { nameid_format: { unauthorized_nameid_format: true } }, **extra, ) end @@ -221,14 +202,10 @@ Saml::Idp::Constants::PASSWORD_AUTHN_CONTEXT_CLASSREFS.each do |password_context| let(:authn_context) { [password_context] } it 'returns FormResponse with success: false for unknown authn context' do - errors = { - authn_context: [t('errors.messages.unauthorized_authn_context')], - } - expect(response.to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { authn_context: { unauthorized_authn_context: true } }, **extra, ) end @@ -240,14 +217,10 @@ let(:authn_context) { ['IAL1'] } it 'returns FormResponse with success: false' do - errors = { - authn_context: [t('errors.messages.unauthorized_authn_context')], - } - expect(response.to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { authn_context: { unauthorized_authn_context: true } }, **extra, ) end @@ -273,14 +246,10 @@ let(:authn_context) { [ial_value] } it 'returns FormResponse with success: false' do - errors = { - authn_context: [t('errors.messages.unauthorized_authn_context')], - } - expect(response.to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { authn_context: { unauthorized_authn_context: true } }, **extra, ) end @@ -291,14 +260,10 @@ let(:authn_context) { [Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF] } it 'returns FormResponse with success: false' do - errors = { - authn_context: [t('errors.messages.unauthorized_authn_context')], - } - expect(response.to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { authn_context: { unauthorized_authn_context: true } }, **extra, ) end @@ -331,14 +296,10 @@ end it 'fails with an unauthorized error' do - errors = { - authn_context: [t('errors.messages.unauthorized_authn_context')], - } - expect(response.to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { authn_context: { unauthorized_authn_context: true } }, **extra, ) end @@ -390,15 +351,13 @@ let(:authn_context) { ['IAL1'] } it 'returns FormResponse with success: false' do - errors = { - authn_context: [t('errors.messages.unauthorized_authn_context')], - service_provider: [t('errors.messages.unauthorized_service_provider')], - } - expect(response.to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { + authn_context: { unauthorized_authn_context: true }, + service_provider: { unauthorized_service_provider: true }, + }, **extra, ) end @@ -408,14 +367,10 @@ let(:name_id_format) { Saml::Idp::Constants::NAME_ID_FORMAT_EMAIL } it 'returns FormResponse with success: false with unauthorized nameid format' do - errors = { - nameid_format: [t('errors.messages.unauthorized_nameid_format')], - } - expect(response.to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { nameid_format: { unauthorized_nameid_format: true } }, **extra, ) end @@ -453,14 +408,10 @@ before { sp.update!(ial: 1) } it 'returns FormResponse with success false' do - errors = { - authn_context: [t('errors.messages.unauthorized_authn_context')], - } - expect(response.to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { authn_context: { unauthorized_authn_context: true } }, **extra, ) end @@ -472,14 +423,10 @@ before { sp.update!(ial: 1) } it 'returns FormResponse with success false' do - errors = { - authn_context: [t('errors.messages.unauthorized_authn_context')], - } - expect(response.to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { authn_context: { unauthorized_authn_context: true } }, **extra, ) end @@ -490,14 +437,10 @@ let(:authn_context) { ['C1'] } it 'returns FormResponse with success false' do - errors = { - authn_context: [t('errors.messages.unauthorized_authn_context')], - } - expect(response.to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { authn_context: { unauthorized_authn_context: true } }, **extra, ) end @@ -507,14 +450,10 @@ let(:authn_context) { ['Fa.Ke.Va.Lu.E0'] } it 'returns FormResponse with success false' do - errors = { - authn_context: [t('errors.messages.unauthorized_authn_context')], - } - expect(response.to_h).to include( success: false, - errors: errors, - error_details: hash_including(*errors.keys), + errors: nil, + error_details: { authn_context: { unauthorized_authn_context: true } }, **extra, ) end diff --git a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb index a0aee910a79..f3d5053aa2d 100644 --- a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb +++ b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb @@ -382,4 +382,53 @@ end end end + + describe '#cancel_establishing_and_pending_enrollments' do + context 'when the user has an establishing in-person enrollment' do + let!(:enrollment) { create(:in_person_enrollment, :establishing, user: user) } + + before do + subject.cancel_establishing_and_pending_enrollments(user) + end + + it "cancels the user's establishing in-person enrollment" do + expect(enrollment.reload.status).to eq('cancelled') + end + end + + context 'when the user has a pending in-person enrollment' do + let!(:enrollment) { create(:in_person_enrollment, :pending, user: user) } + + before do + subject.cancel_establishing_and_pending_enrollments(user) + end + + it "cancels the user's pending in-person enrollment" do + expect(enrollment.reload.status).to eq('cancelled') + end + end + + context 'when the user has both establishing and pending in-person enrollments' do + let!(:establishing_enrollment) { create(:in_person_enrollment, :establishing, user: user) } + let!(:pending_enrollment) { create(:in_person_enrollment, :pending, user: user) } + + before do + subject.cancel_establishing_and_pending_enrollments(user) + end + + it "cancels the user's establishing in-person enrollment" do + expect(establishing_enrollment.reload.status).to eq('cancelled') + end + + it "cancels the user's pending in-person enrollment" do + expect(pending_enrollment.reload.status).to eq('cancelled') + end + end + + context 'when the user has no establishing and pending in-person enrollments' do + it 'does not throw an error' do + expect { subject.cancel_establishing_and_pending_enrollments(user) }.not_to raise_error + end + end + end end diff --git a/yarn.lock b/yarn.lock index 35f0f0896e1..cef33b89d49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4081,10 +4081,10 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -libphonenumber-js@^1.11.18: - version "1.11.18" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.18.tgz#0ce5188a76487fae01afdf318255783cde36501e" - integrity sha512-okMm/MCoFrm1vByeVFLBdkFIXLSHy/AIK2AEGgY3eoicfWZeOZqv3GfhtQgICkzs/tqorAMm3a4GBg5qNCrqzg== +libphonenumber-js@^1.11.19: + version "1.11.19" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.19.tgz#de6437b15d23d9e3b16e5b14c6f2d6d65f58c9b7" + integrity sha512-bW/Yp/9dod6fmyR+XqSUL1N5JE7QRxQ3KrBIbYS1FTv32e5i3SEtQVX+71CYNv8maWNSOgnlCoNp9X78f/cKiA== lightningcss-darwin-arm64@1.23.0: version "1.23.0"