diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index b7db25bc899..c70358fb14f 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -85,10 +85,11 @@ def signup_with_your_email end end - def reset_password_instructions(token:) + def reset_password_instructions(token:, request_id:) with_user_locale(user) do @locale = locale_url_param @token = token + @request_id = request_id @pending_profile_requires_verification = user.decorate.pending_profile_requires_verification? @hide_title = @pending_profile_requires_verification mail(to: email_address.email, subject: t('user_mailer.reset_password_instructions.subject')) diff --git a/app/services/request_password_reset.rb b/app/services/request_password_reset.rb index 17bba0d727d..4acbfa3045e 100644 --- a/app/services/request_password_reset.rb +++ b/app/services/request_password_reset.rb @@ -28,6 +28,7 @@ def send_reset_password_instructions token = user.set_reset_password_token UserMailer.with(user: user, email_address: email_address_record).reset_password_instructions( token: token, + request_id: request_id, ).deliver_now_or_later event = PushNotification::RecoveryActivatedEvent.new(user: user) diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb index c23be3588e6..f57684bf32d 100644 --- a/app/views/devise/passwords/edit.html.erb +++ b/app/views/devise/passwords/edit.html.erb @@ -1,5 +1,7 @@ <% title t('titles.passwords.change') %> +<% request_id = params[:request_id] || sp_session[:request_id] %> + <%= render PageHeadingComponent.new.with_content(t('headings.passwords.change')) %>
<%= t('instructions.password.password_key') %>
@@ -19,6 +21,7 @@ required: true, }, ) %> + <%= hidden_field_tag('request_id', request_id) %> <%= render 'devise/shared/password_strength', forbidden_passwords: @forbidden_passwords %> <%= f.submit t('forms.passwords.edit.buttons.submit'), class: 'display-block margin-y-5' %> <% end %> diff --git a/app/views/user_mailer/reset_password_instructions.html.erb b/app/views/user_mailer/reset_password_instructions.html.erb index c109b8090b7..89826627c92 100644 --- a/app/views/user_mailer/reset_password_instructions.html.erb +++ b/app/views/user_mailer/reset_password_instructions.html.erb @@ -33,7 +33,7 @@- <%= link_to edit_user_password_url(reset_password_token: @token, locale: @locale), - edit_user_password_url(reset_password_token: @token, locale: @locale), + <%= link_to edit_user_password_url(reset_password_token: @token, locale: @locale, request_id: @request_id), + edit_user_password_url(reset_password_token: @token, locale: @locale, request_id: @request_id), target: '_blank', rel: 'noopener' %>
diff --git a/spec/features/irs_attempts_api/event_tracking_spec.rb b/spec/features/irs_attempts_api/event_tracking_spec.rb index 662da28e382..94ff714dcf4 100644 --- a/spec/features/irs_attempts_api/event_tracking_spec.rb +++ b/spec/features/irs_attempts_api/event_tracking_spec.rb @@ -126,4 +126,53 @@ expect(events.count).to eq(0) end end + + scenario 'reset password from an IRS with new browser session and request_id tracks events' do + freeze_time do + user = create(:user, :signed_up) + visit_idp_from_ial1_oidc_sp( + client_id: service_provider.issuer, + irs_attempts_api_session_id: 'test-session-id', + ) + + visit root_path + fill_forgot_password_form(user) + set_new_browser_session + click_reset_password_link_from_email + fill_reset_password_form + + events = irs_attempts_api_tracked_events(timestamp: Time.zone.now) + expected_event_types = %w[forgot-password-email-sent forgot-password-email-confirmed + forgot-password-new-password-submitted] + received_event_types = events.map(&:event_type) + + expect(events.count).to eq received_event_types.count + expect(received_event_types).to match_array(expected_event_types) + end + end + + # rubocop:disable Layout/LineLength + scenario 'reset password from an IRS with new browser session and without request_id does not track event' do + freeze_time do + user = create(:user, :signed_up) + visit_idp_from_ial1_oidc_sp( + client_id: service_provider.issuer, + irs_attempts_api_session_id: 'test-session-id', + ) + + visit root_path + fill_forgot_password_form(user) + set_new_browser_session + click_reset_password_link_from_email + fill_reset_password_form(without_request_id: true) + + events = irs_attempts_api_tracked_events(timestamp: Time.zone.now) + expected_event_types = %w[forgot-password-email-sent forgot-password-email-confirmed] + received_event_types = events.map(&:event_type) + + expect(events.count).to eq received_event_types.count + expect(received_event_types).to match_array(expected_event_types) + end + end + # rubocop:enable Layout/LineLength end diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb index 0e1fc3d60ff..f15c07b2210 100644 --- a/spec/mailers/previews/user_mailer_preview.rb +++ b/spec/mailers/previews/user_mailer_preview.rb @@ -28,7 +28,7 @@ def signup_with_your_email def reset_password_instructions UserMailer.with(user: user, email_address: email_address_record).reset_password_instructions( - token: SecureRandom.hex, + token: SecureRandom.hex, request_id: SecureRandom.hex, ) end diff --git a/spec/support/features/session_helper.rb b/spec/support/features/session_helper.rb index e7e2df844a3..b9c21c962aa 100644 --- a/spec/support/features/session_helper.rb +++ b/spec/support/features/session_helper.rb @@ -657,5 +657,37 @@ def set_new_browser_session # For when we want to login from a new browser to avoid the default 'remember device' behavior Capybara.reset_session! end + + def fill_forgot_password_form(user) + click_link t('links.passwords.forgot') + fill_in t('account.index.email'), with: user.email + click_button t('forms.buttons.continue') + + expect(current_path).to eq forgot_password_path + end + + def click_reset_password_link_from_email + expect(last_email.subject).to eq t('user_mailer.reset_password_instructions.subject') + expect(last_email.html_part.body).to include MarketingSite.help_url + expect(last_email.html_part.body).to have_content( + t( + 'user_mailer.reset_password_instructions.footer', + expires: (Devise.reset_password_within / 3600), + ), + ) + open_last_email + click_email_link_matching(/reset_password_token/) + + expect(page.html).not_to include(t('notices.dap_participation')) + expect(current_path).to eq edit_user_password_path + end + + def fill_reset_password_form(without_request_id: nil) + fill_in t('forms.passwords.edit.labels.password'), with: 'newVal!dPassw0rd' + find_field('request_id', type: :hidden).set(nil) if without_request_id + click_button t('forms.passwords.edit.buttons.submit') + + expect(current_path).to eq new_user_session_path + end end end