I have an action triggered by an AJAX request generated by Ajax.InPlaceEditor or InPlaceCollectionEditor like this:
new Ajax.InPlaceCollectionEditor('agent_email', 'inspections/<%= #inspection.id %>/update_field',
{
collection: [<% #agents.each do |agent| %>
'<%= agent.email %>',
<% end %>],
okText: 'Update',
cancelText: 'Never mind',
savingText: 'Updating...'
});
At the other end, the action contains this:
def update_field
--some code here--
if success
puts "stored change"
render :text => result
else
puts "did note change store"
render :text => inspection.errors.to_json, :status => 500
end
end
Once any of the render methods are reached, the session expires, and next time the user send a request, Devise sends them to the logon on page.
Even though I am exempting update_field from authentication (before_filter :authenticate_user!, :except => :update_field), the session is still getting reset.
I have looked at the answer at a very similar question at Devise session immediately expiring on .js call [AJAX], but it is not solving my particular problem.
Any ideas?
I got this to work by getting the code from http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails (prototype-snippet.js):
/*
* Registers a callback which copies the csrf token into the
* X-CSRF-Token header with each ajax request. Necessary to
* work with rails applications which have fixed
* CVE-2011-0447
*/
Ajax.Responders.register({
onCreate: function(request) {
var csrf_meta_tag = $$('meta[name=csrf-token]')[0];
if (csrf_meta_tag) {
var header = 'X-CSRF-Token',
token = csrf_meta_tag.readAttribute('content');
if (!request.options.requestHeaders) {
request.options.requestHeaders = {};
}
request.options.requestHeaders[header] = token;
}
}
});
... within a Javascript block in my application.html.erb:
<script type="text/javascript">
(... the code from above)
</script>
Also don't forget to add:
<%= csrf_meta_tag %>
in the same file towards the top (if not already there).
The document "CSRF Protection Bypass in Ruby on Rails" explains why this works.
Related
I want to know if it's possible to redirect with controller from an ajax request in Rails 6 ?
I tried to use
redirect_to "url"; return and render :js => "window.location.reload" don't work for me :(
Thank you for your help :)
This works for me:
window.location.href = '/path'
Example:
$.ajax({
url: uri,
type: "POST",
data: form,
dataType: 'json',
processData: false,
contentType: false
}).done(function(e) {
window.location.href = '/project_proposals'
})
Do it in your controller.
render js: "window.location='#{goal_path(#goal)}';"
Ideally you'll want to keep as much business logic out of your JS as possible for rails apps. You also can't cleanly use your route helper methods in js. You can however setup your "redirect" in your controller.
app/view/controllers/goals/adopt_controller.rb
# :nodoc:
class Goals::AdoptController < ApplicationController
def update
# business logic....
# "redirect" the client
render js: "window.location='#{goal_path(#goal)}';"
# or make a reusable js view. this will search a wide variety of possible file names. In this case it'll match /app/view/appliation/redirect.js.erb
# #to = goal_path(#goal)
# render 'redirect'
# the above lets rails guess what you want to do. this is a bit more explicit to only render this view is the client can accept a js response. Other scenarios will throw an error
# #to = goal_path(#goal)
# respond_to do |format|
# format.js { render 'redirect' }
# end
end
end
/app/view/appliation/redirect.js.erb
window.location='<%= escape_javascript to %>';
Is here any possibility to modify devise SessionsController for ajax communication?
Edit
I found the solution, and posted it into answers, thanks
1. Generate Devise controllers so we can modify it
rails g devise:controllers
Now we have all controllers in the app/controllers/[model] directory
2. Edit routes.rb
Let's set Devise to use our modified SessionsController
First add this code (of course change :users to your devise model) into config/routes.rb
devise_for :users, controllers: {
sessions: 'users/sessions'
}
3. Modify sessions_controller.rb
Find the create method and change it to
def create
resource = User.find_for_database_authentication(email: params[:user][:email])
return invalid_login_attempt unless resource
if resource.valid_password?(params[:user][:password])
sign_in :user, resource
return render nothing: true
end
invalid_login_attempt
end
Create new method after protected
def invalid_login_attempt
set_flash_message(:alert, :invalid)
render json: flash[:alert], status: 401
end
4. devise.rb
Insert this into config/initializers/devise.rb
config.http_authenticatable_on_xhr = false
config.navigational_formats = ["*/*", :html, :json]
5. Invalid email or password message
Insert a new message into config/locales/devise.en.yml under the sessions
invalid: "Invalid email or password."
6. View
= form_for resource, url: session_path(:user), remote: true do |f|
= f.text_field :email
= f.password_field :password
= f.label :remember_me do
Remember me
= f.check_box :remember_me
= f.submit value: 'Sign in'
:javascript
$(document).ready(function() {
//form id
$('#new_user')
.bind('ajax:success', function(evt, data, status, xhr) {
//function called on status: 200 (for ex.)
console.log('success');
})
.bind("ajax:error", function(evt, xhr, status, error) {
//function called on status: 401 or 500 (for ex.)
console.log(xhr.responseText);
});
});
Important thing remote: true
The reason why I am using status 200 or 401 unlike {status: 'true'} is less data size, so it is much faster and cleaner.
Explanation
On signing in, you get these data in params
action: "create"
commit: "Sign in"
controller: "users/sessions"
user: {
email: "test#test.cz"
password: "123"
remember_me: "0"
}
utf8: "✓"
Before signing, you need to authorize the user.
resource = User.find_for_database_authentication(email: params[:user][:email])
User.find_for_database_authentication
If user is found, resource will be filled with something like
created_at: "2015-05-29T12:48:04.000Z"
email: "test#test.cz"
id: 1
updated_at: "2015-06-13T19:56:54.000Z"
Otherwise will be
null
If the user is authenticated, we are about to validate his password
if resource.valid_password?(params[:user][:password])
And finally sign in
sign_in :user, resource
Sources
SessionsController
Helped me
Andreas Lyngstad
I'm creating an application, which has authentication based on external API with login/register methods. I have a simple controller called RegistrationsController which fires a request using Curb.
This is the controller:
class RegistrationsController < ApplicationController
def new
end
def create
if params[:user][:email].present? && params[:user][:password].present? && params[:user][:phone].present? && params[:user][:login].present?
# API request
password = params[:user][:password]
body = {
"register" => {
"password" => password,
"email" => params[:user][:email],
"phone" => params[:user][:phone],
"login" => params[:user][:login]
}
}
c = Curl::Easy.http_post("http://domain.com/register", body.to_json
) do |curl|
curl.headers['Content-Type'] = 'application/json'
curl.headers['application'] = 'appname'
curl.headers['device'] = 'www'
end
c.perform
response_body = JSON.parse(c.body_str)
throw response_body # This line ALLWAYS gives me 'login taken' error
return
else
#user = User.new(params[:user])
render action: "new", notice: 'Error'
end
end
end
(I also have a views/registrations/new.html.slim view with a simple form but it's not important right now.)
My routes look like this:
match 'users/sign_up' => 'registrations#new', :via => :get, :as => :user_register
match 'users/sign_up' => 'registrations#create', :via => :post, :as => :user_create
My application, after I click the "Register" button on the registrations#new page, is triggering the Curb request two times. As a result, I'm always getting a 'login taken' error. The user is registered successfully but I'm not getting any result from the first request, just from the second one.
It's somehow caused by Rails and I'm 100% sure about it because it can be seen in the API server logs that the request is triggered twice. Also, I have exactly the same script written in PHP and, in there, the registration works fine.
In my Rails dev console, the request is triggered just one time so it's really strange.
Does anyone have any idea what is going on here?
I found the answer.
If anyone struggles with something similar, it was caused by the c.perform line. Just remove it and it will work fine.
I should study the docs better in the future.
I am working on a form to submit it by AJAX instead of http.
This is the form :
<%= form_for(:image, :remote => true, :url => {:controller=> 'questions',:action => 'upload'},:multipart => true) do |f| %>
<%= f.file_field :image, :onchange => "$(this).parents('form').submit();" %>
<% end %>
I have set the :remote => true option above and submitting the form with an onchange event . I have the following code in controller :
def upload
if request.xhr?
#image = Image.new(params[:image])
#image.save
respond_to do |format|
format.js { render :layout=>false }
end
else
render :text => 'Request Wasnt AJAX'
end
end
My action renders the text everytime , the request does not seem to be AJAX style despite the remote tag being set (it appears correctly even in the final HTML). I can't figure out where I am going wrong with this . I have tested it in the latest browser version of FF and Chrome , so I don't think it's a browser issue. Any ideas ?
Update : I did some more debugging attempts . The issue is with the file field , if I replace the file field with text field , the request is AJAX (everything else remaining same) . But with a file field it always sends a non AJAX request.
Note : Overall objective is to upload an image via AJAX request, with the response rendering nothing, no HTML, no redirection, no reload of the page.
Got it to work by installing the Remotipart gem . To upload image files using ajax form submission , this is the only way . Find the git here :https://github.com/JangoSteve/remotipart
JQuery form.sumbit() submits a form the normal way (Not Ajax). You have to do it differently:
$('#submitButton').click( function() {
$.ajax({
url: 'some-url',
type: 'post',
dataType: 'json',
data: $('#myForm').serialize(),
success: function(data) {
// ... do something with the data...
}
});
});
I'm passing a ajax call to update data in my application through twitter bootstrap modal window. The ajax code is given below:
$(document).ready(function(){
var link=$('#link_hash').val();
$("#update_link").click(function(){
console.log("I'm here");
$.ajax({
url: "profiles/update_link",
type: "POST",
dataType: "html",
data: {link: link,data: $('#link_hash').val() },
success: function(data) {
// some code
},
error: function(data1) {
// some code
}
});
});
});
I have modifies route.rb file to match it to my controllers "update_link" method.
The code in my method is given below:-
def update_link
#link=Link.find_by_link(params[:link])
#tlink=Link.find_by_link(params[:data])
logger.info "=========kkkkkkkkkkkkkk=================================#{#link.inspect}"
logger.info "=========kkkkkkkkkkkkkk=================================#{#tlink.inspect}"
logger.info "=========kkkkkkkkkkkkkk=================================#{params.inspect}"
respond_to do |format|
if #tlink.nil?
#link.update_attributes(:link => params[:data])
...some code....
else
...some code...
end
end
end
end
So in the server log it's showing -
Started POST "/profiles/update_link" for 127.0.0.1 at 2013-02-20 12:08:20 +0530
Processing by ProfilesController#update_link as HTML
Parameters: {"link"=>"9bfzjp", "data"=>"9bfzjpaaa"}
WARNING: Can't verify CSRF token authenticity
Completed 401 Unauthorized in 6ms
So clearly "logger.info" is not showing up...Now after searching I was able to solve the WARNING but still 401 is present...How to solve this??
Thanks in advance....
According to your server log, you are not passing CSRF token, so rails automatically considers request to be malicious and flags it as unverified. default handling of unverified requests is to reset session. Can you comment out protect_from_forgery or add skip_before_filter :verify_authenticity_token to your controller to see if it the case?
If you want to include authenticity token in your ajax request (highly recommended) you can add it to headers in your ajax request:
headers: {
'X-Transaction': 'POST Example',
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
}
Add skip_before_filter :authenticate_user! to your controller.