how to access response headers in React from Rails api-pagination gem - ruby-on-rails

So I got this gem to work in limiting the amounts returned in the JSON response. However I'm not sure how to access the links in the headers that let you get to the next and previous pages.
Here is my controller:
def index
movies = Movie.all
paginate json: movies, per_page: 50
end
This is the part of the readme for the gem that says you can access next and prev through the headers:
$ curl --include 'https://localhost:3000/movies?page=5'
HTTP/1.1 200 OK
Link: <http://localhost:3000/movies?page=1>; rel="first",
<http://localhost:3000/movies?page=173>; rel="last",
<http://localhost:3000/movies?page=6>; rel="next",
<http://localhost:3000/movies?page=4>; rel="prev"
Total: 4321
Per-Page: 10
# ...
I can see them in Postman, but have yet to find a way to access them in my React front end. Thank you for any help you're able to offer.
This is the url for the gem: api-pagination gem

Are you accessing this data in React through a fetch request? Something like
fetch('url').then(res => {res.json()})
You should be able to access headers with fetch('url').then(res => res.headers). You can then manipulate them or save them to state or whatever you want to do with them. Fetch API docs here.

Actually it worked to me. if anyone come here by searching.
Add expose: ['header-key'] in cors configuration in your rails app.
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head], expose: ['X-Pagination']
end
end
After adding expose, you'll be able to get the header key in your client-side code by using response.headers.get("X-Pagination").
Note:- I verified it by using fetch API call resonse.

What I did with in a similar situation was to add the following in my rails controller:
response.header['my_attribute_name'] = my_value
headers['Access-Control-Expose-Headers'] = 'my_attribute_name'
Then on the front-end I just used:
response.headers.get("my_attribute_name")
This solved my issue.

response.headers.get('Link') solved it!

This is a CORS restriction.
White list headers (by implementing Access-Control-Expose-Headers in the server) that you are using for pagination.
Only then you can access any header on a CORS request.
Please also note that, this restriction does not apply on requests from the same origin.

Related

Rails API : how to allow one single url to call my API?

My environment is a Rails 7 API + React frontend.
The issue is that we need to call the API from the frontend only and prevent others urls to make any requests. I tried to configure this with the gem rack-cors and its file config/initializers/cors.rb as this (allowing only localhost:3001) :
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:3001'
resource '*',
headers: :any,
methods: %i[get post put patch delete options head],
expose: %w[Authorization Uid]
end
end
The problem is that a POST request made with POSTMAN is not blocked.
What did I miss ?
Thanks.
Julien

Why does rails not recognize `Accept: application/json` in the request header?

