18F/identity-idp

View on GitHub
app/mailers/user_mailer.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
# frozen_string_literal: true

# UserMailer handles all email sending to the User class. It expects to be called using `with`
# that receives a `user` and `email_address`. This pattern is preferred as the User and
# EmailAddress database records are needed across any email being sent.
#
# Arguments sent to UserMailer must not include personally-identifiable information (PII).
# This includes email addresses. All arguments to UserMailer are stored in the database when the
# email is being sent asynchronously by ActiveJob and we must not put PII in the database in
# plaintext.
#
# Example:
#
#   UserMailer.with(user: user, email_address: email_address).
#     reset_password_instructions(token: token)
#
class UserMailer < ActionMailer::Base
  include Mailable
  include LocaleHelper
  include AccountResetConcern
  include ActionView::Helpers::DateHelper

  class UserEmailAddressMismatchError < StandardError; end

  attr_reader :user, :email_address

  before_action :validate_user_and_email_address
  before_action :attach_images
  after_action :add_metadata
  default(
    from: email_with_name(
      IdentityConfig.store.email_from,
      IdentityConfig.store.email_from_display_name,
    ),
    reply_to: email_with_name(
      IdentityConfig.store.email_from,
      IdentityConfig.store.email_from_display_name,
    ),
  )

  layout 'mailer'

  def validate_user_and_email_address
    @user = params.fetch(:user)
    @email_address = params.fetch(:email_address)
    if @user.id != @email_address.user_id
      raise UserEmailAddressMismatchError.new(
        "User ID #{@user.id} does not match EmailAddress ID #{@email_address.id}",
      )
    end
  end

  def add_metadata
    message.instance_variable_set(
      :@_metadata, {
        user: user, email_address: email_address, action: action_name
      }
    )
  end

  def email_confirmation_instructions(token, request_id:)
    with_user_locale(user) do
      presenter = ConfirmationEmailPresenter.new(user, view_context)
      @first_sentence = presenter.first_sentence
      @confirmation_period = presenter.confirmation_period
      @request_id = request_id
      @locale = locale_url_param
      @token = token
      mail(
        to: email_address.email,
        subject: t('user_mailer.email_confirmation_instructions.subject'),
      )
    end
  end

  def signup_with_your_email(request_id:)
    with_user_locale(user) do
      @root_url = root_url(locale: locale_url_param, request_id: request_id)
      mail(to: email_address.email, subject: t('mailer.email_reuse_notice.subject'))
    end
  end

  def reset_password_instructions(token:, request_id:)
    with_user_locale(user) do
      @locale = locale_url_param
      @token = token
      @request_id = request_id
      @gpo_verification_pending_profile = user.gpo_verification_pending_profile?
      @hide_title = @gpo_verification_pending_profile
      mail(to: email_address.email, subject: t('user_mailer.reset_password_instructions.subject'))
    end
  end

  def password_changed(disavowal_token:)
    with_user_locale(user) do
      @disavowal_token = disavowal_token
      mail(to: email_address.email, subject: t('devise.mailer.password_updated.subject'))
    end
  end

  def phone_added(disavowal_token:)
    with_user_locale(user) do
      @disavowal_token = disavowal_token
      mail(to: email_address.email, subject: t('user_mailer.phone_added.subject'))
    end
  end

  def personal_key_sign_in(disavowal_token:)
    with_user_locale(user) do
      @disavowal_token = disavowal_token
      mail(to: email_address.email, subject: t('user_mailer.personal_key_sign_in.subject'))
    end
  end

  # @param [Array<Hash>] events Array of sign-in Event records (event types "sign_in_before_2fa",
  # "sign_in_after_2fa", "sign_in_unsuccessful_2fa")
  # @param [String] disavowal_token Token to generate URL for disavowing event
  def new_device_sign_in_after_2fa(events:, disavowal_token:)
    with_user_locale(user) do
      @events = events
      @disavowal_token = disavowal_token

      mail(
        to: email_address.email,
        subject: t('user_mailer.new_device_sign_in_after_2fa.subject', app_name: APP_NAME),
      )
    end
  end

  # @param [Array<Hash>] events Array of sign-in Event records (event types "sign_in_before_2fa",
  # "sign_in_after_2fa", "sign_in_unsuccessful_2fa")
  # @param [String] disavowal_token Token to generate URL for disavowing event
  def new_device_sign_in_before_2fa(events:, disavowal_token:)
    with_user_locale(user) do
      @events = events
      @disavowal_token = disavowal_token
      @failed_times = events.count { |event| event.event_type == 'sign_in_unsuccessful_2fa' }

      mail(
        to: email_address.email,
        subject: t('user_mailer.new_device_sign_in_before_2fa.subject', app_name: APP_NAME),
      )
    end
  end

  def personal_key_regenerated
    with_user_locale(user) do
      mail(to: email_address.email, subject: t('user_mailer.personal_key_regenerated.subject'))
    end
  end

  def account_reset_request(account_reset)
    with_user_locale(user) do
      @token = account_reset&.request_token
      @account_reset_deletion_period_interval = account_reset_deletion_period_interval(user)
      @header = t(
        'user_mailer.account_reset_request.header',
        interval: @account_reset_deletion_period_interval,
      )
      mail(
        to: email_address.email,
        subject: t('user_mailer.account_reset_request.subject', app_name: APP_NAME),
      )
    end
  end

  def account_reset_granted(account_reset)
    with_user_locale(user) do
      @token = account_reset&.request_token
      @granted_token = account_reset&.granted_token
      @account_reset_deletion_period_interval = account_reset_deletion_period_interval(user)
      @account_reset_token_valid_period = account_reset_token_valid_period
      mail(
        to: email_address.email,
        subject: t('user_mailer.account_reset_granted.subject', app_name: APP_NAME),
      )
    end
  end

  def account_reset_complete
    with_user_locale(user) do
      mail(to: email_address.email, subject: t('user_mailer.account_reset_complete.subject'))
    end
  end

  def account_delete_submitted
    with_user_locale(user) do
      mail(to: email_address.email, subject: t('user_mailer.account_reset_complete.subject'))
    end
  end

  def account_reset_cancel
    with_user_locale(user) do
      mail(to: email_address.email, subject: t('user_mailer.account_reset_cancel.subject'))
    end
  end

  def please_reset_password
    with_user_locale(user) do
      mail(
        to: email_address.email,
        subject: t('user_mailer.please_reset_password.subject', app_name: APP_NAME),
      )
    end
  end

  def verify_by_mail_letter_requested
    with_user_locale(user) do
      mail(to: email_address.email, subject: t('user_mailer.letter_reminder.subject'))
    end
  end

  def add_email(token)
    with_user_locale(user) do
      presenter = ConfirmationEmailPresenter.new(user, view_context)
      @first_sentence = presenter.first_sentence
      @confirmation_period = presenter.confirmation_period
      @add_email_url = add_email_confirmation_url(
        confirmation_token: token,
        locale: locale_url_param,
      )
      mail(to: email_address.email, subject: t('user_mailer.add_email.subject'))
    end
  end

  def email_added
    with_user_locale(user) do
      mail(to: email_address.email, subject: t('user_mailer.email_added.subject'))
    end
  end

  def email_deleted
    with_user_locale(user) do
      mail(to: email_address.email, subject: t('user_mailer.email_deleted.subject'))
    end
  end

  def add_email_associated_with_another_account
    with_user_locale(user) do
      @root_url = root_url(locale: locale_url_param)
      mail(to: email_address.email, subject: t('mailer.email_reuse_notice.subject'))
    end
  end

  def account_verified(profile:)
    attachments.inline['verified.png'] =
      Rails.root.join('app/assets/images/email/user-signup-ial2.png').read
    with_user_locale(user) do
      @presenter = Idv::AccountVerifiedEmailPresenter.new(profile:)
      @hide_title = true
      @date = I18n.l(profile.verified_at, format: :event_date)
      mail(
        to: email_address.email,
        subject: t('user_mailer.account_verified.subject', app_name: APP_NAME),
      )
    end
  end

  def in_person_completion_survey
    with_user_locale(user) do
      @header = t('user_mailer.in_person_completion_survey.header')
      @privacy_url = MarketingSite.security_and_privacy_practices_url
      if locale == :en
        @survey_url = IdentityConfig.store.in_person_opt_in_available_completion_survey_url
      else
        @survey_url = IdentityConfig.store.in_person_completion_survey_url
      end

      mail(
        to: email_address.email,
        subject: t('user_mailer.in_person_completion_survey.subject', app_name: APP_NAME),
      )
    end
  end

  def in_person_deadline_passed(enrollment:)
    with_user_locale(user) do
      @header = t('user_mailer.in_person_deadline_passed.header')
      @presenter = Idv::InPerson::VerificationResultsEmailPresenter.new(
        enrollment: enrollment,
        url_options: url_options,
      )
      mail(
        to: email_address.email,
        subject: t('user_mailer.in_person_deadline_passed.subject', app_name: APP_NAME),
      )
    end
  end

  def in_person_ready_to_verify(enrollment:, is_enhanced_ipp:)
    attachments.inline['barcode.png'] = BarcodeOutputter.new(
      code: enrollment.enrollment_code,
    ).image_data

    with_user_locale(user) do
      @hide_title = IdentityConfig.store.in_person_outage_message_enabled &&
                    IdentityConfig.store.in_person_outage_emailed_by_date.present? &&
                    IdentityConfig.store.in_person_outage_expected_update_date.present?
      @header = is_enhanced_ipp ?
        t('in_person_proofing.headings.barcode_eipp') : t('in_person_proofing.headings.barcode')
      @presenter = Idv::InPerson::ReadyToVerifyPresenter.new(
        enrollment: enrollment,
        barcode_image_url: attachments['barcode.png'].url,
        is_enhanced_ipp: is_enhanced_ipp,
      )
      @is_enhanced_ipp = is_enhanced_ipp
      mail(
        to: email_address.email,
        subject: t('user_mailer.in_person_ready_to_verify.subject', app_name: APP_NAME),
      )
    end
  end

  def in_person_ready_to_verify_reminder(enrollment:)
    attachments.inline['barcode.png'] = BarcodeOutputter.new(
      code: enrollment.enrollment_code,
    ).image_data

    @is_enhanced_ipp = enrollment.enhanced_ipp?
    with_user_locale(user) do
      @presenter = Idv::InPerson::ReadyToVerifyPresenter.new(
        enrollment: enrollment,
        barcode_image_url: attachments['barcode.png'].url,
        is_enhanced_ipp: @is_enhanced_ipp,
      )
      @header = t(
        'user_mailer.in_person_ready_to_verify_reminder.heading',
        count: @presenter.days_remaining,
      )
      mail(
        to: email_address.email,
        subject: t(
          'user_mailer.in_person_ready_to_verify_reminder.subject',
          count: @presenter.days_remaining,
        ),
      )
    end
  end

  def in_person_verified(enrollment:)
    with_user_locale(user) do
      @hide_title = true
      @presenter = Idv::InPerson::VerificationResultsEmailPresenter.new(
        enrollment: enrollment,
        url_options: url_options,
      )
      mail(
        to: email_address.email,
        subject: t('user_mailer.in_person_verified.subject', app_name: APP_NAME),
      )
    end
  end

  def in_person_failed(enrollment:)
    with_user_locale(user) do
      @presenter = Idv::InPerson::VerificationResultsEmailPresenter.new(
        enrollment: enrollment,
        url_options: url_options,
      )
      mail(
        to: email_address.email,
        subject: t('user_mailer.in_person_failed.subject', app_name: APP_NAME),
      )
    end
  end

  def in_person_failed_fraud(enrollment:)
    with_user_locale(user) do
      @presenter = Idv::InPerson::VerificationResultsEmailPresenter.new(
        enrollment: enrollment,
        url_options: url_options,
      )
      mail(
        to: email_address.email,
        subject: t('user_mailer.in_person_failed_suspected_fraud.subject'),
      )
    end
  end

  def in_person_please_call(enrollment:)
    with_user_locale(user) do
      @presenter = Idv::InPerson::VerificationResultsEmailPresenter.new(
        enrollment: enrollment,
        url_options: url_options,
      )
      @hide_title = true
      mail(
        to: email_address.email,
        subject: t('user_mailer.in_person_please_call.subject', app_name: APP_NAME),
      )
    end
  end

  def account_rejected
    with_user_locale(user) do
      mail(
        to: email_address.email,
        subject: t('user_mailer.account_rejected.subject'),
      )
    end
  end

  def suspended_create_account
    with_user_locale(user) do
      mail(to: email_address.email, subject: t('user_mailer.suspended_create_account.subject'))
    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

  def verify_by_mail_reminder
    with_user_locale(user) do
      @gpo_verification_pending_at = I18n.l(
        user.gpo_verification_pending_profile.gpo_verification_pending_at,
        format: :event_date,
      )
      mail(to: email_address.email, subject: t('user_mailer.letter_reminder_14_days.subject'))
    end
  end

  def suspension_confirmed
    with_user_locale(user) do
      @help_text = t('user_mailer.suspension_confirmed.contact_agency')

      mail(to: email_address.email, subject: t('user_mailer.suspension_confirmed.subject'))
    end
  end

  def account_reinstated
    with_user_locale(user) do
      mail(to: email_address.email, subject: t('user_mailer.account_reinstated.subject'))
    end
  end

  private

  def account_reset_token_valid_period
    current_time = Time.zone.now

    distance_of_time_in_words(
      current_time,
      current_time + IdentityConfig.store.account_reset_token_valid_for_days.days,
      true,
      accumulate_on: :hours,
    )
  end
end