I'd like to be able to set the root url in routes.rb based on the logged in/out state of Authlogic. Here is an example of what I need to do:
root :to => 'users#index', :constraints => lambda { |request| # ??? }
root :to => 'welcome#index'
get '/' => 'users#index', :as => 'user_root'
This would enable logged in users to go to users#index and logged out users to go to welcome#index without doing a redirect. I do know how to set a before_filter and redirect accordingly, but I'd rather not do that.
I know this is possible with Devise but I need to solve this for Authlogic. Similar question here without an answer.
You can create a custom route constraint pretty easily. Any object that responds to matches? can be handed to the :constraints route option. To check for Authlogic authentication, you would need to pull your Authlogic methods from your ApplicationController and put them somewhere else. I recommend using a concern for these anyways.
# app/concerns/authlogic_methods.rb
module AuthlogicHelpers
extend ActiveSupport::Concern
module InstanceMethods
private # All methods are private
def current_user_session
return #current_user_session if defined?(#current_user_session)
#current_user_session = UserSession.find
end
def current_user
return #current_user if defined?(#current_user)
#current_user = current_user_session && current_user_session.user
end
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include AuthlogicHelpers
end
# config/initializers/route_constraints.rb
module RouteConstraints
class LoggedIn
include AuthlogicHelpers
def matches?(request)
current_user
end
end
end
# config/routes.rb
constraints RouteConstraints::LoggedIn.new do
root :to => 'dashboard#show'
end
Note that we're instantiating a new RouteConstraints::LoggedIn object. This is because the Authlogic methods current_user and current_user_session are defined as instance methods and create instance variables — if they aren't separated for each request, you won't be sure a user is actually logged in or another one was logged in before.
You might run into problems if Authlogic tries to access params directly. In this case, you will probably need to override the UserSession.new method to accept the request object as an argument and call request.params to get the params.
Authlogic already have the machinery native (tested against Rails 6.0 and Authlogic 6.0):
class AuthlogicSignedInConstraint
def matches?(request)
Authlogic::Session::Base.controller = Authlogic::ControllerAdapters::AbstractAdapter.new(request)
user_session = UserSession.find
user_session && user_session.user
end
end
Authlogic in recent versions require a controller object to be instantiated otherwise it can't find the session data necessary for loading a session.
In fact, contained under authlogic/controller_adapters/ are adapters for various controller implementations: Rails Controllers, Sinatra and Rack.
The interface is fairly simple, so I wrote an adapter to work with the ActionDispatch::Request that one has access to within a constraint.
This has been tested on Rails 4.2, Rails 5 should be similar.
class AuthlogicRequestAdapter
attr_reader :request
def initialize(request)
#request = request
end
def authenticate_with_http_basic
end
def cookies
request.cookies
end
def params
request.params
end
def session
_, session_hash = Rails.application.config.session_store.new(Rails.application, Rails.application.config.session_options).load_session(request.env)
session_hash
end
end
Example of use in Rails
Route Constraint
class UserRouteConstraint
def matches?(request)
Authlogic::Session::Base.controller = AuthlogicRequestAdapter.new(request)
user = UserSession.find
if user&.record.present?
true
else
false
end
end
end
routes.rb
MyRailsApp::Application.routes.draw do
# ... other routes
constraints(UserRouteContraint.new) do
root :to => 'users#index'
end
root :to => 'welcome#index'
get '/' => 'users#index', :as => 'user_root'
end
Related
In my Rails app I want to forward my users to their personal sign in page which is stored in a cookie :user_subdomain. So when a user goes to www.app.com/sign_in, s/he should be automatically forwarded to www.app.com/sign_in/mycompany.
How can this be achieved?
I would like to keep my sign_in_path helper method because it's sprinkled all over my app.
If I simply redirect the new action using redirect_to sign_in_path(:name => cookies[:user_subdomain]) I end up in a loop.
Thanks for any pointers.
# routes.rb:
get 'sign_in', :to => 'sessions#new'
# sessions_controller.rb:
class SessionsController < ApplicationController
def new
params[:name] ||= cookies[:user_subdomain]
end
...
end
Then the solution is easy. You do not need to redirect, you just need optional parameter for your routes.
bound parameters
# routes.rb:
get 'sign_in(/:company_name)', :to => 'sessions#new'
# This will allow
# /sign_in
# and
# /sign_in/mycompany
# Both will lead to same action and you can keep your helper
# sessions_controller.rb:
class SessionsController < ApplicationController
def new
params[:company_name] ||= cookies[:user_subdomain]
# You logic here
end
...
end
I have a devise enabled route as:
#config/routes.rb
authenticated :user {
root :to => 'home#signed_in'
}
root :to => 'home#index
and controller:
#app/controllers/home_controller.rb
class HomeController < ApplicationController
def signed_in
Rails.application.routes.recognize_path '/'
end
end
which raises:
NoMethodError: undefined method `authenticate?' for nil:NilClass
...lib/devise/rails/routes.rb:286:in `block in authenticated'
I need such thing to render different templates in destroy action based on request.referer controller name. How can get 'authenticated' controller/action name for such URL?
It seems using Rails.application.routes.recognize_path does not work well with custom routes constraints like devise #authenticated method.
See https://github.com/plataformatec/devise/issues/3747
recognize_path won't work as is with constraints that require request related objects (like the warden instance that Devise expects to be in the request env Hash). If you are doing this inside a controller code you might be able to pass the warden object from request.env to the recognize_path call, or try to pass down the object down the the class that is recognizing the path.
In the other hand, as recognize_path isn't a documented public API, I strongly suggest you to not use it and save pieces of the params Hash instead of the raw URL on your application.
One solution to your problem could be to save the controller/action names in session and access them when you need.
authenticated :user do
root :to => 'home#signed_in'
end
unauthenticated :user do
root :to => 'home#index'
end
You can use this: https://gist.github.com/NullVoxPopuli/8c8af217b7404336c72a
class RouteRecognizer
include Singleton
ROUTE_LIST = Rails.application.routes.routes.collect{|r| r.path.spec.to_s}
REGEX_ROUT_LIST = ROUTE_LIST.map{|r|
Regexp.new(r.gsub(/\:(.*)id/, "(\d+)").gsub("(.:format)", ""))
}
def self.is_route?(path)
REGEX_ROUT_LIST.each do |regex|
return true if !!(path =~ regex)
end
false
end
end
and then call with
RouteRecognizer.is_route?('http://whatever')
I am using rails3 and gem devise and i have two roles admin and customer and i want after user
sign out admin should redirect to different path and customer should redirect to different path
when sign out..
You can get your desired functionality by using devise method after sign_out path.
but before you define these methods in application helper.
def is_admin?(user)
admin_role = Role.find(:first, :conditions => ["name = ?", "admin"])
return user.roles.include?(admin_role)
end
def is_customer?(user)
admin_role = Role.find(:first, :conditions => ["name = ?", "customer"])
return user.roles.include?(admin_role)
end
After that include application helper in application controller and define this method
def after_sign_out_path_for(resource_or_scope)
if is_admin?(current_user)
home_path = "/admin/users/sign_in"
elsif is_customer?(current_user)
home_path = "/customer"
end
respond_to?(home_path, true) ? send(root_path) : home_path
end
Hope it will work fine !!!
You can override the after_sign_out_path_for(resource) method that devise uses. Just mention the logic or just the desired redirection path after checking the roles of your user in the method. The devise session's destroy action calls the redirection path through this method.
def after_sign_out_path_for(resource)
#logic
end
Hope this is helpful..
The Devise README covers how to set up custom routes and controllers. In short you'll want to customize your routes for each model, e.g.:
devise_for :admins, :controllers => { :sessions => "admins/sessions" }
devise_for :customers, :controllers => { :sessions => "customers/sessions" }
Then create the corresponding controllers and override Devise::SessionsController#destroy, e.g.:
class Admins::SessionsController < Devise::SessionsController
def destroy
super
redirect_to ...
end
end
Story: A non logged in user should see a welcome static page and when he's logged in he should see a list of his blog posts.
I suppose the right way to do this is to route root to the action that lists all the user's posts which then checks for authentication. If the user isn't logged in then it renders the welcome page?
I need help with writing an action for the posts controller which displays posts for the logged in user.
routes.rb:
root :to => "posts#index"
post_controller.rb
class PostsController < ApplicationController
before_filter :authenticate_user!
def index
#posts = current_user.posts.all
end
end
If the user is not logged in, the before filter catches this and redirects somewhere (login? error message?). Otherwise the index method is called and the index view rendered.
This would work out of the box with devise, if you roll another authentication you need to adapt and/or write your own helpers, e.g. something like this:
application.html.erb
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
helper_method :user_signed_in?
private
def current_user
#current_user ||= User.find_by_id(session[:user_id]) if session[:user_id]
end
def user_signed_in?
return 1 if current_user
end
def authenticate_user!
if !current_user
flash[:error] = 'You need to sign in before accessing this page!'
redirect_to signin_services_path
end
end
end
I had this problem but didn't want a redirect (adds delay and changes url) so I'm deciding between constraints suggested in User-centric Routing in Rails 3 or scoped routes mentioned in Use lambdas for Rails 3 Route Constraints
Constraints
root :to => "landing#home", :constraints => SignedInConstraint.new(false)
Scoped routes
scope :constraints => lambda{|req| !req.session[:user_id].blank? } do
# all signed in routes
end
I have looked all over the place, and found a lot of info... but nothing works for me and I don't get it :(
I know that you are suppose to override the registration controller, like this:
class Users::RegistrationsController < Devise::RegistrationsController
def after_sign_up_path_for(resource)
authors_waiting_path
end
end
Then following the example showed by Tony Amoyal http://www.tonyamoyal.com/2010/07/28/rails-authentication-with-devise-and-cancan-customizing-devise-controllers/, I am supposed to change my routes to update the access the new controller:
devise_for :users, :controllers => { :registrations => "users/registrations" } do
#get '/author/sign_up', :to => 'devise/registrations#new'
#get '/client/sign_up', :to => 'devise/registrations#new'
get '/author/sign_up', :to => 'users/registrations#new'
get '/client/sign_up', :to => 'users/registrations#new'
end
Yes, I have something a bit strange here, because I am catching some specific path to send them to the registration page, this allows me to create effectively 2 registration scenario.
I commented what I had before I had overridden the registration controller.
Even with all this and my authors_waiting_path being a valid path, it just keeps on going to the sign-in page after registration :(
This is really frustrating.
Alex
edit: I also found this on the devise wiki: https://github.com/plataformatec/devise/wiki/How-To:-Redirect-after-registration-(sign-up)
But I have no idea where to define this create method ? should I override the session controller ???
edit 2:
I put a dummy override of the controller:
class Pouets::RegistrationsController < Devise::RegistrationsController
def after_sign_up_path_for(resource)
authors_waiting_path
end
def new
super
end
def create
puts "was here"
super
end
def edit
super
end
def update
super
end
def destroy
super
end
def cancel
super
end
end
And I never the "was here" in my logs.... I really have the feeling that it's totally ignoring the override... I must be doing something wrong :(
Ok... I am able to override it so you should be either :0
Create folder app/controllers/users
put there registrations_controller.rb with: (option with session - but it will try sign_in and later redirect - it may be not intended behavior for you ). Furthermore this is from devise wiki and I am not sure if it works
class Users::RegistrationsController < Devise::RegistrationsController
def create
session["#{resource_name}_return_to"] = complete_path
super
end
end
restart application (just for ensure you don't trust anything)
All in all you must override Create If you want redirect only Users... if you want define some more complex scenario you should monkeypatch sign_in_and_redirect
so your controller will looks like
class Users::RegistrationsController < Devise::RegistrationsController
# POST /resource/sign_up
def create
build_resource
if resource.save
set_flash_message :notice, :signed_up
#sign_in_and_redirect(resource_name, resource)\
#this commented line is responsible for sign in and redirection
#change to something you want..
else
clean_up_passwords(resource)
render_with_scope :new
end
end
end
second option try to monkeypatch helper ....
module Devise
module Controllers
# Those helpers are convenience methods added to ApplicationController.
module Helpers
def sign_in_and_redirect(resource_or_scope, resource=nil, skip=false)
#intended behaviour for signups
end
end
end
end
I have tried the above solution and while it works, reading devise code, I have found that all you actually need in order to sign-out just registered user and redirect is:
to add is_approved or similar to your user table and
to add active_for_authentication? method in your User model
Code:
class User < ActiveRecord::Base
# ... some code
def active_for_authentication?
super && is_approved
end
end
Was a bit hard to find when I needed it, but that is all. I am actually writing it here in case someone else needs it.