From 82adf9ee538df2e9e30943be40144fa53b7d88b9 Mon Sep 17 00:00:00 2001 From: Luis Date: Tue, 27 Jun 2023 16:52:31 -0500 Subject: [PATCH 1/7] LG-9934 Prevent suspended user from reset password [skip changelog] --- app/services/request_password_reset.rb | 2 +- spec/services/request_password_reset_spec.rb | 40 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/app/services/request_password_reset.rb b/app/services/request_password_reset.rb index aa2b588d369..336d097f40d 100644 --- a/app/services/request_password_reset.rb +++ b/app/services/request_password_reset.rb @@ -26,7 +26,7 @@ def send_reset_password_instructions if throttle.throttled? analytics.throttler_rate_limit_triggered(throttle_type: :reset_password_email) irs_attempts_api_tracker.forgot_password_email_rate_limited(email: email) - else + elsif !user.suspended? token = user.set_reset_password_token UserMailer.with(user: user, email_address: email_address_record).reset_password_instructions( token: token, diff --git a/spec/services/request_password_reset_spec.rb b/spec/services/request_password_reset_spec.rb index 6534cb77f8b..e84c8462035 100644 --- a/spec/services/request_password_reset_spec.rb +++ b/spec/services/request_password_reset_spec.rb @@ -81,6 +81,46 @@ end end + context 'when the user is found and confirmed, but is suspended' do + subject(:perform) do + described_class.new( + email: email, + irs_attempts_api_tracker: irs_attempts_api_tracker, + ).perform + end + + before do + user.suspend! + allow(UserMailer).to receive(:reset_password_instructions). + and_wrap_original do |impl, user, email, options| + token = options.fetch(:token) + expect(token).to be_present + expect(Devise.token_generator.digest(User, :reset_password_token, token)). + to eq(user.reset_password_token) + + impl.call(user, email, **options) + end + end + + it 'does not send password reset instructions' do + expect { subject }. + not_to(change { user.reload.reset_password_token }) + end + + it 'does not send a recovery activated push event' do + expect(PushNotification::HttpPush).not_to receive(:deliver). + with(PushNotification::RecoveryActivatedEvent.new(user: user)) + + subject + end + + it 'does not call irs tracking method forgot_password_email_sent' do + subject + + expect(irs_attempts_api_tracker).not_to have_received(:forgot_password_email_sent) + end + end + context 'when the user is found, not privileged, and not yet confirmed' do it 'sends password reset instructions' do allow(UserMailer).to receive(:reset_password_instructions). From 90f56430677fdad4b85a1b662196a8f1fc66f5ca Mon Sep 17 00:00:00 2001 From: Matt Wagner Date: Wed, 28 Jun 2023 11:27:32 -0400 Subject: [PATCH 2/7] changelog: Internal, Account suspension, Don't send reset emails to suspended users From bcdb6d16d47115b116b58a4d5735ecc920369407 Mon Sep 17 00:00:00 2001 From: Luis Date: Wed, 5 Jul 2023 11:58:24 -0500 Subject: [PATCH 3/7] Add email for suspended user --- app/mailers/user_mailer.rb | 6 +++++ app/services/request_password_reset.rb | 8 +++++- .../suspended_reset_password.html.erb | 15 +++++++++++ config/application.yml.default | 1 + config/locales/user_mailer/en.yml | 4 +++ config/locales/user_mailer/es.yml | 4 +++ config/locales/user_mailer/fr.yml | 4 +++ lib/identity_config.rb | 1 + spec/mailers/previews/user_mailer_preview.rb | 7 +++++ spec/mailers/user_mailer_spec.rb | 27 +++++++++++++++++++ spec/models/user_spec.rb | 2 +- spec/services/request_password_reset_spec.rb | 18 ++++++++----- 12 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 app/views/user_mailer/suspended_reset_password.html.erb diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 1844917f7f1..d4c618b6b6c 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -376,6 +376,12 @@ def account_rejected end end + def suspended_reset_password + with_user_locale(user) do + mail(to: email_address.email, subject: t('user_mailer.suspended_reset_password.subject')) + end + end + private def email_should_receive_nonessential_notifications?(email) diff --git a/app/services/request_password_reset.rb b/app/services/request_password_reset.rb index 336d097f40d..10c0183acd8 100644 --- a/app/services/request_password_reset.rb +++ b/app/services/request_password_reset.rb @@ -26,7 +26,13 @@ def send_reset_password_instructions if throttle.throttled? analytics.throttler_rate_limit_triggered(throttle_type: :reset_password_email) irs_attempts_api_tracker.forgot_password_email_rate_limited(email: email) - elsif !user.suspended? + elsif user.suspended? + puts("HEREHEREHERE!") + UserMailer.with( + user: user, + email_address: email_address_record, + ).suspended_reset_password().deliver_now_or_later + else token = user.set_reset_password_token UserMailer.with(user: user, email_address: email_address_record).reset_password_instructions( token: token, diff --git a/app/views/user_mailer/suspended_reset_password.html.erb b/app/views/user_mailer/suspended_reset_password.html.erb new file mode 100644 index 00000000000..d859e5198ae --- /dev/null +++ b/app/views/user_mailer/suspended_reset_password.html.erb @@ -0,0 +1,15 @@ + + + + + + + + +
+   +
+ +

+<%= t('user_mailer.suspended_reset_password.message', support_code: IdentityConfig.store.account_suspended_support_code, contact_number: IdentityConfig.store.idv_contact_phone_number) %> +

\ No newline at end of file diff --git a/config/application.yml.default b/config/application.yml.default index 0ab6bf8714c..4a9b63a4dad 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -24,6 +24,7 @@ aamva_verification_url: https://example.org:12345/verification/url all_redirect_uris_cache_duration_minutes: 2 account_reset_token_valid_for_days: 1 account_reset_wait_period_days: 1 +account_suspended_support_code: EFGHI acuant_maintenance_window_start: acuant_maintenance_window_finish: acuant_assure_id_password: '' diff --git a/config/locales/user_mailer/en.yml b/config/locales/user_mailer/en.yml index 47455638ed7..783d2988642 100644 --- a/config/locales/user_mailer/en.yml +++ b/config/locales/user_mailer/en.yml @@ -284,3 +284,7 @@ en: in with this email address, you can ignore this message. link_text: Go to %{app_name} reset_password_html: If you can’t remember your password, go to %{app_name} to reset it. + suspended_reset_password: + header: We couldn’t reset your password + message: There was an issue resetting your password. Please give our contact center a call at %{contact_number} and provide this code - %{support_code}. + subject: We couldn’t reset your password diff --git a/config/locales/user_mailer/es.yml b/config/locales/user_mailer/es.yml index b6b8b12f536..7f3a116cb5e 100644 --- a/config/locales/user_mailer/es.yml +++ b/config/locales/user_mailer/es.yml @@ -304,3 +304,7 @@ es: una sesión con este email, puede ignorar este mensaje. link_text: Ir a %{app_name} reset_password_html: Si no recuerda su contraseña, vaya a %{app_name} para restablecerla. + suspended_reset_password: + header: No pudimos restablecer su contraseña + message: Se produjo un problema al restablecer su contraseña. Llame a nuestro centro de atención al número %{contact_number} y proporcione este código - %{support_code}. + subject: No pudimos restablecer su contraseña diff --git a/config/locales/user_mailer/fr.yml b/config/locales/user_mailer/fr.yml index 46e5293f5ee..f44b538fec2 100644 --- a/config/locales/user_mailer/fr.yml +++ b/config/locales/user_mailer/fr.yml @@ -316,3 +316,7 @@ fr: link_text: Allez à %{app_name} reset_password_html: Si vous ne vous souvenez plus de votre mot de passe, allez à %{app_name} pour le réinitialiser. + suspended_reset_password: + header: Nous n’avons pas pu réinitialiser votre mot de passe + message: La réinitialisation de votre mot de passe a posé un problème. Veuillez appeler notre centre d’appels au %{contact_number} et fournir ce code - %{support_code}. + subject: Nous n’avons pas pu réinitialiser votre mot de passe diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 394a5f89ad5..749d10aebb9 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -97,6 +97,7 @@ def self.build_store(config_map) config.add(:all_redirect_uris_cache_duration_minutes, type: :integer) config.add(:account_reset_token_valid_for_days, type: :integer) config.add(:account_reset_wait_period_days, type: :integer) + config.add(:account_suspended_support_code, type: :string) config.add(:acuant_maintenance_window_start, type: :timestamp, allow_nil: true) config.add(:acuant_maintenance_window_finish, type: :timestamp, allow_nil: true) config.add(:acuant_assure_id_password) diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb index 9460a4fff30..89b8e152aa0 100644 --- a/spec/mailers/previews/user_mailer_preview.rb +++ b/spec/mailers/previews/user_mailer_preview.rb @@ -32,6 +32,13 @@ def reset_password_instructions ) end + def suspended_reset_password + UserMailer.with( + user: user, + email_address: email_address_record, + ).suspended_reset_password + end + def password_changed UserMailer.with(user: user, email_address: email_address_record). password_changed(disavowal_token: SecureRandom.hex) diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 847e272b65a..ccf4d1487c9 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -762,6 +762,33 @@ def expect_email_body_to_have_help_and_contact_links end end + describe '#suspended_reset_password' do + let(:mail) do + UserMailer.with(user: user, email_address: email_address). + suspended_reset_password + end + + it_behaves_like 'a system email' + it_behaves_like 'an email that respects user email locale preference' + + it 'sends to the specified email' do + expect(mail.to).to eq [email_address.email] + end + + it 'renders the subject' do + expect(mail.subject).to eq t('user_mailer.suspended_reset_password.subject') + end + + it 'renders the body' do + expect(mail.html_part.body). + to have_content( + t( + 'user_mailer.suspended_reset_password.message', + support_code: IdentityConfig.store.account_suspended_support_code, contact_number: IdentityConfig.store.idv_contact_phone_number + ), + ) end + end + describe '#deliver_later' do it 'does not queue email if it potentially contains sensitive value' do user = create(:user) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index aa6de66bba3..3185e805989 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -774,7 +774,7 @@ end describe '#suspend!' do - context 'user is not already supsended' do + context 'user is not already suspended' do let(:mock_session_id) { SecureRandom.uuid } before do UpdateUser.new(user: user, attributes: { unique_session_id: mock_session_id }).call diff --git a/spec/services/request_password_reset_spec.rb b/spec/services/request_password_reset_spec.rb index e84c8462035..e1c648056dd 100644 --- a/spec/services/request_password_reset_spec.rb +++ b/spec/services/request_password_reset_spec.rb @@ -62,7 +62,7 @@ end end - it 'sends password reset instructions' do + it 'sets password reset token' do expect { subject }. to(change { user.reload.reset_password_token }) end @@ -91,22 +91,28 @@ before do user.suspend! - allow(UserMailer).to receive(:reset_password_instructions). + allow(UserMailer).to receive(:suspended_reset_password). and_wrap_original do |impl, user, email, options| token = options.fetch(:token) - expect(token).to be_present - expect(Devise.token_generator.digest(User, :reset_password_token, token)). - to eq(user.reset_password_token) + expect(token).not_to be_present impl.call(user, email, **options) end end - it 'does not send password reset instructions' do + it 'does not set a password reset token' do expect { subject }. not_to(change { user.reload.reset_password_token }) end + it 'sends the correct email to the suspended user' do + allow(UserMailer).to receive(:suspended_reset_password) + + subject + + expect(UserMailer).to have_received(:suspended_reset_password) + end + it 'does not send a recovery activated push event' do expect(PushNotification::HttpPush).not_to receive(:deliver). with(PushNotification::RecoveryActivatedEvent.new(user: user)) From 1645600c2618d79fe2bd19adb6c51493c27ad55f Mon Sep 17 00:00:00 2001 From: Luis Date: Wed, 5 Jul 2023 14:09:12 -0500 Subject: [PATCH 4/7] Lint fixes --- app/services/request_password_reset.rb | 3 +-- spec/mailers/user_mailer_spec.rb | 15 ++++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/services/request_password_reset.rb b/app/services/request_password_reset.rb index 10c0183acd8..ad2ca7ed5fd 100644 --- a/app/services/request_password_reset.rb +++ b/app/services/request_password_reset.rb @@ -27,11 +27,10 @@ def send_reset_password_instructions analytics.throttler_rate_limit_triggered(throttle_type: :reset_password_email) irs_attempts_api_tracker.forgot_password_email_rate_limited(email: email) elsif user.suspended? - puts("HEREHEREHERE!") UserMailer.with( user: user, email_address: email_address_record, - ).suspended_reset_password().deliver_now_or_later + ).suspended_reset_password.deliver_now_or_later else token = user.set_reset_password_token UserMailer.with(user: user, email_address: email_address_record).reset_password_instructions( diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index ccf4d1487c9..1f7acd12fd1 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -765,7 +765,7 @@ def expect_email_body_to_have_help_and_contact_links describe '#suspended_reset_password' do let(:mail) do UserMailer.with(user: user, email_address: email_address). - suspended_reset_password + suspended_reset_password end it_behaves_like 'a system email' @@ -781,12 +781,13 @@ def expect_email_body_to_have_help_and_contact_links it 'renders the body' do expect(mail.html_part.body). - to have_content( - t( - 'user_mailer.suspended_reset_password.message', - support_code: IdentityConfig.store.account_suspended_support_code, contact_number: IdentityConfig.store.idv_contact_phone_number - ), - ) end + to have_content( + t( + 'user_mailer.suspended_reset_password.message', + support_code: IdentityConfig.store.account_suspended_support_code, contact_number: IdentityConfig.store.idv_contact_phone_number, + ), + ) + end end describe '#deliver_later' do From 2eb51f5a0e9fdcdc9097f3df5e201eab81513571 Mon Sep 17 00:00:00 2001 From: Luis Date: Wed, 5 Jul 2023 14:10:50 -0500 Subject: [PATCH 5/7] Format fixes --- app/views/user_mailer/suspended_reset_password.html.erb | 4 +--- spec/mailers/user_mailer_spec.rb | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/views/user_mailer/suspended_reset_password.html.erb b/app/views/user_mailer/suspended_reset_password.html.erb index d859e5198ae..aa33ddbe304 100644 --- a/app/views/user_mailer/suspended_reset_password.html.erb +++ b/app/views/user_mailer/suspended_reset_password.html.erb @@ -1,5 +1,3 @@ - - @@ -12,4 +10,4 @@

<%= t('user_mailer.suspended_reset_password.message', support_code: IdentityConfig.store.account_suspended_support_code, contact_number: IdentityConfig.store.idv_contact_phone_number) %> -

\ No newline at end of file +

diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 1f7acd12fd1..13f95464e00 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -784,7 +784,8 @@ def expect_email_body_to_have_help_and_contact_links to have_content( t( 'user_mailer.suspended_reset_password.message', - support_code: IdentityConfig.store.account_suspended_support_code, contact_number: IdentityConfig.store.idv_contact_phone_number, + support_code: IdentityConfig.store.account_suspended_support_code, + contact_number: IdentityConfig.store.idv_contact_phone_number, ), ) end From 8e2929a8363d7d3ac479607dd00f605d908e2e38 Mon Sep 17 00:00:00 2001 From: Luis Date: Wed, 5 Jul 2023 14:25:51 -0500 Subject: [PATCH 6/7] Spec and lint fixes --- config/locales/user_mailer/en.yml | 4 +++- config/locales/user_mailer/es.yml | 4 +++- config/locales/user_mailer/fr.yml | 4 +++- spec/services/request_password_reset_spec.rb | 17 +++++++++++------ 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/config/locales/user_mailer/en.yml b/config/locales/user_mailer/en.yml index 783d2988642..ff493c2dab7 100644 --- a/config/locales/user_mailer/en.yml +++ b/config/locales/user_mailer/en.yml @@ -286,5 +286,7 @@ en: reset_password_html: If you can’t remember your password, go to %{app_name} to reset it. suspended_reset_password: header: We couldn’t reset your password - message: There was an issue resetting your password. Please give our contact center a call at %{contact_number} and provide this code - %{support_code}. + message: There was an issue resetting your password. Please give our contact + center a call at %{contact_number} and provide this code - + %{support_code}. subject: We couldn’t reset your password diff --git a/config/locales/user_mailer/es.yml b/config/locales/user_mailer/es.yml index 7f3a116cb5e..776d08be207 100644 --- a/config/locales/user_mailer/es.yml +++ b/config/locales/user_mailer/es.yml @@ -306,5 +306,7 @@ es: reset_password_html: Si no recuerda su contraseña, vaya a %{app_name} para restablecerla. suspended_reset_password: header: No pudimos restablecer su contraseña - message: Se produjo un problema al restablecer su contraseña. Llame a nuestro centro de atención al número %{contact_number} y proporcione este código - %{support_code}. + message: Se produjo un problema al restablecer su contraseña. Llame a nuestro + centro de atención al número %{contact_number} y proporcione este código + - %{support_code}. subject: No pudimos restablecer su contraseña diff --git a/config/locales/user_mailer/fr.yml b/config/locales/user_mailer/fr.yml index f44b538fec2..b42f450869c 100644 --- a/config/locales/user_mailer/fr.yml +++ b/config/locales/user_mailer/fr.yml @@ -318,5 +318,7 @@ fr: à %{app_name} pour le réinitialiser. suspended_reset_password: header: Nous n’avons pas pu réinitialiser votre mot de passe - message: La réinitialisation de votre mot de passe a posé un problème. Veuillez appeler notre centre d’appels au %{contact_number} et fournir ce code - %{support_code}. + message: La réinitialisation de votre mot de passe a posé un problème. Veuillez + appeler notre centre d’appels au %{contact_number} et fournir ce code - + %{support_code}. subject: Nous n’avons pas pu réinitialiser votre mot de passe diff --git a/spec/services/request_password_reset_spec.rb b/spec/services/request_password_reset_spec.rb index e1c648056dd..912dc8005af 100644 --- a/spec/services/request_password_reset_spec.rb +++ b/spec/services/request_password_reset_spec.rb @@ -91,6 +91,15 @@ before do user.suspend! + allow(UserMailer).to receive(:reset_password_instructions). + and_wrap_original do |impl, user, email, options| + token = options.fetch(:token) + expect(token).to be_present + expect(Devise.token_generator.digest(User, :reset_password_token, token)). + to eq(user.reset_password_token) + + impl.call(user, email, **options) + end allow(UserMailer).to receive(:suspended_reset_password). and_wrap_original do |impl, user, email, options| token = options.fetch(:token) @@ -105,12 +114,8 @@ not_to(change { user.reload.reset_password_token }) end - it 'sends the correct email to the suspended user' do - allow(UserMailer).to receive(:suspended_reset_password) - - subject - - expect(UserMailer).to have_received(:suspended_reset_password) + it 'sends an email to the suspended user' do + expect { subject }.to change { ActionMailer::Base.deliveries.count }.by(1) end it 'does not send a recovery activated push event' do From 3a4ba4e76fa5cd3182169fc442cb810b650beb8f Mon Sep 17 00:00:00 2001 From: Luis Date: Wed, 5 Jul 2023 14:58:15 -0500 Subject: [PATCH 7/7] Spec fix --- config/locales/user_mailer/en.yml | 1 - config/locales/user_mailer/es.yml | 1 - config/locales/user_mailer/fr.yml | 1 - 3 files changed, 3 deletions(-) diff --git a/config/locales/user_mailer/en.yml b/config/locales/user_mailer/en.yml index ff493c2dab7..36c6dbde09a 100644 --- a/config/locales/user_mailer/en.yml +++ b/config/locales/user_mailer/en.yml @@ -285,7 +285,6 @@ en: link_text: Go to %{app_name} reset_password_html: If you can’t remember your password, go to %{app_name} to reset it. suspended_reset_password: - header: We couldn’t reset your password message: There was an issue resetting your password. Please give our contact center a call at %{contact_number} and provide this code - %{support_code}. diff --git a/config/locales/user_mailer/es.yml b/config/locales/user_mailer/es.yml index 776d08be207..dfe89bf2622 100644 --- a/config/locales/user_mailer/es.yml +++ b/config/locales/user_mailer/es.yml @@ -305,7 +305,6 @@ es: link_text: Ir a %{app_name} reset_password_html: Si no recuerda su contraseña, vaya a %{app_name} para restablecerla. suspended_reset_password: - header: No pudimos restablecer su contraseña message: Se produjo un problema al restablecer su contraseña. Llame a nuestro centro de atención al número %{contact_number} y proporcione este código - %{support_code}. diff --git a/config/locales/user_mailer/fr.yml b/config/locales/user_mailer/fr.yml index b42f450869c..c4575e15f2a 100644 --- a/config/locales/user_mailer/fr.yml +++ b/config/locales/user_mailer/fr.yml @@ -317,7 +317,6 @@ fr: reset_password_html: Si vous ne vous souvenez plus de votre mot de passe, allez à %{app_name} pour le réinitialiser. suspended_reset_password: - header: Nous n’avons pas pu réinitialiser votre mot de passe message: La réinitialisation de votre mot de passe a posé un problème. Veuillez appeler notre centre d’appels au %{contact_number} et fournir ce code - %{support_code}.