I have some frontend javascript that makes an asynchronous http request to my backend rails server. On the frontend I am not using XHR (I use axios, although that's not entirely relevant to the question).
In the request, I set the following to tell the server I'm sending JSON and to make sure I get JSON back:
const config = {
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
};
In my backend Rails controller if inspect the request I can verify the Accept header:
> request.headers
"HTTP_ACCEPT"=>"application/json, text/plain, */*"
However ActionPack/Rails still does not respect that and defaults to the format being :html
> request.format
=> #<Mime::Type:0x00007fe223919f80 #hash=-1773238723920954657, #string="text/html", #symbol=:html, #synonyms=["application/xhtml+xml"]>
Why is that?
I know I can append .json to my request URL to "force" it to specify that format, but is that the only option? I can append it easily but it seems like an implementation specific to Rails and not really the "right" approach.
Additionally, the source code for the request.format method explicitly sets :json as the format on XHR requests - does rails only respect XHR requests at the moment?
Thanks!
What you are doing is correct, but sending the Axios/Fetch API requests from browser will show "CORS" error from the browser end(you can see it in your browser dev tools console). You can know more about it from MDN Docs
To resolve this, You'll need to send the Access-Control-Allow-Origin Header to the requests you receive on your web server. You can do it manually by adding this header in application_controller.rb or Simply use a gem like rack-cors. I'll show you how to do using rack-cors gem:
Add gem 'rack-cors' in your Gemfile
Run the command bundle i
In your config/application.rb file, add the following lines:
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', headers: :any, methods: [:get, :post, :options]
end
end
Now restart your rails server
You can also see above instructions and more details here
Now make the API call again without forcing .json at the end of the URL and it should work.

rails 4 API gives 401 unathorized response after successful log in using angular2-token package

I have a setup in which I have a rails 4 API having the gem devise_token_auth and hosted as a separate application so I have also rack-cors configured to handle cross origin requests. Using angular2-token on my front end Angular 2 applicaiton I have been able to successfully sign up and sign in as well as sign out users via my API.
The issue however, which I have encountered occurs only when the user is signed in and upon refreshing the browser I get this error in the rails API console as well as in the browser, checked in firefox as well as chrome.
Started GET "/api/v1/auth/validate_token" for 127.0.0.1 at 2017-02-06 17:42:49 +0500
Processing by DeviseTokenAuth::TokenValidationsController#validate_token as JSON
followed by
SELECT "users".* FROM "users" WHERE "users"."uid" = $1 LIMIT 1 [["uid", "abc#xyz.com"]]
Completed 401 Unauthorized in 76ms (Views: 0.2ms | ActiveRecord: 0.3ms)
My initial assumption during the configuration of this package in my Angular2 app was that it will implicitly include authentication headers in each request. However after repeatedly going through the gem's documentation I also added the headers myself when I initialize the token service in my app.component.ts file.
this._tokenService.init({
apiPath: API_PATH,
globalOptions: {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
"access_token_name": localStorage.getItem('accessToken'),
"client_name": localStorage.getItem('client'),
"uid_name": localStorage.getItem('uid')
}
}
});
Even after that the response hasn't changed to the request and I was unable to receive these headers on the server end as well.
However after hours of inspection an idea finally came to me which was to inspect the headers m getting on the server and when I used ruby's request.header.inspect on my server end application I get the following output with the information required for validation of the token but it seems that the name of the keys of these header values are different form what the devise_token_auth expects to validate token (I went through the source of the devise_auth_token gem here.
"HTTP_ACCESS_TOKEN_NAME"=>"xxxxxxxxxxxxxxxxxx", "HTTP_EXPIRY"=>"xxxxxxxxxxxxxxxxxx", "HTTP_UID"=>"abc#xyz.com", "HTTP_CLIENT_NAME"=>"xxxxxxxxxxxxxxxxxx", "HTTP_TOKEN_TYPE"=>"Bearer"
What I believe is the user is not being set by the devise_token_auth gem based on the headers that are being passed.
After repeatedly going through the documentation of Angular2-token as well as devise_token_auth gem I am confused whether or not to manually add headers for authentication because I believe they are being passed already but with different keys.
I would just like to know if that is the case I am experiencing its been almost a full day and I cannot figure out a way to pin point the reason behind the 401 response.
Thanks a lot.
EDITED:
Moreover I am also getting nil when accessing current_user or any devise helper after successful sign in on server end.
Here are the rack-cors configuration for my api rails applicaiton as well.
application.rb
config.middleware.use Rack::Cors do
allow do
origins '*'
resource '/cors',
:headers => :any,
:methods => [:post],
:credentials => true,
:max_age => 0
resource '*',
:headers => :any,
:expose => ['access-token', 'expiry', 'token-type', 'uid', 'client'],
:methods => [:get, :post, :options, :delete, :put]
end
end
The headers I get upon inspecting are following:
HTTP_ACCESS_TOKEN
HTTP_CLIENT
HTTP_EXPIRY
HTTP_TOKEN_TYPE
HTTP_UID
These are the headers sent even if I don't mention any headers while configuring the angular2-token package.
I am confused why it lets me login in the first place and later throw an error with a 401 code and response of
{"success":false,"errors":["Invalid login credentials"]}
When I try and manually check token's validation using the following code
this._tokenService.validateToken().subscribe(
res => console.log(res),
error => console.log(error)
);
You should also pass Expiry and Token-type on requests for devise_token_auth to authenticate, something like this:
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Uid', this.uid);
headers.append('Client', this.client);
headers.append('Access-Token', this.access_token);
headers.append('Expiry', this.expiry);
headers.append('Token-Type', 'Bearer');
this.http.post('http://my-api.com/', JSON.stringify(resource), {headers: header}).subscribe((res)=>{
#Your Logic Here
});
This example is for generic HTTP requests, but you can apply that rule on your angular token plugin. ie.:
this._tokenService.init({
apiPath: API_PATH,
globalOptions: {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
"access_token_name": localStorage.getItem('accessToken'),
"client_name": localStorage.getItem('client'),
"uid_name": localStorage.getItem('uid'),
"expiry_name": localStorage.getItem('expiry'),
"token-type_name': 'Bearer'
}
}
});
You have set custom headers name for devise_token_auth? First example works with default configuration, without _name in the end of the headers' names, you should try modifying if that is the case.
After spending a few days on this issue and going through multiple threads of related issues repeatedly posted on related topics I came across the following issue and I realized I have rails 4 and have used rails-api gem to generate my API.
After that I created a rails 5 API with --api option (without rails-api gem) and with devise_token_auth and rack-cors on my api end I was successful in sending authorized request using the angular2-token package. Along with that I was also able to send authorized http post requests with the authorization headers access-token, token-type, expiry, uid as mentioned in the devise_token_auth gem's documentation.
This might not be the exact solution or I may not have pinpointed the cause of the issue but this was what worked for me.

