I have overriden Devise's sign out path and made it request.referer. The problem with that is if the user is on a page which has the authenticate_user! before filter applied, I am sent to the sign in page and shown an error. This is not ideal, and I would like to redirect to the root_path ONLY when the user is coming from a path which has authentication requirements. What is the best way to do this?
I ended up inspecting the callbacks for the controller that the referrer url is associated with. If there are any authenticate_*! callbacks, they are each inspected to see if there are any actions specified in the #if instance variable. If there are none, the callback is applied to all of the actions and we know that the referring url is from a restricted action. If there are actions specified, we use regex to check if the referring action is restricted or not.
Any of the callbacks which meet the criteria are added to an array. After looping through the callbacks, we check if that array is empty. If it is, we send the user back to request.referer. If it is not empty, that means that sending them to request.referer will redirect them to the sign in page and show an error. In that case, we send the user to root_path.
If anything raises an error, the user is sent to root_path.
Here is the code:
def after_sign_out_path_for(resource_or_scope)
begin
controller_name = Rails.application.routes.recognize_path(request.referer)[:controller]
controller_class = "#{controller_name}_controller".camelize.constantize
authentication_callbacks = controller_class._process_action_callbacks.select { |callback| callback.filter.match /authenticate_\w+!/ }
action_name = Rails.application.routes.recognize_path(request.referer)[:action]
restricting_callbacks = []
authentication_callbacks.each do |callback|
callback_scope = callback.instance_variable_get(:#if)
if callback_scope.empty? || callback_scope.first.match(/action_name\s==\s(\'|\")#{Regexp.quote(action_name)}(\'|\")/)
restricting_callbacks << callback
end
end
restricting_callbacks.empty? ? request.referer : root_path
rescue Exception => e
request.referer
end
end
Related
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!
Im trying to redirect the user after the sign up by saving the referer in case when user came to sign up through clicking on any specific page. But its not working properly.
In my app controller
def save_referer
unless user_signed_in?
unless session['referer']
session['referer'] = request.referer || 'none'
end
end
end
In user model
def save_with(referer)
referer = referer unless referer == "null"
self.save
end
Here im saving it
if current_user.sign_in_count <= 1
if current_user.save_with(session[:referer])
redirect_to session[:referer]
else
redirect_to any_other
end
In User model, I guess you are not explicitly referring the attribute correctly.
def save_with(referer)
self.referer = referer unless referer == "null"
self.save
end
Generally, I take this strategy:
Anywhere I have a signup button I link to signup like so:
Signup!
Then in my signup's Action I do this:
def signup
session[:signup_refer] = params[:signup_refer] if params[:signup_refer] # put the refer in a session
redirect_to(signup_path) if session[:signup_refer] && params[:signup_refer] # this clears params if you want to keep your url nice an clean for the user
# do signup stuffs
signup_refer = session[:signup_refer] # get the refer info in a variable
session[:signup_refer] = nil # clear the session before redirecting
redirect_to(signup_refer ? signup_refer : "/default_post_signup")
end
I know it could be messy, but it totally works for me (well works in Merb, where it's "redirect() not redirect_to()) but you get the idea.
I am trying to force a user to login once they call this update action in my article controller (I am trying to work with gradual engagement) but once they login, I want to still call this action instead of halting.
def update
#article.attributes = params[:article]
#article.save
#store this article as a session variable
session[:pending_article] = #article.body
respond_with(#article, :location => article_url(#article))
end
Right now I am using a before_filter for the action that requires the user to login
def require_user
unless current_user
store_location
flash[:notice] = "You must be logged in to access this page"
redirect_to login_url
return false
end
end
However, I understand that before filters halt the original action once they redirect, so update never gets called. Basically, I want a user to be logged in to save an article but I want to save their work so I'm storing the article body in a session variable which I grab later. Is there a better way to require a user to login for an action but call it afterwards anyway?
In your require_user method you can do something like this:
session[:article] = params[:article]
Then in your login method (/sessions/create?) do this:
# this should take you back /articles/new,
# you may have to move your call to store_location
# or manually set session[:return_to]
redirect_back_or_default
Then in ArticlesController#new
def new
#article = Article.new(session[:article] || {})
end
Then the saved article params from the session are still there so the form is pre-filled out.
Be careful storing too much content in the session though. In Rails the default session store is a cookie, and cookies only hold about 4k of data. You may need to change your session store to pull this off.
I have a simple method in the ApplicationController that, when called, may set a 'flash[:notice]' then redirect to the root_url.
The problem is that even though that method is only called once, the root URL renders that flash[:notice] TWICE.
Here's a the method (which is a before_filter used in other controllers, and is defined in the ApplicationController) :
def authenticate
if params[:id].try(:size) == 40
company = Company.find_by_hash_identifier(params[:id])
if company
session[:editable_companies] ||= []
session[:editable_companies] << company.id
session[:editable_companies].compact!.uniq!
end
end
unless session[:editable_companies].try('&', [company.try(:id), params[:id]])
flash[:notice]= "You are not permitted to edit this company.<br />Please check the URL from the email we sent you, and try again."
flash.keep[:notice]
redirect_to root_url and return
end
end
In the root_url view, I get two flashes like so:
You are not permitted to edit this company.You are not permitted to edit this company.
Get rid of the flash.keep(:notice) line.
You don't (at least shouldn't) need to call flash.keep(:notice) to store the flash across a redirect. A value in the flash hash only gets auto-deleted on a render.
Turns out it was a problem in the view. :-(
I actually had flash[:notice] twice.
I have a RoR application that's using the RESTful Authentication plug-in. Everything works great. I recently enabled cookie based authentication and that works fine too. The problem is that I want to change the default landing page when the user is authenticated using a cookie. I want to have a cookie authenticated user redirected to the same page they are redirected to upon successful login from the login form. They are always directed to the original request URL. I'm racking my brain on this as I thought I understood how it works and every change I make seems to have no impact.
I suspect this is something simple but I'm obviously missing it. I'd appreciate any feedback, guidance or suggestions you might offer.
I solved the problem but it's a bit ugly in my opinion. Here's what I did.
In the cookie authentication method I set a session variable indicating the cookie login method was used.
def login_from_cookie
user = cookies[:auth_token] && User.find_by_remember_token(cookies[:auth_token])
if user && user.remember_token?
session[:cookie_login] = true **# this is my addition**
self.current_user = user
handle_remember_cookie! false # freshen cookie token (keeping date)
self.current_user
end
end
Then in the :before_filter set_current_user I just check for that variable and redirect if it is set making sure to set the variable to nil.
def set_current_user
Authorization.current_user = current_user
if session[:cookie_login]
redirect_to :controller => :users, :action => :search
session[:cookie_login] = false
end
end
It's not pretty but it does work. I'm definitely open to any suggestions about how to clean this up.
You could add this line to the session controller after a successful login:
redirect_to :controller => 'dashboard', :action => 'index'
I'm using Bort so maybe this isn't part of Restful_Authentication itself but there is a successful_login method in the sessions controller that uses this restful_auth method:
redirect_back_or_default( root_path )
which is in defined in authenticated_system.rb
def redirect_back_or_default(default)
redirect_to(session[:return_to] || default)
session[:return_to] = nil
end
Can't you just have your routes setup so that
map.root :controller => :users, :action => :search
And then have a before_filter that checks to make sure that some "logged in" parameter is set? This param would just need to be set whenever the user logs in, either via cookie or via normal means. Then, whether the cookie authentication happens or normal auth happens, it will go to the default page. Maybe I'm misunderstanding the problem.
Restful Authentication stores the original URL that was trying to be accessed when the request is made. All of you have to do is prevent it from storing that value OR clear that value when a cookie authentication is performed and then the user will get redirected back to your default page.
I would probably do it like this in authenticated_system.rb
def login_from_cookie
user = cookies[:auth_token] && User.find_by_remember_token(cookies[:auth_token])
if user && user.remember_token?
self.current_user = user
session[:return_to] = nil # This clears out the return value so the user will get redirected to the default path
handle_remember_cookie! false # freshen cookie token (keeping date)
self.current_user
end
end
The is session[:return_to] = nil
Then just make sure you have set your default path in your sessions controller and you should be all set. The code in your sessions controller should be something like this:
redirect_back_or_default(the_path_you_want_to_send_them_to)