Currently I have an Ajax call via a button click that updates a shopping cart, but the controller that it posts to has the authenticate_user! before_filter applied, which is what I want, as a user must be logged in.
Because it's an ajax call I normally just get a 401 error and no redirect, so to solve this I have:
$(document).on "ajaxError", (event, request, settings) ->
if request.status == 401
window.location.href = '/users/login'
However this is causing me issues with getting the flash[:notice] to appear as it's lost by the time i get to the login page.
From doing some reading on various posts on here I understand that I can use flash.keep which would persist my message, but I think to do that I am going to have to change my approach on handling the redirect. If I can do this in the controller I could also use if request.xhr?, couldn't I?
My question is how would I add this functionality whilst keeping all existing functionality of the authenticated_user! helper method. From looking at the devise docs the helper is dynamically built, isn't it?
I know I could put a helper into my application controller:
def authenticate_user!
super
end
So expanding on that I have tried
def authenticate_user!
if request.xhr?
respond_to do |format|
format.html { redirect_to new_user_session_path }
end
else
super
end
But when clicking the update button, which is this
$(window).load ->
$('a[data-target]').click (e) ->
e.preventDefault()
$this = $(this)
if $this.data('target') == 'Add to'
url = $this.data('addurl')
new_target = "Remove from"
else
url = $this.data('removeurl')
new_target = "Add to"
$.ajax url: url, type: 'put', success: (data) ->
$('.badge-number').html(data)
$this.find('span').html(new_target)
$this.data('target', new_target)
I get this error in the console
Routing Error
No route matches [PUT] "/users/login"
Not sure where to go from here.
This sounds quite high-friction from the user point of view. If I click "add to cart", I expect the thing to be added to the cart. I certainly don't expect to be redirected to a login/signup page. At this point, I would just navigate away.
A better approach is, if user is not logged in, create a guest user and write its id to the cookies. Guest user has cart, can add stuff, etc.
Then, at checkout page, you offer the choice "login" / "sign up". In either case, you get a real user. At which point you migrate guest cart and delete the guest user.
Example:
def current_or_guest_user
return guest_user unless current_user
# current user exists. And we found a guest user
if session[:guest_user_id]
transfer_guest_user_stuff # cart, posts and what-have-you
guest_user.destroy
session[:guest_user_id] = nil
end
current_user
end
def guest_user
unless #cached_guest_user
#cached_guest_user = User.find_or_create_by(id: session[:guest_user_id], is_guest: true)
session[:guest_user_id] = #cached_guest_user.id
end
#cached_guest_user
end
As the first piece of advice, You could use Trubolinks gem which provide all AJAX functionality "in-box" and is default in Rails 4+.
As the second piece of advice, You could generate Users Controler ( rails generate devise:controllers Users) and\or Devise modules such as sessions. passwords and other controlers with Customize Devise Tools.
Additionally, You could customize before_filter in certain Controller's code, for example:
class TicketsController < ApplicationController
before_filter :store_location
before_filter :authenticate_user!, :only => [:new, :delete]
In this sample, :authenticate_user should be only for :new and :delete methods. Other methods not require users authentication ...
I hope it help You!
Related
I login to my Rails application using a login page which is reached either directly through a login link that uses the route
match 'ladmin/login' => 'ladmin#login'
or if I try to edit content.
In either case I get taken to the login page, which allows me to login and then returns me to the page I was trying to use or the app index page, but now as a logged in user.
Most of the time this works totally ok, I get logged in and returns to my content edit page or to the links index page as expected (if I had just use the 'login' link iself).
However I have been able to track down a bug whereby
if I use either of my ajax links the request.url is remembered incorrectly going forward
(to either toggle group shading or to toggle Summary/Details, then the next time (even if several clicks) that I try to login (assuming I am logged out initially) results in the blank page - though I am actually now logged in. Interestingly I notice that when this happens, the url that I end up at in the browser address bar is always localhost:3000/toggle_row_shading which seems like a clue to the problem - it seems like the request.url is remembered incorrectly .
I require digest/sha1 for authentication in the User model with various methods for authentication and password.
The relevant code would seem to be in my LadminController:
def login
session[:user_id] = nil
if request.post?
user = User.authenticate(params[:username], params[:password])
if user
session[:user_id] = user.id
session[:username] = user.username
uri = session[:original_uri]
session[:original_uri] = nil
redirect_to(uri || {:action => "index", :controller => :links})
else
flash.now[:notice] = "Invalid username/password combination"
end
end
end
I can temporarily got around it with
def login
session[:user_id] = nil
if request.post?
user = User.authenticate(params[:username], params[:password])
if user
session[:user_id] = user.id
session[:username] = user.username
redirect_to({:action => "index", :controller => :links})
else
flash.now[:notice] = "Invalid username/password combination"
end
end
end
However this 'forgets' the page I have come from, e.g. an 'edit' link and just uses links#index to put me on after login which is not ideal. It does get around the blank page problem though, as the steps to reproduce it now longer make it happen.
How can I have the login return me to the intended edit page and not give me the blank page?
btw my code for the ajax links is:
%a{href: '#', :data => {toggle_group_row_shading: 'toggle'}}
click to toggle
Do I need an extra route perhaps as the app started at rails 2.3.8 ?
My routes include
match 'toggle_full_details' => 'links#toggle_full_details'
match 'toggle_row_shading' => 'links#toggle_row_shading'
get 'verify_link/:id', to: 'links#verify_link', as: :verify_link
get 'unverify_link/:id', to: 'links#unverify_link', as: :unverify_link
should I have them all as gets perhaps?
My application controller includes:
before_filter :authorize, :except => :login
and
def authorize
unless User.find_by_id(session[:user_id])
session[:original_uri] = request.url #request.request_uri
flash[:notice] = "Please Log In!"
redirect_to :controller => 'ladmin', :action => 'login'
end
end
I have had this happen to me before. The problem I found was that for AJAX requests, the session[:original_uri] is still getting stored and... is bad. Basically, it's storing a uri for an ajax request which doesn't display very well after logging in. The code I ended up with is something like:
if request.get? && !request.xhr?
session[:original_uri] = request.url
end
This way we're not storing into the session[:original_uri] things that shouldn't be redirected to after logging in. You may find other things in your app to add to this conditional. For example, I had some download links that would render a send_file, but those were no good for storing for next login either.
I'm trying to let users submit a reservation request without being logged in. After they submit unauthed users are prompted to sign in or sign up. After signing up I'd like the form to be submitted and the (new registered) users to be taken to the checkout page.
Store location keeps the last page to return users after logging in. I need to figure out how to continue users on their intended path by submitting their forms and placing them on the checkout page after sign up/ sign in.
def store_location
#stores the last url. Used for post-login redirects to what the user was trying to do last.
if (!request.fullpath.match("/users/") && !request.xhr?) # don't store ajax calls
session[:previous_url] = request.fullpath
end
end
Ok, I think this is pretty dirty but I haven't been able to find another way to do this.
after_sign_in_path_for is a method Devise calls that allows you to send people to different pages after sign in.
I took all the create logic out of the controller and put it into a service object.
def after_sign_in_path_for(resource)
if session[:booking_params].present?
#booking = Booking::CreateBooking.new(user:current_user, params: session[:booking_params]).return_booking
checkout_booking_path(#booking.public_id)
else
session[:previous_url] || root_path
end
end
In the controller, the create method is split into two parts. If there is no current user I save their params into the session and they are sent to login. If there is the CreateBooking service object is called normally.
def create
if current_user.nil?
session[:booking_params] = params
redirect_to new_user_registration_path
else
#booking = Booking::CreateBooking.new(user:current_user, params:params).return_booking
respond_to do |format|
format.html { redirect_to checkout_booking_path(#booking.public_id) }
end
end
end
In the after_sign_in_path_for method I check for the session params and create the booking there.
Let me know if there is a better way to do this!
I made Devise authentication to log out via GET, but couldn't make it log out using this Angular.js code:
$scope.logout = ->
$http.get('/users/sign_out').success ->
#If it does not redirect from 'editor' to 'login' then you haven't actually logged out
$location.path('editor')
Devise's logout behaviour seems to be random - sometimes it logs out, sometimes not.
And if I enter /users/sign_out into browser's address bar, it logs out always.
Ok, I switched the Devise authentication's log out to POST request to get rid of caching problems and used following Angular.js code:
$scope.logout = ->
$http.post('/users/sign_out').success ->
$location.path('editor')
The first time it logged out fine, as always, but then I couldn't make it to log out.
I decided to make my own method to see what happens:
match '/logout' => 'api#logout', :via => :post
class ApiController < ApplicationController
before_filter :authenticate_user!
def logout
sign_out
if current_user
puts 'Has not signed out!'
else
puts 'Has signed out!'
end
head :ok
end
end
and detected that after sign_out the current_user is always nil, but then the Angular application by some miracle manages to access other methods of ApiController, and current_user isn't nil there!
I do not understand that. Ok, let us suppose that there may follow some other HTTP request, right after (or at the same time as) logout request, passing the authentication cookie and Devise re-logins, but shouldn't the session ID passed in cookie be expired immediately after call of sign_out method?!
sorry I never responded earlier, hope this helps
My Sesisons Controller
$scope.signOutUser = function () {
$http.delete('/api/users/sign_out', {
auth_token: Session.currentUser // just a cookie storing my token from devise token authentication.
}).success( function(result) {
$cookieStore.remove('_pf_session');
$cookieStore.remove('_pf_name');
$cookieStore.remove('_pf_email');
location.reload(true); // I need to refresh the page to update cookies
}).error( function(result) {
console.log(result);
});
}
My Devise Sessions Controller I overrode
class SessionsController < Devise::SessionsController
before_filter :authenticate_user!, only: :destroy
def destroy
token = params[:auth_token]
#user = User.find_by_authentication_token(token)
#user.reset_authentication_token!
sign_out(#user)
render status: :ok, json: {message: "You have successfully logged out"}
end
end
As you can see, I'm not using Rails cookies and thus my answer may not pertain. If I did I would probably add a line like session[:user] = nil in my destroy action.
I am using Devise and Omniauth for facebook authentication. I am trying to redirect the User to the previous page after the User logs in either with Devise(regular) or Omniauth(fb).
"request.referer" works when the user uses the dropdown login on my navbar, but when the user tries to log in through the "http://localhost:3000/users/sign_in" url, "request.referer" gives me back an infinite loop error.
Here is the sequence. The problem is in (d).
(a) Unlogged-in User clicks Vote up for Mission.
(b) User is redirected to the "users/sign_in" url, because of the "before_filter :authenticate_user!" in the Missions Controller.
(c) User signs in either by typing in username/pword, or clicking Facebook icon.
(d) User should be redirected back to previous page, with the function VoteUp completed, but instead gives routing error
=> when I go back and refresh the page, the user is signed in, so I know it's just a problem with routing
MissionsController.rb
class MissionsController < ApplicationController
before_filter :authenticate_user!, :except => [:show, :index]
def vote_for_mission
#mission = Mission.find(params[:id])
if #mission.voted_by?(current_user)
redirect_to request.referer, alert: 'You already voted on this mission.'
else
#mission.increment!(:karma)
#mission.active = true
#mission.real_author.increment!(:userpoints) unless #mission.real_author.blank?
current_user.vote_for(#mission)
redirect_to request.referer, notice: 'Your vote was successfully recorded.'
end
end
ApplicationsController.rb
class ApplicationController < ActionController::Base
protect_from_forgery
def after_sign_in_path_for(resource)
sign_in_url = "http://localhost:3000/users/sign_in" || "http://onvard.com/users/sign_in" ||
"http://www.onvard.com/users/sign_in" #url_for(:action => 'new', :controller => 'sessions', :only_path => false, :protocol => 'http')
if (request.referer == sign_in_url)
env['omniauth.origin'] || request.env['omniauth.origin'] || stored_location_for(resource) || getting_started_path || root_path
else
request.referer
end
end
The User is redirected to the "getting_started_path," which I put so I knew the previous options weren't working. The 'else' case, which is the dropdown login form I created with jquery, redirects the user to the previous page perfectly fine, but when I try to put the request.referer in the 'request.referer==sign_in_url' case, it gives me an infinite loop.
How would I redirect the User to the previous page even when the user logs in through the 'users/sign_in' url??
You can't simply redirect back, because the sign_in_url is meant to only accept users that are not logged in, and after logging in your app is sending a logged in user back to that page.
If you need to redirect the user to a specific location after login you should store the original place the user tried to go before the redirect to the login page, then after the login, check if the user should go to a different place after logging in, and redirect him there.
I'm addressing this reply to future generations who use devise and have the same problem of OP: you should be able to solve it by using #stored_location_for, that returns the path that the user wanted to reach before being redirected to the sign in page.
How would I set in my rails app so that if a first time user comes to my site www.example.com they see a page that they can sign in but if an already logged in goes to www.example.com it now displays their own posts but still at the same www.example.com url.
Would I do something like render template based if they are logged in or is there some other way to do this?
You can set the users#home to be the root URL:
UsersController:
def home
if logged_in?
#blogs = current_user.blogs
render :action => 'logged_in'
else
render :action => 'non_logged_in'
end
end
Have 2 files in the app/views/users folder:
logged_in.html.erb & non_logged_in.html.erb
A great article was writen by Steve Richert. He is using advanced constraint when defining the route, see here
It depends on how you are making your log in logic
Usually you should have two actions, one for home/login form and another for user logged in home. You can make a before_filter on your application controller, so you can test if the user is logged in or not and then redirect him to home (logged out) if not.
If you are not using your own code or another solution I would like to recommend you this gem called devise, it implements a lot of login logic itself and is easy to change too.
EDIT: I think this solutions is better than the others that were presented and I didn't put the code (although it is quite the same code of the before_filter link), so here it is:
class ApplicationController < ActionController::Base
before_filter :require_login
private
def require_login
unless logged_in?
flash[:error] = "You must be logged in to access this section"
render :controller => 'home', :action => 'not_logged_in'
else
# whatever code you need to load from user
render :controller => 'home', :action => 'logged_in'
end
end
end
This solutions works perfectly because it tests if the user is logged in in every controller/action he tries to access.