I'm trying to send a 401 authorized from a Rails controller for webhooks, but I can't seem to the right way to do it since I'm not rendering a page.
render plain: "Unauthorized", status: :unauthorized
throws this error:
Read error: #<NoMethodError: undefined method `bytesize' for [:error, "Unauthorized"]:Array>
I don't understand why because
render plain: "Unauthorized", status: 400
works fine.
head(:unauthorized)
Returns a response that has no content (merely a status code and headers).
See ActionController::Head.
Are you, by any chance, using this snippet from rails_warden or equivalent middleware?
If so, the correct stanza is
manager.failure_app = Proc.new { |_env|
['401', {'Content-Type' => 'application/json'}, [{ error: 'Unauthorized' }.to_json]]
}
Related
For example, an api consumer sends:
"ticket": [{ "param": "value"}]
The controller does:
params.require(:ticket).permit(:name)
This would return a 500 error: "Undefined method permit for array"
Is there a DRY / best practice way to handle this? I think a status 400 should be returned instead.
I think the strong parameters gem should handle this internally, but they don't, so here is my solution
rescue_from NoMethodError do |exception|
if exception.message =~ /undefined method `permit' for/
render_error(message: 'Invalid parameter format.', type: :invalid_parameters, status: :bad_request)
else
raise
end
end
Based on your solution .. you would want to handle all the possibilities verse a specific error response. You can set in a rescue in your render method like this and set unauthorized if there is a method error or just something else going wrong :
begin
render json: json, status: 200
rescue_from NoMethodError do |exception|
render :unauthorized
end
rescue => e
render :unauthorized
end
I want to return from my controller if either a validation failed or a parameter is missing with 400 - bad request. So in my controller if have
if params["patch"].nil? then
raise ActionController::BadRequest.new( "The Json body needs to be wrapped inside a \"Patch\" key")
end
and i catch this error in my Application Controller with:
rescue_from ActionController::BadRequest, with: :bad_request
def bad_request(exception)
render status: 400, json: {:error => exception.message}.to_json
end
But it seems like i cannot add custom messages when raising ActionController::BadRequest. Because when passing an invalid body the response is only {"error":"ActionController::BadRequest"} and not the hash i provided.
In the console i get the same behaviour. raise ActiveRecord::RecordNotFound, "foo" indeed raises ActiveRecord::RecordNotFound: foo.
But raise ActionController::BadRequest, "baa" results in
ActionController::BadRequest: ActionController::BadRequest
How can i add custom messages to the BadRequest exception?
Try this:
raise ActionController::BadRequest.new(), "The Json body needs to be wrapped inside a \"Patch\" key"
I'm having a really weird issue trying to write a test around a rails controller.
The controller uses RestClient to get info from another server. If that 2nd server returns a bad response, I want to forward on that error message to the client.
The controller:
def callback_submit
begin
response = RestClient.post(...)
render json: response
rescue RestClient::BadRequest => e
render status: :bad_request, json: { error: e.response.body }
end
end
Then I have an rspec test for this:
it 'returns a failure message' do
stub_request( :post, 'http://...' ).to_return( status: 400, body: 'some error' )
post :callback_submit
expect(response.body).to eq({error: 'some error'}.to_json)
end
Now the problem is that the rspec output is:
expected: "{\"error\":\"some error\"}"
got: "{\"error\":\"\"}"
Where it gets weird is that I can change my controller to puts some details...
rescue RestClient::BadRequest => e
error = e.response.body.to_s
puts error.class
puts error
render status: :bad_request, json: { error: error }
Which outputs:
String
some error
expected: "{\"error\":\"some error\"}"
got: "{\"error\":\"\"}"
So the error variable seems to be a string and the correct value, but render turns it into an empty string.
Now it gets really weird... If I interpolate the string:
rescue RestClient::BadRequest => e
error = e.response.body.to_s
puts error.class
puts error
render status: :bad_request, json: { error: "#{error}" }
Or just simply:
rescue RestClient::BadRequest => e
render status: :bad_request, json: { error: "#{e.response.body}" }
Then my test PASSES!
At first I thought e.response.body must not be a String, which is why I puts its class which prints "String" and also called .to_s on it, but that didn't seem to change anything.
Can anyone explain this weird behavior?
Asked some Ruby friends and one of them found the problem, but didn't want to come post an answer here....
The problem ends up being that we are on an older RestClient (1.8). In the RestClient 2.0 notes:
Response objects are now a subclass of String rather than a String that mixes in the response functionality.
Response#body and #to_s will now return a true String object rather than self. Previously there was no easy way to get the true String response instead of the Frankenstein response string object with AbstractResponse mixed in.
So in the older version I am using, e.response ends up being this weird String mixin mashup thing, and render doesn't seem to like it.
The interpolation turns it back into a real Ruby string again.
The problem is that response.body is looking at the response object and the body is empty because of the error.
Chances are you want e.message or just e.to_s which will be the error message (or the error class name) rather than the response body.
Excerpt:
class Exception < RuntimeError
attr_accessor :response
attr_writer :message
#.......
def to_s
message
end
def message
#message || default_message
end
def default_message
self.class.name
end
end
Source
I have basically this:
class Api::NameController < ApplicationController
skip_before_filter :verify_authenticity_token
def index
end
def create
begin
cou = params[:data].count
rescue
render status: 422, json: {
message: "Error Loading data- Check your data",
}.to_json
end
input = params[:data]
# Operations with the input data
render status: 200, json: {
message: "Succesful data calculation",
data_output: input
}.to_json
end
end
The data is: {"data":[1,34,5]}
I would like to render the status error 422, when the data has a bad sintax (example: {"dat":[1,34,5]} or {"data":1,34,5]} etc)
With the example above doe not work. When sending via cURL {"data":1,34,5]} I got:
Started POST "/api/energy" for 127.0.0.1 at 2015-05-29 08:35:50 +0200
Error occurred while parsing request parameters.
Contents:
{"data":1,5,67]}
ActionDispatch::ParamsParser::ParseError (795: unexpected token at '{"data":1,5,67]}'):
When sending {"dat":[1,34,5]} I got
Processing by Api::EnergyController#create as JSON
Parameters: {"dat"=>[1, 5, 67], "energy"=>{"dat"=>[1, 5, 67]}}
Completed 500 Internal Server Error in 1ms (Views: 0.2ms | ActiveRecord: 0.0ms)
AbstractController::DoubleRenderError (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".):
Could anyone help me putting this error status in the correct form?
In one controller I got a method that I want to refactor to share with other controllers. At the same time, I'm passing it to the callback before_action.
app/models/meal_controller.rb
def check_for_user
token = request.headers[:token]
if token.nil?
render json: "Unathorized", status: 401
elsif #meal.user.auth_code != token
render json: "Forbidden", status: 403
end
end
So my approach was move check_for_userto the ApplicationController and modify it as follows:
def check_for_user(item)
token = request.headers[:token]
if token.nil?
render json: "Unathorized", status: 401
elsif item.user.auth_code != token
render json: "Forbidden", status: 403
end
end
And back to the MealController, create a other "dummy" method without params and call the check_for_user.
def check_for_user_meal
check_for_user(#meal)
end
My question is: is there a better way to refactor this code?
Thanks in advance.
I have no problem with your code going in ApplicationController, if it's only a few lines of code.
However, I'd suggest you check the difference between 401 and 403. The main difference is that 401 means there was an error with your authentication attempt, please try again; whereas 403 means you are trying to authenticate in a way that is incorrect so please stop trying.
With a username/password user input, 401 makes sense because its likely the user has mistyped something.
But with tokens, another attempt is only going to get the same result. So both no token and the wrong token should result in a 403 error.
So I'd refactor you code like this:
def request_token
request.headers[:token]
end
def check_for_user(item)
if request_token.nil? || item.user.auth_code != request_token
render json: "Forbidden", status: 403
end
end
You could create a module so the method would be available across the application.