store all http request, response with ahoy gem - ruby-on-rails

I want made that all log will store database.
therefore I try setup Ahoy gem in my Rails Api only Project.
Ahoy is awesome gem. but there is not Best pratice on README.md of Ahoy github.
i hope store http request header, reponse header and body with Ahoy.
this code is track_action for storing event in ApplicationController.
class ApplicationController < ActionController::Base
after_action :track_action
protected
def track_action
ahoy.track "Viewed #{controller_name}##{action_name}", { request: { header: request.headers },
response: { headers: response.headers, body: JSON.parse(response.body) } }
end
end
but i got error.
Completed 500 Internal Server Error in 493ms (Views: 34.8ms | ActiveRecord: 89.1ms | Allocations: 317174)
SystemStackError (stack level too deep):
app/controllers/application_controller.rb:14:in `track_action'
What is Best practice ahoy.track for storing all request, response log?
storing all http request, response with Ahoy gem

Related

ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken): While POSTing a JSON arugments to a Rails Controller

I am using a React/Redux frontend with a rails server running the backend. I have a button that onClick will send trigger an action that consists of two dispatches and a fetch, one dispatch before and one after. The fetch finds the server properly but I am given a 422 Error, meaning there is some issue on the Rails side of things after the request is accepted. The error is as you see in the title, ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken). However, I have the params set up to require a player object and permit the proper attributes.
The action with the fetch (which I know works) looks like this
export default function completeAttributeSelection(playerObj){
const playerPOST = ({
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({
atk: playerObj.atk,
sAtk: playerObj.sAtk,
def: playerObj.def,
sDef: playerObj.sDef,
spd: playerObj.spd,
hp: playerObj.hp,
name: playerObj.name
})
})
return (dispatch) => {
dispatch({type: "LOADING"})
console.log("Domain: " + DOMAIN())
fetch((DOMAIN() + "/players/update_or_create"), playerPOST)
.then(resp => resp.json())
.then(json => {
console.log(json)
dispatch({type: "ATTRIBUTE_UPDATE_COMPLETE", payload: json})
})
}
}
And this is the controller that handles the request:
class PlayersController < ApplicationController
def update_or_create
puts ("Update or Create hit")
#player = Player.create_or_find_by(name: player_params[:name])
puts (player_params)
#player.update(class: player_params[:class], lvl: player_params[:level], atk: player_params[:atk], sAtk: player_params[:sAtk], def: player_params[:def], sDef: player_params[:sDef], spd: player_params[:spd], hp: player_params[:hp])
render json{#player}
end
private
def player_params
params.require(:player).permit(:name, :inv_hash, :lvl, :name, :class, :atk, :def, :sAtk, :sDef, :spd, :hp, :move_distance)
end
end
Since I am not using any secrets, keys, or anything like has_secure_password I am struggling to see what exactly is getting caught up by this.
The entirety of the prompts I get from the Rails Terminal (before the long jumbled error) is as follows...
Processing by PlayersController#update_or_create as JSON
Parameters: {"atk"=>6, "sAtk"=>6, "def"=>5, "sDef"=>9, "spd"=>10, "hp"=>85, "name"=>"test01", "player"=>{"name"=>"test01", "atk"=>6, "def"=>5, "sAtk"=>6, "sDef"=>9, "spd"=>10, "hp"=>85}}
HTTP Origin header (http://localhost:3000) didn't match request.base_url (http://localhost:3006)
Completed 422 Unprocessable Entity in 0ms (ActiveRecord: 0.3ms | Allocations: 394)
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
The short answer to get this to work is adding protect_from_forgery with: :null_session to your controller.
class PlayersController < ApplicationController
protect_from_forgery with: :null_session
# ...
end
The longer answer deals with CSRF and the so called authenticity token. This is a good source it seems https://blog.nvisium.com/understanding-protectfromforgery
Cross-Site Request Forgery is a serious vulnerability that stems from
the trust that web applications place on the session identification
cookies that are being passed between browser and server. For a more
detailed explanation of CSRF, I suggest looking at the OWASP guide on
Cross-Site Request Forgery.
Rails includes a built-in mechanism for preventing CSRF,
protect_from_forgery, which is included by default in the
application_controller.rb controller when generating new applications.
This protect_from_forgery method leverages magic to ensure that your
application is protected from hackers!

401 Unauthorized error when making requests to rails api on heroku

I just deployed my first app to heroku and it used a Rails API (backend) and a React frontend. When I deployed my app I get 401 unauthorized errors whenever I try to make a request that involves the use of a JSON Web Token(JWT). I am sending the token from localstorage in my app and everything worked fine when I was sending it in my development environment. I only have this issue in production.
When I make the fetch request from my frontend and send over my JWT to my backend, I get the following messages in my heroku server logs:
2020-11-29T04:45:31.742735+00:00 app[web.1]: I, [2020-11-29T04:45:31.742670 #4] INFO -- : [f3c19eae-e431-4c9f-b93b-7499797f2c03] [active_model_serializers] Rendered ActiveModel::Serializer::Null with Hash (0.13ms)
2020-11-29T04:45:31.742984+00:00 app[web.1]: I, [2020-11-29T04:45:31.742919 #4] INFO -- : [f3c19eae-e431-4c9f-b93b-7499797f2c03] Filter chain halted as :authorized rendered or redirected
2020-11-29T04:45:31.744091+00:00 app[web.1]: I, [2020-11-29T04:45:31.744019 #4] INFO -- : [f3c19eae-e431-4c9f-b93b-7499797f2c03] Completed 401 Unauthorized in 2ms (Views: 0.7ms | Allocations: 218)
Another strange thing about this is that I get an error message along with the 401 status error in my React frontend that tells me to Please Log in even though I logged into my app to receive a token from my backend before I even attempted to make another fetch request
Below I will post other relevant code snippets so that you can see what I was trying to do
Here is the Code in my frontend that sends the request
addToCart = () =>{
//make a fetch request to add the item in the customer's current cart
fetch("https://health-and-fit-store-api.herokuapp.com/cart_products",{
method:"POST",
headers:{
"Authorization": localStorage.token,
"Content-Type":"application/json",
Accept:"application/json"
},
body:JSON.stringify({
productId :this.props.id
})
})
.then(res => res.json())
.then(data => {
this.props.addToCart(data)
toast.dark(`Added ${data.product.name.toLowerCase()} to your cart`)
})
}
Here is the code in my Rails API that receives the request
before_action :authorized, only: [:create,:delete]
def create
#will be receiving token in fetch request
#use the of the current cart and the product id passed in by the post request
current_cart = #customer.carts.find_by(checked_out:false)
product = Product.find(params[:productId])
new_cart_product = CartProduct.create(cart:current_cart,product:product, quantity:1)
render json: new_cart_product
end
Here is the code from my application controller that I used to set up JWT
class ApplicationController < ActionController::API
def encode_token(payload)
# should store secret in env variable
JWT.encode(payload, ENV['jwt_encode_string'])
#byebug
end
def auth_header
# { Authorization: 'Bearer <token>' }
request.headers['Authorization']
end
def decoded_token
if auth_header
token = auth_header
# header: { 'Authorization': '<token>' }
begin
JWT.decode(token, ENV['jwt_encode_string'], true, algorithm: 'HS256')
rescue JWT::DecodeError
nil
end
end
end
def logged_in_customer
if decoded_token
customer_id = decoded_token[0]['customer_id']
#customer = Customer.find_by(id: customer_id)
end
end
def logged_in?
!!logged_in_customer
end
def authorized
render json: { error_message: 'Please log in' }, status: :unauthorized unless logged_in?
end
end
If anyone can help me out with this, I would really appreciate it, I've been stuck on this for days. Also please note that I have checked out every other post involving this issue on StackOverflow and have exhausted every kind of search on Google that I could think of.
thanks to #snake, I actually ended up solving this issue. The problem wasn't with the token I was using, but their suggestion led me to go back and check out my fetch requests to the api endpoint and I had an extra comma at the end of the request that was causing the 401 status code error.
Once I fixed that, everything worked beautifully.

How can I handle generic errors with a JSON response in a hybrid (JSON API + HTML) Rails 5 app?

I spent one day trying several approaches, but still haven't quite got there so decided to ask now...
I have a Rails 5 app which is mainly a JSON API (using the actual JSON API specs), but also a "normal" Rails app with transactional emails and account related pages (reset password, etc).
What I'd like to achieve is that Rails always returns a JSON response with some meaningful error response to all API calls, rather than the default HTML error page or a header only 400 error.
The main cases I'm trying to handle are JSON parsing issues and Ruby exceptions (500 errors).
I tried:
using rescue_from on the ActionController level – seems the framework handles these exceptions before they would reach the controller
Handling them on the Rack level with a middleware – this worked in test but not in dev despite setting consider_all_requests_local to false in both
Registering a new Mime-type and a parser as JSON API Resources gem does it – looked promising, but the parser code is never hit
I'm really at my wit's end, something which sounded so simple ended up being deceptively complicated with me trying to hunt down where are these exceptions get handled in the framework without much success...
Well I managed to work it out in the end, so thought I should share what worked.
What I missed before is that I had to fiddle a bit with MIME types so that Rails would understand and properly use JSON API:
config/initializers/mime_types.rb
JSON_API_MIME_TYPES = %w[
application/vnd.api+json
text/x-json
application/json
].freeze
Mime::Type.unregister :json
Mime::Type.register 'application/json', :json, JSON_API_MIME_TYPES
Mime::Type.register_alias 'application/json', :json, %i[json_api jsonapi]
After this I could finally handle 500 errors in the base controller:
rescue_from StandardError,
with: :render_standard_error
def render_standard_error
render json: {
status: 500,
error: 'Unhandled error',
message: 'An unexpected error occurred.'
}, status: :internal_server_error
end
Then for handling JSON parse errors, this was the solution:
app/middleware/catch_json_parse_errors
class CatchJsonParseErrors
def initialize(app)
#app = app
end
def call(env)
#app.call(env)
rescue ActionDispatch::Http::Parameters::ParseError => error
if JSON_API_MIME_TYPES.include?(env['CONTENT_TYPE']) ||
JSON_API_MIME_TYPES.include?(env['HTTP_ACCEPT'])
return [
400, { 'Content-Type' => 'application/json' },
[{
status: 400,
error: 'JSON parse error',
message: "There was a problem in the JSON you submitted: #{error}"
}.to_json]
]
else
raise error
end
end
end
config/application.rb
require './app/middleware/catch_json_parse_errors'
...
config.middleware.use CatchJsonParseErrors

How to successfully use validate_token in the devise_token_auth gem?

I am fairly new to Ruby and Rails itself and I'm trying to build a simple Rails API.
I want to authenticate users via token and I am therefore using the devise_token_auth gem.
I can successfully make a POST request at /auth/sign_in and I am now trying to make a GET request at /auth/validate_token
What I have, as a "test":
uri = URI.parse("http://localhost:3000/auth/sign_in")
response = Net::HTTP.post_form(uri, {"email" => params[:session][:email], "password" => params[:session][:password]})
uri2 = URI.parse("http://localhost:3000/auth/validate_token")
params = { :auth_token => response['access-token'], :uid => response['uid'], :client => response['client'] }
uri2.query = URI.encode_www_form(params)
response2 = Net::HTTP.get_response(uri2)
I am therefore using the access-token and uid retrieved from the response header but I keep getting a 401 response code from /auth/validate_token:
Started GET "/auth/validate_token?auth_token=EEV40VDHfOaWtBzv3bn_DQ&uid=username%40example.com&client=NAzWNJalYBJLRni9dCGxXA" for ::1 at 2016-06-22 15:22:35 +0100
Processing by DeviseTokenAuth::TokenValidationsController#validate_token as */*
Parameters: {"auth_token"=>"EEV40VDHfOaWtBzv3bn_DQ", "uid"=>"username#example.com", "client"=>"NAzWNJalYBJLRni9dCGxXA"}
Completed 401 Unauthorized in 0ms (Views: 0.1ms | ActiveRecord: 0.0ms)
What am I doing wrong? How can I solve this?
I believe the problem is twofold:
you send the authentication credentials as headers to the /validate_token endpoint
you send the token header as access-token instead of auth_token
You can read about it in this github issue. It may not have been at the time of your problem, but it is currently published in the README.
Here are all the headers necessary for a valid authenticated request (at the time of this writing):
"access-token": "wwwww",
"token-type": "Bearer",
"client": "xxxxx",
"expiry": "yyyyy",
"uid": "zzzzz"
Note: these are not necessary for every endpoint, but usually access-token, client, and uid are.

Rails Request Content-Type Seems incorrect when accessing API with AngularJS

I have a straightforward Rails 4.1.4 application and I'm trying to connect an AngularJS application on a separate host to it's API. Whilst I'm have no problem accessing it, Rails seems to think the request is HTML and ignores the Content-Type: 'application/json'
Started GET "/api/v1/locations?access_token=xxx&headers=%7B%22Content-type%22:%22application%2Fjson%22%7D" for 127.0.0.1 at 2014-09-03 17:12:11 +0100
Processing by Api::V1::LocationsController#index as HTML
Parameters: {"access_token"=>"xxx", "headers"=>"{\"Content-type\":\"application/json\"}"}
And in my NG application, I've tried a number of combinations of headers including:
app.factory('Location', ['$resource', "$localStorage",
function($resource, $localStorage){
return $resource('http://my-api.com/api/v1/locations/', {headers:{'Content-type' : 'application/json'}}, {
query: {
method: 'GET',
headers: {'Content-type': 'application/json'},
isArray: true,
dataType: 'json',
params: { access_token: $localStorage.accessToken }
}...
The response looks ok on the NG side, it's responding with JSON despite only having this in my locations controller:
class Api::V1::LocationsController < Api::V1::BaseController
doorkeeper_for :all
before_filter :authorize
respond_to :json
def index
#locations = #current_user.locations.order("created_at desc").limit(5)
respond_with #locations
end
end
I have also set (and tested unset) the cors headers.
I read somewhere that Rails won't read the content-type header if there's forward slashes in it...
Whilst this doesn't appear to be causing many issues, I do think it's interfering with Doorkeeper that's part of the application.
This wasn't a Rails problem. Turns out I needed to fiddle with some headers etc. in the NG config.
I added the following to app.js
$httpProvider.defaults.useXDomain = true;
// $httpProvider.defaults.withCredentials = true;
delete $httpProvider.defaults.headers.common["X-Requested-With"];
$httpProvider.defaults.headers.common["Accept"] = "application/json";
$httpProvider.defaults.headers.common["Content-Type"] = "application/json";
The second line threw an error but I've left in there for good measure.

Resources