User object is sometimes nil in Clearance SessionsController after successful sign in

58 Views Asked by At

I've assumed that if status.success? is true then I could rely on @user being present. But sometimes I get an error that @user is nil.

How is it possible that the reference to @user within #track_sign_in would sometimes be nil?

class Clearance::SessionsController < Clearance::BaseController
  before_action :redirect_signed_in_users, only: [:new]
  skip_before_action :require_login, only: [:create, :new, :destroy], raise: false

  def create
    @user = authenticate(params)

    sign_in(@user) do |status|
      if status.success?
        track_sign_in
        redirect_back_or url_after_create
      else
        flash.now.alert = status.failure_message
        render template: "sessions/new", status: :unauthorized
      end
    end
  end

  private

  def track_sign_in
    @user.update!(sign_in_count: @user.sign_in_count + 1)
  end
end

I used to get this error even before changing the above controller logic to include 2FA. Here's the full controller now. I've been getting the error more often recently since changing it, but I've seen the error when the controller looked like the above too.

class SessionsController < Clearance::SessionsController
  before_action :skip_authorization
  layout 'simple'

  def create
    @user = find_user

    authed_user = authenticate(params)

    if params[:session][:otp_attempt].present? && session[:otp_user_id]
      authenticate_user_with_otp_two_factor(@user)
    elsif !@user || [email protected]_factor_required? || !authed_user || (@user.two_factor_required? && [email protected]_factor_setup?)
      do_sign_in(authed_user)
    else
      prompt_for_otp_two_factor(authed_user)
    end
  end

  private

  def valid_otp_attempt?(user)
    user.authenticate_otp(params[:session][:otp_attempt])
  end

  def authenticate_user_with_otp_two_factor(user)
    if valid_otp_attempt?(user)
      # Remove any lingering user data from login
      session.delete(:otp_user_id)

      do_sign_in(user)
    else
      flash.now[:alert] = 'Invalid code. Try again.'
      prompt_for_otp_two_factor(user, :unauthorized)
    end
  end

  def prompt_for_otp_two_factor(user, status = :ok)
    @user = user

    session[:otp_user_id] = user.id
    render 'sessions/two_factor', status: status
  end

  def do_sign_in(user)
    sign_in(user) do |status|
      if status.success?
        track_sign_in
        redirect_back_or url_after_create
      else
        flash.now.alert = status.failure_message
        render template: "sessions/new", status: :unauthorized
      end
    end
  end

  def find_user
    if session[:otp_user_id]
      User.find(session[:otp_user_id])
    elsif params[:session][:email]
      User.find_by(email: params[:session][:email])
    end
  end

  def track_sign_in
    @user.update!(
      sign_in_count: @user.sign_in_count + 1,
      last_sign_in_at: Time.current,
      last_sign_in_ip: request.remote_ip,
    )
  end
end

And here's a screen shot of the stack track:

enter image description here

0

There are 0 best solutions below