I have a Rails 4.0 app with an Ember.js frontend. I'm using Ember-Auth in conjunction with Devise to handle authentication. For the most part, everything works. However, if I use Jquery File Upload, then all subsequent queries to the server result in an InvalidAuthenticityToken error. The file upload itself works perfectly, but if for instance I visit the Organizations index page afterwards, I'll get the error. If I reload the page, then the errors stop coming and everything works fine again until I perform another upload.
The uploader looks like this:
didInsertElement: ->
$('#image_upload').fileupload
url: "/images"
formData: [{ name: 'auth_token', value: Whistlr.Auth.get('authToken') }]
success: (response) =>
#get('parentView').get('controller').set('image_token', response.token)
Even if I remove everything but the url, I get the InvalidAuthenticityToken afterwards. Any idea what's happening?
Following my suspicion that the session was getting reset, I tried passing the authenticity_token back the client side from the server. Surely enough, it was changing. So I manually placed the new authenticity_token in the headers. The code looks like this:
def create
image = Image.create(image_params)
render json: {image: image, authenticity_token: form_authenticity_token}, status: 201
end
$('#image_upload').fileupload
url: "/images"
dataType: "json"
formData: [{ name: 'auth_token', value: Whistlr.Auth.get('authToken') }]
success: (response) =>
#get('parentView').get('controller').set('image_token', response.image.token)
$('meta[name="csrf-token"]').attr('content', response.authenticity_token)
Note the last line of the javascript, which replaces the csrf-token. Thankfully, this works. As far as I can tell, it's also secure. (If you see a security flaw, please let me know!) But it still seems weird that this is necessary. Nowhere else in my app do I have to manually replace the csrf-token. Why is this happening here? Is it because the session is getting reset, and if so, why is that happening here but not elsewhere?
Related
I'm attempting to upgrade to Turbo from Turbolinks and I've found that the client is not rendering redirects for form submissions.
Versions:
rails 6.1.4
hotwire-rails 0.1.2
#hotwired/turbo-rails 7.0.0-beta.8
I've ignored the incompatibility between Turbo and Devise for now - just trying to get regular forms working without having to disable Turbo on them.
Here's an example action:
def update
authorize #label
#label.update(label_params)
if #label.save
redirect_to document_labels_path(document_id: #document.id)
else
render :new, status: :unprocessable_entity
end
end
Here's a rendered form:
<form class="simple_form new_label" id="label_form" novalidate="novalidate" action="/documents/72/labels" accept-charset="UTF-8" method="post">
...
</form>
When submitting a valid form, the server will say Processing by LabelsController#create as TURBO_STREAM and correctly serve a 302. It will then serve the 200 for the redirect location. The browser however is left just looking at the submitted form. Changing the redirect status to 303 doesn't change anything.
I added a console.log for every Turbo event:
document.addEventListener("turbo:load", function () {
console.log('TURBO:LOAD')
})
document.addEventListener("turbo:click", function () {
console.log('TURBO:CLICK')
})
document.addEventListener("turbo:before-visit", function () {
console.log('TURBO:BEFORE-VISIT')
})
document.addEventListener("turbo:visit", function () {
console.log('TURBO:VISIT')
})
document.addEventListener("turbo:submit-start", function () {
console.log('TURBO:SUBMIT-START')
})
document.addEventListener("turbo:before-fetch-request", function () {
console.log('TURBO:BEFORE-FETCH-REQUEST')
})
document.addEventListener("turbo:before-fetch-response", function () {
console.log('TURBO:BEFORE-FETCH-RESPONSE')
})
document.addEventListener("turbo:submit-end", function (event) {
console.log('TURBO:SUBMIT-END')
// event.detail
})
document.addEventListener("turbo:before-cache", function () {
console.log('TURBO:BEFORE-CACHE')
})
document.addEventListener("turbo:before-stream-render", function () {
console.log('TURBO:BEFORE-STREAM-RENDER')
})
document.addEventListener("turbo:render", function () {
console.log('TURBO:RENDER')
})
This is what the output is for a successful form submission:
TURBO:BEFORE-FETCH-REQUEST
TURBO:SUBMIT-START
TURBO:BEFORE-FETCH-RESPONSE
TURBO:SUBMIT-END
There is no render event. Investigating event.detail.fetchResponse.response for turbo:submit-end it seems to be perfectly aware that the client should redirect, it just didn't.
Response {type: "basic", url: "http://lvh.me:3000/documents/72/labels", redirected: true, status: 200, ok: true, …}
body: (...)
bodyUsed: true
headers: Headers {}
ok: true
redirected: true
status: 200
statusText: "OK"
type: "basic"
url: "http://lvh.me:3000/documents/72/labels"
__proto__: Response
Update: It is actually performing the redirect and the server is generating the response. The issue is that the client is not rendering the redirect response.
What is happening here is that your application is specifying that it prefers turbo-stream responses over text/html responses. If you were to look at your request headers for the redirect page, you'll likely see the following:
Accept: text/vnd.turbo-stream.html, text/html, application/xhtml+xml
As a result, Rails returns the data with the first type it recognizes, which is text/vnd.turbo-stream.html. Turbo in your browser sees this and, since it's not interpretable as a Turbo Stream, unhelpfully ignores it quietly.
The solution (workaround?) is to make sure you are redirecting to the html version of your page:
redirect_to document_labels_path(document_id: #document.id, format: :html)
This will return the page with a Content-Type of text/html, and Turbo will replace the whole page with the contents.
Jeff's answer is correct but I wanted to share the specific fix for the issue I was having.
If you use HAML or Slim, I've seen it on more than one codebase where developers rename all template files .haml instead of .html.haml (same for Slim). It's never bitten me before using Turbo, but without .html in the filename, part of Rails won't know what format to serve a response in, so it defaults to the request format.
Turbo makes a turbostream request when submitting a form, but if the response is a redirect, it expects it to be text/html in order to render it. If it receives a turbostream response to a redirect request, Turbo just sits there doing nothing with no console errors or warnings (terrible default behavior IMO).
So if your templates do not include .html, just add it back and Turbo will render redirects. You may still need status: :see_other.
More information:
https://github.com/hotwired/turbo-rails/issues/122
https://github.com/hotwired/turbo-rails/issues/287
Adding to the excellent answer of Jeff Seifert.
If you don't need turbo streams, you may also unregister the turbo-stream content type altogether by putting this into an initializer e.g. config/initializers/turbo.rb:
Rails.application.config.after_initialize do
Mime::Type.unregister(:turbo_stream)
end
Note: I'm using Grails 2.5.5.
This is my method in the controller (I know save() shouldn't be a GET, but I'm just testing things out):
def save(Test cmd) {
println cmd.duration
println params.duration
}
This is my client code:
let data = JSON.parse($('#req').val());
$.ajax({
url: url,
data: data,
method: 'GET',
contentType: 'application/json'
});
When this flow is executed, on the controller side, cmd.duration does not print what was sent from the client side (instead it's the default value of zero since duration is typed as an int). On the other hand, params.duration does print what was sent from the client side.
So this indicates that it's not a problem with how the data is getting sent, but instead has to do with some data binding issue?
Also, just for reference, POST works perfectly fine with the above server-side code. The command object gets populated appropriately as long as I change the client code accordingly (changing method type and stringifying the JSON):
let data = JSON.parse($('#req').val());
$.ajax({
url: url,
data: JSON.stringify(data),
method: 'POST',
contentType: 'application/json'
});
I know there are similar questions out there, but it seems like most of them deal with issues with POST requests. So this is a bit different.
Any help is appreciated!
It looks like I just needed to remove the contentType in the ajax call on the client side for the GET request. Once I did that, everything worked as expected.
Not sure if that is expected behavior, but it works for me for now.
I'm trying to get rows that match a var, In my case a url. in the database and return the whole row as a json format.
Basically if url in table1 matches the url under eventurl in table2. Then the whole row is passed through to the ajax request as a jsonformat.
Heres what i have so far.
Routes.rb
resources :gig do
scope constraints: { format: "json" } do
get :gigdata, on: :member
end
end
In my ajax call i have this
url: 'gigdata/' + gigurlofevent , (no need to include the whole url ajax file here as its working elsewhere)
and in my controller i have this
respond_to :json, only: :gigdata
def gigdata
gig = Gigstable.where(eventurl: (params[:gigurlofevent]))
render json: gig
end
Now at the moment, I can't get into the gigdata with a byebug.
I'm wondering what i need to do/ what ive missed
Thanks
Sam
edit
Heres the start of the ajax call down the the success function
$.ajax({
beforeSend: function(xhr) {
xhr.setRequestHeader("Content-Type", "application/json");
},
type: 'GET',
crossDomain: true,
dataType: 'json',
url: 'gigdata/' + gigurlofevent ,
success: function(json) {
debugger;
Common problem:
You wrote:
(no need to include the whole url ajax file here as its working
elsewhere)
Actually you need to include complete code because common problem lays in Content-Type field. You need to specify content type of request in header like 'application/json'.
I am testing my backend api with curl
For example:
curl http://localhost/api/v1/some_action -H 'Content-Type: application/json' -vvv
Last parameter -vvv very useful to debug requests because it makes curl to output request and response with headers in console.
Another way to debug ajax
Open developer tools (for chrome it's: F12 or ctrl+shift+i), click on network tab and click on XHR filter button. Reload page with ctrl+r and execute your ajax request one more time. Information about your ajax request will appear in the window below filters. Now you can check out what kind of data comes from server and whats going wrong.
Golden rule
Rails server outputs all requests in console. Don't be shy to read output when something works not like you expected.
In my project I have to integrate the library and parse the files presented in csv format. To access the library and get the information form that file I use $ajax as follows:
<script>
$(document).ready(function(){
$.ajax({
type: "GET",
url: "http://stats.xxx.tv/osexternal/reports/xxxxx/xxx_2014_YTD/2014-03-12.csv",
contentType: 'application/json',
dataType: 'json',
username: 'xxxx#xxxx.com',
password: 'dT$xxxx%949',
success: function (){
console.log('success');
},
error: function(jqXHR, textStatus, errorThrown) {
console.log(textStatus, errorThrown);
}
});
});
<script>
Can anyone let me know what's the wrong with this approach as I am getting cross domain problem.And please let me know any alternatives by using gems.
Thanks for your help in advance!
What you're running into appears to be a CORs issue of some kind. Things to note about CORs issues:
It is a security policy for Javascript, so it only affects calls in/from JS.
Being able to access it from the browser 'directly' doesn't have anything to do with CORs
CORS can be really irritating
Now, on how to solve it, you can try adding:
with_credentials: true to the Ajax arguments, but I have a feeling it's going to be something weirder than that... as well, since you have to include a username and password it's probably best not to expose those on the client for anyone to have...
So, what I'd do is make the call on the server (example is for a rails controller action, but the method could be used in a Sinatra app just the same) then return the CSV to the browser:
require 'net/http'
class MyController < ActionController::Base
# ...
def get_csv
uri = URI('http://stats.adap.tv/osexternal/reports/xxxxx/xxx_2014_YTD/2014-03-12.csv')
csv_request = Net::HTTP::Get.new(uri)
csv_request.basic_auth("username", "password")
csv_data = csv_request.request.body
csv
end
end
I'm assuming you are using Ruby because of your "gems" reference. Here's the doc for Net::HTTP
http://ruby-doc.org/stdlib-2.1.1/libdoc/net/http/rdoc/Net/HTTP.html
and a slightly easier to digest version:
http://www.rubyinside.com/nethttp-cheat-sheet-2940.html
In general, it'll always be easier (and safer) to have your server make a request to an external host (this is a broad generalization and there are absolutely cases where this isn't what you want). If you need to make a cross domain request I'd suggest starting with:
http://www.html5rocks.com/en/tutorials/cors/
It'll probably give you some good tips to figure out why it's not currently working.
Best,
I am doing a site which submits a form to a different server. For upload progress tracking I use: for server side the NginxHttpUploadProgressModule und for client side - jquery-upload-progress. I have tested the setup by submitting the form to the same server and everything worked fine. Submitting to another server doesn't show the progress tracking(cross domain scripting). After hours of investigating this matter I came to the conclusion that the GET request generated by JQuery is at fault.
The query looks like this:
http://domain.com/upload/progress/?X-Progress-ID=39b2825934dbb2f33fe936df734ff840&callback=jsonp1249230337707&_=1249230345572
From the NginxHttpUploadProgressModule site:
The HTTP request to this location must have either an X-Progress-ID parameter or X-Progress-ID HTTP header containing the unique identifier as specified in your upload/POST request to the relevant tracked zone. If you are using the X-Progress-ID as a query-string parameter, ensure it is the LAST argument in the URL.
So, my question is how do I append the X-Progress-ID parameter to the end of the jquery GET request or set the X-Progress-ID header?
This doesn't work with jsonp(code from jquery.uploadProgress.js):
beforeSend: function(xhr) {
xhr.setRequestHeader("X-Progress-ID", options.uuid);
}
Currently the request is generated this way(code from jquery.uploadProgress.js):
jQuery.uploadProgress = function(e, options) {
jQuery.ajax({
type: "GET",
url: options.progressUrl + "?X-Progress-ID=" + options.uuid,
dataType: options.dataType,
success: function(upload) {
...
I solved the GET parameter problem(code from jquery.uploadProgress.js):
jQuery.uploadProgress = function(e, options) {
jQuery.ajax({
type: "GET",
url: options.progressUrl,
dataType: options.dataType,
data: "X-Progress-ID=" + options.uuid,
success: function(upload) {
...
Modified GET request looks like this:
http://domain.com/upload/progress/?callback=jsonp1249230337707&_=1249230345572&X-Progress-ID=39b2825934dbb2f33fe936df734ff840
The nginx webserver is now correctly responding.
However as Ron Evans pointed out the client side progress tracking part won't work unless NginxHttpUploadProgressModule is modified.
You simply cannot fire an XmlHttpRequest from a webpage, to a domain different from the page's domain. It violates security definitions that are default on all browsers.
the only thing that I can think of that you can do is to use Flash or Silverlight to initiate the progress calls (Flash and Silverlight can, given the correct crossdomain.xml setup, send async requests from the browser to preset list of domains)
or, setup a browser addin (say Firefox plugin, or IE ActiveX, or Embedded WinForm control) that can initiate calls without the same-domain restriction (as the request will not originate from the webpage, but from the browser itself)
You need to install the Apache module for upload status as well, just using the jQuery plugin will not work.
To respond to Ken, I suggest you familiarize yourself with JSONP spec, since JSONP was created specifically to handle cross-domain Javascript calls.
Anyhow, this code works great in Passenger/Apache WITH my modified Apache module. Without modifying the extension for Nginx it will not work with a JSONP call.
I made a minor modification that solved the problem for me, you can check it out here:
http://github.com/tizoc/nginx-upload-progress-module/commit/a40b89f63b5a767faec3c78d826443a94dc5b126