ruby-on-railsrubyruby-on-rails-4devisedevise-confirmable

Check if user confirmed during login, using devise


I have a React FE + Rails BE app and I'm using devise for authentication. I'm trying to generate a custom error message for users who try to log in after their confirmation period has expired.

Currently, they are being blocked from logging in but it doesn't show a custom error message explaining why they have been blocked.

My session_controller.rb

def create

  if BANNED_EMAILS.include?(params["user"]["email"])
    render :status => 500, :json => { error: true, message: 'Your account has been suspended. Please contact us for more information.' }

//I get an error `ActiveRecord::RecordNotFound (Couldn't find User)` when I try this even though the user has already signed up and exists in the database

  elsif User.find_by_email!(params[:user][:email]).confirmed? == true
    render :json => { error: true, message: 'Please confirm your registered email to access your account.'}, :status => 500
  else
    self.resource = warden.authenticate!(auth_options)
    sign_in(resource_name, resource)
    render :status => 200, :json => resource.to_json    
end

end

Is there some other way to pull the information of the user trying to log in or is there some other way to show the same error?


Solution

  • If you look at Devise::SessionsController (or any of the devise controllers) you can see that it yields:

    class Devise::SessionsController < DeviseController
      # POST /resource/sign_in
      def create
        self.resource = warden.authenticate!(auth_options)
        set_flash_message!(:notice, :signed_in)
        sign_in(resource_name, resource)
        yield resource if block_given?
        respond_with resource, location: after_sign_in_path_for(resource)
      end
    end
    

    This lets subclasses tap into the flow without overriding the entire method.

    class MySessionsController < DeviseController
      def create
        # taps into the yield right after the user is authenticated by warden
        super do |user|
          if user.banned?
            # 500 is the wrong response code
            # it means that the server is broken and cannot respond to the request
            render json: { error: true, message: 'Your account has been suspended. Please contact us for more information.' }, 
              status: 401 and return
          elsif user.requires_confirmation?
            render json: { error: true, message: 'Please confirm your registered email to access your account.'}, 
              status: 401 and return
          end
        end
      end
    end
    

    Instead of using a code constant (are you really going to redeploy the application every time a user gets banned?) you should use a flag on the user's table to denote if the user is banned.

    Likewise, the business logic of determining when a user must be confirmed should be handled on the model layer:

    class User < ApplicationRecord
      # ...
      def requires_confirmation?
        if user.confirmed? || user.created_at < 3.days.ago
          false
        else
          true
        end
      end
    end