Reputation: 21125
My Rails 4 app uses RocketPants for its JSON API and Pundit for authorization.
I have code in my /app/controllers/api/v1/base_controller.rb
file to handle errors from Pundit. Whenever a user isn't authorized to update a resource, Pundit throws a NotAuthorizedError
exception and I rescue it with my user_not_authorized
method:
class API::V1::BaseController < RocketPants::Base
include Pundit
version 1
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
def user_not_authorized
error! :forbidden
end
end
When I call the error!
method that RocketPants provides from my exception handler, I expect to get a JSON response like this:
{
"error": "forbidden",
"error_description": "The requested action was forbidden."
}
Instead, however, calling error!
just immediately blows up the request:
Completed 500 Internal Server Error in 143ms
RocketPants::Forbidden - RocketPants::Forbidden:
rocket_pants (1.13.1) lib/rocket_pants/controller/error_handling.rb:44:in `error!'
app/controllers/api/v1/base_controller.rb:61:in `user_not_authorized'
Full stack trace here.
Why isn't the error!
method doing what it should when called from my Pundit exception handler?
If I put error! :forbidden
in the middle of my controller action, it works as expected.
For context, the controller that inherits from base_controller.rb
and calls Pundit's authorize
method looks like this:
class API::V1::MealsController < API::V1::BaseController
before_filter :find_entity
def create
meal = @entity.meals.build(meal_params)
authorize(@entity, :update?)
if meal.save
expose meal, status: :created
else
expose meal.errors, status: 422
end
end
end
Upvotes: 1
Views: 2223
Reputation: 21125
Apparently raising exceptions in a rescue_from
is a bad idea and according to the Rails docs, exceptions raised in a handler are not bubbled up:
Exceptions raised inside exception handlers are not propagated up.
Docs: http://api.rubyonrails.org/classes/ActiveSupport/Rescuable/ClassMethods.html
Instead of re-raising RocketPants' exception, I'm simply creating and returning the JSON error message myself:
def user_not_authorized
# error! :forbidden
head 403
error = { error: 'Action not allowed.', error_description: 'Sorry, you are not allowed to perform this action.'}
expose error
end
This works!
UPDATE
I've since found an even cleaner solution: just map the Pundit exception to the RocketPants exception. This means that whenever a Pundit::NotAuthorizedError
error is raised it'll be treated as a RocketPants::Forbidden
error.
Got the entire solution down to a single line of code at the top of base_controller.rb
:
map_error! Pundit::NotAuthorizedError, RocketPants::Forbidden
No handler required.
Upvotes: 2