I have a concern module as below
concerns/onboarding.rb
module Onboarding
def status(onboard, params)
if onboard.finished? && params[:process].present?
render json: { error: 'You have already finished.' }, status: :not_implemented
end
end
end
I am using it below
module MyAccount::User
class OnboardingsController < ApplicationController
include Onboarding
def update
status(current_kyc, params)
current_kyc.update!(wizard_params)
end
private
def current_kyc
#wizard ||= current_user.onboardings.where(company_id: current_company.id).last
end
def wizard_params
params.require(:onboarding).permit(:step, :finished)
end
end
end
This issue is, after render json: { error: 'You have already finished.' }, status: :not_implemented, current_kyc.update!(wizard_params) is still executed. I don't know what the issue but current_kyc.update!(wizard_params) shouldn’t be implemented if render json: { error: 'You have already finished.' }, status: :not_implemented is executed.
Calling render doesn't return or stop execution of a controller method. It only sets what should happen after the method call.
You can use performed? to check if the render method was already called, like this:
def update
status(current_kyc, params)
current_kyc.update!(wizard_params) unless performed?
end
But I am not really sure if this improves readability and makes it easier to understand what is actually going on in the controller.
Related
module UserCheck
def self.status(onboarding, params)
if onboarding && params[:process].present?
render json: { status: :ok }
else
render json: { error: 'You have already finished your onboarding.' }, status: :not_implemented
end
end
end
module MyAccount::User
class UserController < MyAccountController
def update
UserCheck.status(wizard_onboarding, params)
end
end
end
In the users_controller, I am using the module UserCheck to check the onboarding status and return an error in the else case. But when the else condition runs it doesn’t render json error message but instead returns the undefined method 'render' for UserCheck:Module. Could you help me fix this issue?
I would pass the controller to that method and call then render on that controller, like this:
module UserCheck
def self.status(onboarding, params, controller)
if onboarding && params[:process].present?
controller.render json: { status: :ok }
else
controller.render json: { error: 'You have already finished your onboarding.' }, status: :not_implemented
end
end
end
module MyAccount::User
class UserController < MyAccountController
def update
UserCheck.status(wizard_onboarding, params, self)
end
end
end
Personally, I see no benefit in extracting such simple code from the controller into a module. It makes the controller much harder to understand and to debug. And to understand what the controller is returning, you need to look into a different file.
I am working on rails and trying to make a simple blog site and its working the way i want to on my local machine but when pushed to production its being blocked by the callback functions.
My before_action :authorized_user? callback is being called and it prompts for logging if not logged in for performing any method on the blog , and if logged in all methods create, update and destroy methods are working perfectly in my development environment but in production even after the user is logged in also and when the create method is being called it asks for to log in . I am unable to understand from where or what code is causing this to happen because the same is working perfectly fine on local machine.
Any help will he highly appreciated.
My blog_controller.rb file is
class BlogsController < ApplicationController
before_action :set_blog, only: [:show, :update, :destroy, :lock_blog, :pin_blog]
before_action :authorized_user?, except: [:index, :show]
def index
#blogs = Blog.all
render json: { blogs: #blogs },status: :ok
end
def show
comments = #blog.comments.select("comments.*, users.username").joins(:user).by_created_at
render status: :ok, json: { blog: #blog, blog_creator: #blog.user, comments: comments }
end
def create
#blog = Blog.new(blog_params.merge(user_id: #current_user.id))
if authorized?
if #blog.save
render status: :ok,
json: {blog: #blog , notice: "Blog Successfully created"}
else
errors = #blog.errors.full_messages.to_sentence
render status: :unprocessable_entity, json: {error:errors}
end
end
end
def update
if authorized?
if #blog.update(blog_params)
render status: :ok,
json: {blog: #blog, notice:"Blog successfully updated"}
else
render status: :unprocessable_entity,
json: {errors: #blog.errors.full_messages.to_sentence}
end
else
handle_unauthorized
end
end
def destroy
if authorized?
if #blog.destroy
render status: :ok,
json: {notice:'Blog deleted'}
else
render status: :unprocessable_entity,
json: {errors: #blog.errors.full_messages.to_sentence}
end
else
handle_unauthorized
end
end
private
def set_blog
#blog = Blog.find(params[:id])
end
def blog_params
params.require(:blog).permit(:title,:body,:image,:is_pinned, :is_locked)
end
def authorized?
#blog.user_id == #current_user.id || #current_user.admin_level >= 1
end
def handle_unauthorized
unless authorized?
render json:{notice:"Not authorized to perform this task"}, status:401
end
end
end
and application_controller.rb file is
class ApplicationController < ActionController::Base
skip_before_action :verify_authenticity_token
include CurrentUserConcern
include ExceptionHandlerConcern
include TokenGenerator
def authorized_user?
render json: { notice: 'Please log in to continue' }, status: :unauthorized unless #current_user
end
def authorized_admin?
authorized_user?
render json: {errors: 'Insufficient Administrative Rights'}, status: 401
end
private
end
current_user_concern.rb file
module CurrentUserConcern
extend ActiveSupport::Concern
included do
before_action :set_current_user
end
def set_current_user
if session[:token]
#current_user = User.find_by(token: session[:token])
end
end
end
Its generally recommended to use libraries for authentication and authorization instead of reinventing the wheel unless its for learning purposes. They have many eyes looking for bugs and insecurites and are battle hardened by tons of users. Home-rolled authentication systems are a very common source of security breaches which could lead to very expensive consequences.
If you're going to roll your own authorization and authentication solution I would suggest you take a page from the libraries like Devise, Pundit and CanCanCan and raise an error when a user is not authorized or authenticated so that you immediately halt whatever the controller is doing and stop the callback chain from executing further.
# app/errors/authentication_error.rb
class AuthenticationError < StandardError; end
# app/errors/authorization_error.rb
class AuthorizationError < StandardError; end
# app/controllers/concerns/
module Authenticable
extend ActiveSupport::Concern
included do
helper_method :current_user, :user_signed_in?
before_action :authenticate_user
rescue_from AuthenticationError, with: :handle_unauthorized
end
def current_user
#current_user ||= find_user_from_token if session[:token].present?
end
def find_user_from_token
User.find_by(token: session[:token])
end
def user_signed_in?
current_user.present?
end
def authenticate_user
raise AuthenticationError.new('Please log in to continue') unless user_signed_in?
end
def handle_unauthenticated(error)
render json: {
notice: error.message
},
status: :unauthorized
end
end
end
# app/controllers/concerns/authorizable.rb
module Authorizable
extend ActiveSupport::Concern
included do
rescue_from AuthenticationError, with: :handle_unauthorized
end
def authorize_admin
raise UserAuthenticationError.new('Insufficient Administrative Rights') unless current_user.admin?
end
def handle_unauthorized(error)
render json:{
notice: error.message
}, status: :unauthorized
end
end
class ApplicationController < ActionController::Base
skip_before_action :verify_authenticity_token
include Authenticable
include Authorizable
# Should you really be mixing this into the controller? Seperate the responsibilites!
include TokenGenerator
end
It also makes debugging much easier as you can disable rescue_from in testing so that you get an exception instead of just a cryptic failure message.
You should also setup your authorization system so that it always authenticates (and authorizes) unless you explicitly opt out. This is a best practice that reduces the possible of security breaches simply due to programmer omission. You opt out by calling skip_before_action :authorize_user.
Instead of your set_current_user use a memoized getter method (current_user) to remove issues caused by the ordering of callbacks. ||= is conditional assignment and will prevent it from querying the database again if you have already fetched the user. This should be the ONLY method in the system that knows how the user is stored. Do not access #current_user directly to avoid leaking the implementation details into the rest of the application.
Methods ending with ? are by convention predicate methods in Ruby and should be expected to return a boolean. Name your modules by what their responsibility is and not what code they contain - avoid the postfix Concern as it tells you nothing about what it does.
I have a rails 4 project, where I'm using DeviseTokenAuth.
Everything works fine, but I'd like to refuse access to user with a specific status.
So basically
if user.status == :locked => Account :unauthorized
So this is what I've done so far
class SessionsController < DeviseTokenAuth::SessionsController
def new
super
end
def create
super
render json: { error: "Account is locked MOFO " }, status: :unauthorized if current_user.status.to_sym == :locked
end
end
But when I do that I get :
AbstractController::DoubleRenderError - Render and/or redirect were called multiple times in this action. Please notethat you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".:
Any idea ?
thanks
This error happens because double render methods are called from sessions controller create method. One option is to override render_create_success method to get the desired result.
class SessionsController < DeviseTokenAuth::SessionsController
protected
def render_create_success
if current_user.status.to_sym == :locked
render json: { error: "Account is locked MOFO " }, status: :unauthorized
else
super
end
end
end
Hi inmy app I has such a situation:
in some_controller.rb I has a code aout this:
def start
method1(param)
....
if some_case
render json: {ok: "ok"}
end
end
def method1
...
if some_case
render json: {error: "Some error"}
return
end
end
The thing is when it's time to render a json with error, I get a double render error. It advices me to use render .. and return. I've tried even that, and still get this error.
Is this because render do not breaks execution itself, but just returns smth to a caller method? If it's so, what can I do in my case? The thing is method1 is actually a big method and I surely want it to be separateed from start method. And in case there are no reasons to render an error there, I want an execution of start to be continued.
Thanx!
Consider using filter instead. This works:
before_action :method1, only: :start
def start
....
if some_case
render json: {ok: "ok"}
end
end
def method1
...
if some_case
render json: {error: "Some error"}
return
end
end
When render occurs in filter, it does not run action itself then, so no double render occurs.
Use this code
def method1
if some_case
render json: {error: "Some error", status: :unprocessable_entity }
end
end
In my rails controller, I have to check after getting #group with before_action that this group is not system.
But I have lot's of repetition in my controller. I've tried to turn into a separate method but I get the classic :
Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".
Here is a part of my code without the separate method who give me the error.
def destroy
if #group.is_system?
render json: { errors: 'You can\'t delete a group system' }, status: 403
return
end
...
end
def update
if params[:group] && !params[:group].empty?
if #group.is_system?
render json: { errors: 'You can\'t edit a group system' }, status: 403
return
end
...
else
render json: { errors: 'Missing correct parameters' }, status: :unprocessable_entity
end
end
.....
You could have in a parent controller:
def render_errors(errors, status)
render json: { errors: Array(errors) }, status: status
end
def render_403(errors)
render_errors(errors, 403)
end
def render_422(errors)
render_errors(errors, 422)
end
then in your action:
before_action :check_system
def check_system
# I assume you already defined #group
render_403('You can\'t delete a group system') if #group.is_system?
end
Notice I changed a bit of your code: having errors key which is only a string is very misleading, should be an array.