18F/identity-idp

View on GitHub
app/controllers/users/reset_passwords_controller.rb

Summary

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

module Users
  class ResetPasswordsController < Devise::PasswordsController
    include AuthorizationCountConcern
    before_action :store_sp_metadata_in_session, only: [:edit]
    before_action :store_token_in_session, only: [:edit]

    def new
      analytics.password_reset_visit
      @password_reset_email_form = PasswordResetEmailForm.new('')
    end

    def create
      @password_reset_email_form = PasswordResetEmailForm.new(email)
      result = @password_reset_email_form.submit

      analytics.password_reset_email(**result.to_h)

      if result.success?
        handle_valid_email
      else
        render :new
      end
    end

    def edit
      if params[:reset_password_token]
        redirect_to edit_user_password_url
      else
        result = PasswordResetTokenValidator.new(token_user).submit

        analytics.password_reset_token(**result.to_h)
        if result.success?
          @reset_password_form = ResetPasswordForm.new(build_user)
          @forbidden_passwords = forbidden_passwords(token_user.email_addresses)
        else
          handle_invalid_or_expired_token(result)
        end
      end
    end

    # PUT /resource/password
    def update
      self.resource = user_matching_token(user_params[:reset_password_token])
      @reset_password_form = ResetPasswordForm.new(resource)

      result = @reset_password_form.submit(user_params)

      analytics.password_reset_password(**result.to_h)

      if result.success?
        session.delete(:reset_password_token)
        handle_successful_password_reset
      else
        handle_unsuccessful_password_reset(result)
      end
    end

    protected

    def store_sp_metadata_in_session
      return if params[:request_id].blank?
      StoreSpMetadataInSession.new(session:, request_id: params[:request_id]).call
      bump_auth_count
    end

    def forbidden_passwords(email_addresses)
      email_addresses.flat_map do |email_address|
        ForbiddenPasswords.new(email_address.email).call
      end
    end

    def email_params
      params.require(:password_reset_email_form).permit(:email, :resend)
    end

    def email
      email_params[:email]
    end

    def request_id
      sp_session[:request_id]
    end

    def handle_valid_email
      RequestPasswordReset.new(
        email: email,
        request_id: request_id,
        analytics: analytics,
      ).perform

      session[:email] = email
      resend_confirmation = email_params[:resend]

      redirect_to forgot_password_url(resend: resend_confirmation)
    end

    def store_token_in_session
      return if session[:reset_password_token]
      session[:reset_password_token] = params[:reset_password_token]
    end

    def handle_invalid_or_expired_token(result)
      flash[:error] = t("devise.passwords.#{result.errors[:user].first}")
      session.delete(:reset_password_token)
      redirect_to new_user_password_url
    end

    def user_matching_token(token)
      reset_password_token = Devise.token_generator.digest(User, :reset_password_token, token)

      user = User.find_or_initialize_with_error_by(:reset_password_token, reset_password_token)
      user.reset_password_token = token if user.reset_password_token?
      user
    end

    def password_token
      session[:reset_password_token] || params[:reset_password_token]
    end

    def token_user
      @token_user ||= User.with_reset_password_token(password_token)
    end

    def build_user
      User.new(reset_password_token: password_token)
    end

    def handle_successful_password_reset
      send_password_reset_risc_event
      create_reset_event_and_send_notification
      flash[:info] = t('devise.passwords.updated_not_active') if is_flashing_format?
      redirect_to new_user_session_url
    end

    def send_password_reset_risc_event
      event = PushNotification::PasswordResetEvent.new(user: resource)
      PushNotification::HttpPush.deliver(event)
    end

    def handle_unsuccessful_password_reset(result)
      reset_password_token_errors = result.errors[:reset_password_token]
      if reset_password_token_errors.present?
        session.delete(:reset_password_token)
        flash[:error] = t("devise.passwords.#{reset_password_token_errors.first}")
        redirect_to new_user_password_url
        return
      end

      @forbidden_passwords = forbidden_passwords(resource.email_addresses)
      render :edit
    end

    def create_reset_event_and_send_notification
      _event, disavowal_token = create_user_event_with_disavowal(:password_changed, resource)
      UserAlerts::AlertUserAboutPasswordChange.call(resource, disavowal_token)
    end

    def user_params
      params.require(:reset_password_form).
        permit(:password, :password_confirmation, :reset_password_token)
    end

    def assert_reset_token_passed
      # remove devise's default behavior
    end
  end
end