How to serve a sitemap.xml using reverse proxy in rails?

Due to business requirements, I need to create a new sitemap, every time a new page is added in the admin panel. We're using Heroku, so we looked into the sitemap_generator gem to do this. We are uploading the sitemap every time the rake sitemap:refresh is called.
But the sitemap needs to be inside our domain such as https://example.org/sitemap.xml. So we decided to use reverse proxy (with rack-reverse-proxy gem)
in our config.ru we have
use Rack::ReverseProxy do
reverse_proxy '/sitemap.xml', 'http://our-bucket.amazonaws.com/sitemaps/sitemap.xml', :timeout => 15000, :preserve_host => true
end
and our robots.txt file is
User-Agent: *
Allow: /
Disallow: /admin
But when we submit in google webmaster tools, I get an error, saying URL restricted by robots.txt, when I try to access directly in the browser https://our_domain.com/sitemap.xml I get an
<Error>
<Code>InvalidArgument</Code>
<Message>Unsupported Authorization Type</Message>
<ArgumentName>Authorization</ArgumentName>
but accessing the s3 link, http://our-bucket.s3.amazonaws.com/sitemaps/sitemap.xml our sitemap.xml is displayed correctly.
Any ideas? Is what we're attempting to do even possible?
Ran into this same issue because I was on a system that was behind Basic Auth, and so it was passing along that header, which S3 did not like.
Resolved mine by updating reverse_proxy to this latest commit (for some reason the setting I needed didn't make it into the latest release tag):
gem 'rack-reverse-proxy', require: 'rack/reverse_proxy', git: 'https://github.com/waterlink/rack-reverse-proxy.git', ref: 'a4f28a6'
and adding the following setting to reverse_proxy:
config.middleware.use Rack::ReverseProxy do
reverse_proxy_options stripped_headers: ['Authorization']
... <rules here>
end
This question was old but hopefully this helps someone in the future.
You could create an action that responds as xml like so app/controllers/sitemap_controller.rb:
layout false
def index
#my_pages = Pages.all
render formats: :xml
end
And correspondent view file app/views/sitemap/index.xml.builder:
base_url = request.url.chomp('sitemap.xml')
xml.instruct! :xml, version: '1.0', encoding: 'utf-8'
xml.tag! 'urlset',
'xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9',
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
'xsi:schemaLocation' => 'http://www.sitemaps.org/schemas/sitemap/0.9
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd' do
#my_pages.each do |page|
xml.url {
xml.loc URI.join(base_url, page.url)
}
end
end
Don't forget to create a route for it:
config/routes.rb:
get '/sitemap.xml', to: 'sitemap#index'
No need for reverse proxy, you could also create an rake task to ping search engines and you're good to go.
Happy coding!

Swagger-ui only sending OPTIONS not POST http method despite working API

I am using Swagger-UI to browse my own API, built with grape and automatically documented with grape-swagger.
I've googled and tried every suggestion I can find, but I cannot get POST to work. Here's my headers:
header "Access-Control-Allow-Origin", "*"
header "Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, PATCH, DELETE"
header "Access-Control-Request-Method", "*"
header "Access-Control-Max-Age", "1728000"
header "Access-Control-Allow-Headers", "api_key, Content-Type"
I just threw in everything suggested. I've enabled all the HTTP methods in supportedSubmitMethods and I have tested the API using the POSTMAN Chrome extension and it works perfectly. Creates a user properly and returns the correct data.
However all I get with swagger post is the server reporting:
Started OPTIONS "/v1/users.json" for 127.0.0.1 at 2012-12-21 04:07:13 -0800
and swagger response looking like this:
Request URL
http://api.lvh.me:3000/v1/users.json
Response Body
Response Code
0
Response Headers
I have also tested the OPTIONS response with POSTMAN and it is below:
Allow →OPTIONS, GET, POST
Cache-Control →no-cache
Date →Fri, 21 Dec 2012 12:14:27 GMT
Server →Apache-Coyote/1.1
X-Request-Id →9215cba8da86824b97c6900fb6d97aec
X-Runtime →0.170000
X-UA-Compatible →IE=Edge
I had the same problem and just solved it, hope this helps somebody.
Swagger-UI accepts multiple parameters through POST only through a 'form' paramType, not 'body' paramType, referenced in this issue https://github.com/wordnik/swagger-ui/issues/72.
I used the branch :git => 'git://github.com/Digication/grape-swagger.git' changing 'post' request paramType to 'form'. Generated xml output for swagger_doc (probably at path/swagger_doc/api or similar) should look something like this:
<api>
<path>/api/v2/...</path>
<operations type="array">
...
<httpMethod>POST</httpMethod>
<parameters type="array">
<parameter>
<paramType>form</paramType>
...More
Not
<paramType>body</paramType>
...More
I used the grape-swagger-rails gem to automatically install swagger-ui on localhost (files can also be downloaded from the swagger-ui site), and everything works!!
Had the same problem. Fixed by adding CORS
add into Gemfile:
gem 'rack-cors', :require => 'rack/cors'
add into application.rb
config.middleware.use Rack::Cors do
allow do
origins '*'
# location of your API
resource '/*', :headers => :any, :methods => [:get, :post, :options, :put]
end
end
be sure that you've changed location of your API here.
Nice to hear you are using grape-swagger: I think it is awesome :)
I am not entirely sure you are having the same problem, but when testing locally from the browser it will try to check if the origin is the same as requested, so to make sure I do not get that error, I created a small middleware that will tell the browser we allow all origin.
I am using a rails process (created with the awesome rails-api gem), so I create a new file in lib/middleware/access_control_allow_all_origin.rb with the following content:
module Middleware
class AccessControlAllowAllOrigin
def initialize(app)
#app = app
end
def call(env)
status, headers, body = #app.call(env)
allow_all_origin!(headers)
[status, headers, body]
end
private
def allow_all_origin!(headers)
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Request-Method'] = '*'
end
end
end
and at the bottom of my application.rb I just add the middleware as follows:
require 'middleware/access_control_allow_all_origin'
config.middleware.insert_after Rack::ETag, Middleware::AccessControlAllowAllOrigin
Hope this helps.
I do not know about the solution for ruby-on-rails as I am using Swagger with play framework 2.0.2.
I provided a domain name to it and changed the basePath to domain name in application.conf file as swagger.api.basepath="domain-name" and it worked.
You can change the basePath in api-docs to domain-name. I read about the api-docs on
api-docs.
And does your web server hijack headers? If you are using NGinx for example, your "OPTIONS" request might not send the appropriate values as a response, in some cases.
What is your OPTIONS request response? Can you dump it out here? I'll tell you if it can be that.

Resources