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.
Related
This is my controller
class Api::V1::WebhooksController < ApplicationController
include Api::V1::WebhookHelper
include Api::V1::LogHelper
skip_before_action :verify_authenticity_token
after_action :handle_wehbook
# Schedule plan
def schedule_plan
add_log(status: "webhook", message: "New Plan is scheduled.")
end
def handle_wehbook
if webhook_verified?
webhook_verified!
render status: 200, json: { error: 'webhook is verified.' }
else
webhook_verified!(verified: false)
render status: 500, json: { error: 'webhook is not verified.' }
end
end
end
This is Webhook Helper.
I am sure in WebhookHelper, it never redirects or renders anything.
require 'openssl'
require 'base64'
module Api::V1::WebhookHelper
include Api::V1::LogHelper
def webhook_verified?
digest = OpenSSL::Digest.new('sha256')
hmac = OpenSSL::HMAC.digest(digest, secret, request.body.read)
hash = Base64.encode64(hmac).strip
hash == signature
end
def secret
ENV["API_KEY"]
end
def signature
request.headers["HTTP_X_SIGNATURE"]
end
def webhook_verified!(verified: true)
if verified
add_log(status: "webhook", message: "Webhook is verified.") # only puts log
else
add_log(status: "webhook", type: "warning", message: "Webhook is not verified.") # only puts log
end
end
end
I am getting this issue.
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".):
app/controllers/api/v1/webhooks_controller.rb:31:in `handle_wehbook'
I am not sure I am calling render or redirect multiple times in my action.
Anyone can help me?
Your handle_wehbook function is an after_action, it runs after some other action.
The latter has already rendered something (it may be an rails error or a redirect) thus the double render
My service authenticate some data on the user's role basis. If the query params are wrong, i want to exit the code and render some error message as the api response?
render json: 'something' and return
Error that i get it:
"status": 500,
"error": "Internal Server Error",
"exception": "#<NoMethodError: undefined method `render' for AuthenticationService:Class>",
"traces": {
"Application Trace": [
The short answer is: you can't.
For something like authentication or permission checking it would be more common to ask your service to authenticate and then that service would either return a value that you can react to or throw an exception that you can react to.
That way each part of your code can take responsibility for what it needs to and no more. Your service can authenticate and your controller can call render.
So, for example, you might end up with something like this in your service:
def authenticate!
if !okay
raise AuthenticationError
end
end
And in your controller:
def my_action
begin
AuthenticationService.new.authenticate!
rescue AuthenticationError
render json: 'something' and return
end
end
(This is a very basic example - and I've made up an error class and an okay method to demonstrate)
How do I render custom error JSON in a Rails 5 API? Right now, if I perform a GET on this url http://localhost:3000/users/5, it returns the 404 not found code, and all the traces associated with it. How can I stop Rails from automatically rendering all the traces?
Example of the generated error response: https://pastebin.com/C1dQA5eL
Hi you can create a custom module and extend it in your controller. Create a method in that module with parameters of resource and value. And on the basis of that send response and after that you can extend it in your respective Controller
like this:
class MyController
include AppError
end
I think you should if....else.
def show
user = User.find_by(id: params[:id])
if user.present?
render json: user
else
render json: { status: :not_found }
end
end
I am trying to setup Facebook's webhook, and when specifying the callback url, they send a get request to it and expect to receive a code.
I defined an action:
def webhook
render json: { code: '1234567890' }
end
So the reponse to the request is { code: '1234567890' } when they are expecting '1234567890'.
I tried to only have:
def webhook
render json: {'1234567890'}
end
But I get an error.
I also tried to use the head method but nothing is sent back.
How can I only send back an integer ?
As Nani pointed it out in the comments:
def webhook
render :json => 1826416246
end
Works fine !
For example...
module Api
module V1
class SessionsController < ApplicationController
respond_to :json
skip_before_filter :verify_authenticity_token
def create
#user = User.find_by(email: params[:session][:email].downcase)
if #user && #user.authenticate(params[:session][:password])
token = User.new_remember_token
#user.update_attribute(:remember_token, User.digest(token))
respond_with :api, :v1, _____________
else
#error
end
end
end
end
end
The #error part of the code, if the user is not properly authenticated. What syntax do I need to properly convey to the caller that the authentication did not go through for example, or in other cases, maybe data was not saved?
Like CBroe said, respond with an appropriate status code, such as 400 or 403. You could do just that (using 'head' to return the status code only), or also add an error message in JSON format:
{ 'error' : 'Authorization failed' }
The client code will want to check the status code and possibly the 'error' key in the JSON response and handle it appropriately.
Examples to put at the end of your controller action (pick one):
return head(:bad_request) # returns a 400 status code only
render :json => { :error => 'That was an invalid request' } # defaults to 200 status
render :json => { :error => 'Oops! Bad request' }, :status => 400
The last example overrides the default status to make it a 400. In general, the status can be an integer like that, or a symbol like :not_found or :bad_request. Hope that helps.