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?
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