I've built out a fairly complex Rails (2.3.8) app with a lot of jQuery ajax requests. There is an occasional bug, which I have difficultly replicating, where a jQuery $.ajax({..}) request will request a page it shouldn't (like the dash page, which is never called with an ajax request)...
What ensures is absolutely madness. Incredibly strange and terrible errors happen.
For at least a stopgap solution (the app is in production), how can I set up a before filter than will detect any unsolicited xhr/ajax request (or ANY such request on the given controller actions) and kill it before it hits the controller?
In any controller:
before_filter :stop_ajax, :only => [:dashboard]
application_controller.rb:
def stop_ajax
if request.xhr?
render :file => "#{Rails.root}/public/404.html", :status => 404
end
end
Related
I have set up a tableless model as described in the Rails 7 guides:
class ContactForm
include ActiveModel::Model
attr_accessor :name, :email, :message
validates :name, :email, :message, presence: true
end
I have set up an action to check if the submitted content is valid and send the email if it is:
def contact_process
#contact_form = ContactForm.new(contact_form_params)
if #contact_form.valid?
UserMailer.with(#contact_form).contact_form.deliver_later
redirect_to contact_path
else
render :contact
end
end
When there are errors and the contact template is rendered again #contact_form seems to be a blank ContactForm instance and for example #contact_form.errors.count returns 0 even though it was printing the correct number in the console just before the render command.
The problem was due to trying to render a page following a form submission with a 200 rather than a 4xx or 5xx code. A 422 code can be added as follows and is recommended when there's a problem with a model:
render 'contact', status: :unprocessable_entity
Rails 7 ships with Turbo. It expects a form submission to be followed by a 303 code redirect, one exception to this being when you need to render validation errors. Here is how it handles form submission, and why a render command on its own isn't going to work and you're probably going to just get some version of the previous page:
Turbo Drive handles form submissions in a manner similar to link
clicks. The key difference is that form submissions can issue stateful
requests using the HTTP POST method, while link clicks only ever issue
stateless HTTP GET requests.
After a stateful request from a form submission, Turbo Drive expects
the server to return an HTTP 303 redirect response, which it will then
follow and use to navigate and update the page without reloading.
The exception to this rule is when the response is rendered with
either a 4xx or 5xx status code. This allows form validation errors to
be rendered by having the server respond with 422 Unprocessable Entity
and a broken server to display a “Something Went Wrong” screen on a
500 Internal Server Error.
The reason Turbo doesn’t allow regular rendering on 200 is that
browsers have built-in behavior for dealing with reloads on POST
visits where they present a “Are you sure you want to submit this form
again?” dialogue that Turbo can’t replicate. Instead, Turbo will stay
on the current URL upon a form submission that tries to render, rather
than change it to the form action, since a reload would then issue a
GET against that action URL, which may not even exist.
The above solution will work perfectly but in case we are using Devise above will add lots of effort because Devise uses render internally and we need to customize at everywhere.
Apart from the above, another permanent solution is here.
Create a turbo controller:
class TurboController < ApplicationController
class Responder < ActionController::Responder
def to_turbo_stream
controller.render(options.merge(formats: :html))
rescue ActionView::MissingTemplate => error
if get?
raise error
elsif has_errors? && default_action
render rendering_options.merge(formats: :html, status: :unprocessable_entity)
else
redirect_to navigation_location
end
end
end
self.responder = Responder
respond_to :html, :turbo_stream
end
In initializers/devise.rb use this:
config.parent_controller = 'TurboController'
In the case of another controller, you can inherit this controller.
Rails 7 uses Turbo and stimulus framework to increase frontend performance.
You need to install turbo and stimulus.
# This is the method that defines the application behavior when a request is found to be unverified.
# By default, Rails resets the session when it finds an unverified request.
def handle_unverified_request
reset_session
end
I have seen this explanation at Rails 4 Authenticity Token
now my question is when and how every request sometimes become unverified? how it was hapenning? and when.
thankyou, i have tried to search it but i have seen explanation so deep-technical hence i can understand in an easy way
Rails adds a CSRF authenticity token to form submissions.
If you have a Rails-generated form in your browser and you inspect it, you'll see something like this:
<input type="hidden" name="authenticity_token" value="/LV6706J3W++oCASgg8+wuySgIksE9BNjamMbMW8Zv+G039yyxbpcRpUlUzuVbVvodKtDnUbknwo+jsBzsoO8g==">
Rails checks this hidden tag on form submission to make sure it's the same form that Rails generated in the first place. This helps prevent CSRF attacks
If this field's value doesn't match what Rails expects, it goes to the handle_unverified_request method you mentioned.
And it's not just forms, Rails can add tokens to the session to make sure it can match a request to an active session.
Regardless of the source, if Rails gets a mis-match, it wants to handle that as a security threat.
In essence, Rails is asking you "what should I do when I think the request I received is unverified and potentially an attack?"
In this case, Rails would reset_session which logs out the current_user.
Rails allows you to turn off or limit CSRF protection in cases where you may need to do strange things, but it's not advisable in any instances I'm familiar with.
You can do this by changing the options on protect_from_forgery as mentioned in the SO post you linked.
def handle_unverified_request
reset_connection
# validate only for html submit and not for ajax
if request.post? && !request.xhr? && request.content_type != 'multipart/form-data'
redirect_to controller: 'logout', action: 'index', is_invalid_token: true
end
return
end
and then i have log out controller
if !params[:is_invalid_token].nil?
flash[:notice] = "You dont have access with this."
flash[:notice_header] = 'Forbidden Access'
end
redirect_to :controller => 'login', :action => 'index'
I have rails applications where I am loading comments using Ajax after page load.
class CommentsController < ApplicationController
respond_to :js
def index
#comments = Comments.all
respond_with #comments
end
end
It is working as expected. But bingbot is trying to access this url with which it leads to
An ActionController::InvalidCrossOriginRequest occurred in comments#index:
Security warning: an embedded tag on another site requested protected JavaScript. If you know what you're doing, go ahead and disable forgery protection on this action to permit cross-origin JavaScript embedding.
like that it is coming for all url's which are only responding to js format.
I know about rack-cors, but it is for allowing cross side script access, but here it is not.
app/views/comments/index.js.erb
$('.comments_container').html("<%=j render 'comments' %>");
comments.js
jQuery(function() {
return $.ajax({
url: $('.comments_container').data('url')({
dataType: "script"
})
});
});
Assuming you need some help with CORS(Cross-origin resource sharing), You are getting error because your CORS policy is default to "denying" every direct XHR access.
You can use the rack-cors gem https://github.com/cyu/rack-cors to avoid this. Hope this help!
I'm building a single page Rails 4 app that uses the Soundcloud and Instagram gems. I have noticed that sometimes these API requests slow down my app. I am unsure on what the best approach/practice is for handling such requests so that they do not adversely effect the performance of my app? I've looked into asynchronous requests, sidekiq and have also found this example: http://www.jonb.org/2013/01/25/async-rails.html
My Index Controller looks like this:
require 'soundcloud'
class HomeController < ApplicationController
def index
# register a new client, which will exchange the username, password for an access_token
soundcloud_client = Soundcloud.new(
:client_id => ENV['SOUNDCLOUD_ID'],
:client_secret => ENV['SOUNDCLOUD_SECRET'],
:username => ENV['SOUNDCLOUD_USERNAME'],
:password => ENV['SOUNDCLOUD_PASSWORD']
)
# Soundcloud
#activity = soundcloud_client.get('/me/favorites').first(12)
# Instagram
#instagram = Instagram.user_recent_media("1111111", {:count => 12})
end
end
maybe a better approach to this, is to load your index as you would, but do not load the list from soundcloud on that index.
Instead have an Ajax call on your view that calls a pure JSON action inside your controller, doing it that way you will serve the page rapidly, and not block on soundclock. You can put a spinner on your view indicating that the page is still loading. Looking into jquery load method.
https://api.jquery.com/load/
good luck.
I have a controller index action which returns json output.
render :json => my_array.to_json
What type of caching do I have to use here. Does 'page caching' make sense for this.
Or do I have to do action caching like below
caches_action :index
Either action caching or page caching would work fine; page caching would have the benefit of never calling the Rails stack, but it depends on whether you need to control who accesses that Json feed.
I'm a big fan of using page caching if you can get away with it - there are big savings on system resources to be had. :)
EDIT: Page caching example, in case there was any confusion:
class SomeController < ApplicationController
caches_page :index
def index
render :json => my_array.to_json
end
end
Unless I've misunderstood something, that should be all you need to do.
Same considerations should apply to JSON as any other output. If you need to validate access to the data for the user, then action caching is the way to go, otherwise page caching should be fine.
If the data changes due to logic in your app, then both forms of caching are problematic and you are better off using something else.