devise with multiple layout - ruby-on-rails

I really like to authenticate my devise user through 2 different interfaces with a view to have 2 different layout.
For example I would be able to use /users/sign_in and /admin/sign_in based on the same User model.
I had set 2 routes :
devise_for :users
and
devise_for :users, :module => "admin/users", :path => ''
But I'm not sur it's the right way to do that because I need overwrite current_user on my application controller, like this:
def current_user
super || current_admin_user
end
Moreover I have 2 methods : authenticate_user! and authenticate_admin_user!
I'm really confused with this specification, can anybody help ?

I'm not sure if I got your problem, if not, please comment on it :)
There is no need to overwrite current_user. You can create a filter that filters admins like this:
def require_admin_user
unless current_user.admin
flash[:error] = "You need admin privileges to enter this area"
redirect_to root_path
end
end
current_user will return the current_user logged in, whether it is an admin or it isn't an admin. If you want for an user to be able to login in as an admin only if they as an ordinary user, I would suggest another approach: creating another model for admins and filtering for require_user! for the admin sign_in.

Your best bet is to use STA (Single Table Inheritance)… Then you can use 2 devise_for declarations, one for each model.

I have a different controller admin in that i have added a login action.
class AdminController < ApplicationController
def login
#user = User.new
end
end
In view of login.html.erb
<%= form_for(#user, :as => :user, :url => session_path(:user)) do |f| %>
<% end %>
U can now call admin/login path
and successfully got sign up, but if you want to redirect to some other page after sign up instead of root url then
In application controller write inside this method of devise
def after_sign_in_path_for(resource)
end

Related

Conditional Route on sign_out using Devise with Rails

I am working on an app in which there is a model User with roles member and Admin.
As per requirement, I have to made two separate login pages for Admin and Member.
with http://localhost:3000/admin/admin_login
it goes to admin login page and with
http://localhost:3000/users/sign_in
it goes to member login page.
Just after login I route them according to their roles to Admin panel or simple website for members.
But at time of logout both goes to
http://localhost:3000
but I want admin to go to http://localhost:3000/admin/admin_login,
while http://localhost:3000 is fine for member's logout.
Is there a way to see User's Role at time of Sign_out and route them accordingly.
You can define a after sign out path according to resource in application controller..
class ApplicationController < ActionController::Base
private
# Overwriting the sign_out redirect path method
def after_sign_out_path_for(resource_or_scope)
root_path
end
end
In after_sign_out_for_for method you can check user role and redirect to
For more details visit devise wiki After Sign out path in devise
I think you can try like this
Just save whatever path you want to redirect user after sign_out based on the role after login in session and use that session in after_sign_out_path_for method
after_filter :store_location
def store_location
return unless session[:login_url].blank?
session[:login_url] = current_user.admin? ? admin_path : other_user_path
end
def after_sign_out_path_for(resource)
session[:login_url] || request.referer || root_path
end
there are one other way just overwrite devise sessoin controller and redirect user from there based on role
# routes.rb
devise_for :users, :controllers => { :sessions => "sessions" } # etc
# sessions_controller.rb
class SessionsController < Devise::SessionsController
def destroy
#login_path = set path in a variable based on user role before sing_out
#code to sign out
#
redirect_to login_path
end
end
Are the pages that they are clicking on the logout links different for admins and users? If so, you could check the request.referrer property in your after_sign_out_path method and route accordingly.
BTW if you are routing them to the right pages after logging in then why do you need separate login pages? you may want to just have one login page for simplicity.
You can also leave it config.sign_out_via =: get => config/initializers/devise.rb is not the best way but solved for me.

Login in to main app from active admin

I have app, and connected active admin. I'm trying to let admin user login as any user via active admin without password using devises's sign_in #user method. Is it possible to achieve this out of box?
I can make redirect with username in params/session, but that isnt secure, as if i wouldnt like to pass anything outside active admin.
Any ideas?
I'm not sure exactly what you mean by "not secure"; the devise internals can make sure that you are an admin user, etc, and all you need to pass around is the username of the new user. That said, the code below is entirely within ActiveAdmin and will achieve what you want.
N.B. I can't think of an easy way to sign the Admin user back in (I use the same Devise for all users and use role-based auth).
member_action :sign_in_as, :method => :put do
user = User.find(params[:id])
sign_in user, bypass: true
redirect_to root_path
end
action_item :only => :show do
link_to('Sign in As', sign_in_as_admin_user_path(user), method: 'put')
end
you can do this by using additional method to authenticate admin first like this,
def authenticate_any!
if authenticate_admin_user!
true
else
authenticate_user!
end
end
or overriding devise
module Devise
module Controllers
module Helpers
def authenticate_user!
if authenticate_admin_user!
return true
end
super
end
end
end
end

How to secure user show page alongside user admin functions when using devise

I'm using devise and have let admins manage users with a Manage::UsersController.
This is locked down using cancan:
# models/ability.rb
def initialize(user)
if user admin?
can :manage, User
end
end
Normal users can have nothing to do with User other than through devise, so everything looks secure.
Now I want to give users a 'show' page for their own account information (rather than customising the devise 'edit' page). The advice (1,2,3) seems to be to add a users_controller with a show method.
I tried to give non-admins the ability to read only their own information with:
if user admin?
can :manage, User
else
can :read, User, :id => user.id # edited based on #Edwards's answer
end
However this doesn't seem to restrict access to Manage::UsersController#index, so it means that everybody can see a list of all users.
What's the simplest way to achieve this? I can see two options, (but I'm not sure either is right):
1) Prevent user access to Manage::UsersController#index
def index
#users = User.all
authorize! :manage, User # feels hackish because this is 'read' action
end
2) Over-write devise controller
Per this answer over-writing a devise controller could be a good approach, but the question is which controller (the registrations controller?) and how. One of my concerns with going this route is that the information I want to display relates to the User object but not devise specifically (i.e. user plan information etc.). I'm also worried about getting bogged down when trying to test this.
What do you recommend?
In your ability.rb you have
can :read, User, :user_id => user.id
The User model won't have a user_id - you want the logged in user to be able to see their own account - that is it has the same id as the current_user. Also, :read is an alias for [:index, :show], you only want :show. So,
can :show, User, :id => user.id
should do the job.
I would keep your registration and authentication as Devise controllers; then, create your own User controller that is not a devise controller.
In your own controller, let's call it a ProfilesController, you could only show the specific actions for the one profile (the current_user)
routes
resource :profile
profiles controller
class ProfilesController
respond_to :html
def show
#user = current_user
end
def edit
#user = current_user
end
def update
#user = current_user
#user.update_attributes(params[:user])
respond_with #user
end
end
Since it's always only editing YOU, it restricts the ability to edit or see others.

Redirect user after log in only if it's on root_path

I have a root_path on my Rails application that is not user-protected i.e. it's a simple portal homepage, with a login form.
After the users log in, I'd like it to go to dashboard_path.
I've done this:
def signed_in_root_path(scope_or_resource)
dashboard_path
end
This apparently should be used when an user signs in, and I don't want it to go to the root_path, while still keeping the user going back to a previous page if it tries to hit a restricted area and it's either timed out or not logged in.
i.e.:
restricted_page -> login -> restricted_page_but_logged_in
I don't want to change this behavior, and that's why I haven't used after_sign_in_path, but want to redirect it if it's on root_path, or any route that doesn't require user authentication.
My problem is that this is not working. After signing in, I'm getting redirected back to root_path, which I believe is because of after_sign_in_path getting triggered before.
Is there any way to do this? Thanks!
Edit: This works the second time I log in, i.e. I go to root_path, log in, gets the flash message stating me that I'm logged in, and enter username and password again on the form on root_path. I successfully get redirected to dashboard_path. Still, not quite the behavior I want.
Just a thought
You can define two root url one for signed in url which will point to dashboard and second for non signed in users which will point to login page
define different root url based on some constraints
in routes.rb
root url for signed in users
constraints(AuthenticatedUser) do
root :to => "dashboard"
end
root url for non signed in users
root :to=>"users/signin"
then create class AuthenticatedUser in lib/authenticated_user.rb
class AuthenticatedUser
def self.matches?(request)
user_signed_in?
end
end
now if user is signed in root_url will point to dashboard else it will point to signin page
Your can also create two roots using(did not tested it yet)
root :to => "dashboard", :constraints => {user_signed_in?}
root :to => "users/signin"
more on constrains
http://edgeguides.rubyonrails.org/routing.html#request-based-constraints
Note
The priority of url is based upon order of creation,
first created -> highest priority resources
It sounds like you're over complicating the issue. If you get into overriding routing variables it just leads to headaches down the line. I would recommend using a before filter to require a login and use the except param or skip that before filter for your landing page if you're using a separate controller. As an example:
class ApplicationController < ActionController::Base
before_filter :require_login, :except => :root
def root
# Homepage
end
protected
def require_login
redirect_to login_path and return unless logged_in?
end
end
(Make sure you have logged_in? defined)
If you are using a separate controller it will look something like this:
class HomepageController < ApplicationController
skip_before_filter :require_login
before_filter :route
protected
def route
redirect_to dashboard_path and return if logged_in?
end
end
Regarding proper routing after a login, that would come down to what you're doing when you're creating your session. Regardless, this setup should catch anyone that's logged in trying to hit the homepage, and route them to your dashboard and anyone trying to hit restricted content (Anything besides root) and route them to the login_path
You can override the after_sign_in_path_for method without losing the desired behavior.
def after_sign_in_path_for(resource_or_scope)
stored_location_for(resource_or_scope) || dashboard_path
end
I would also put a condition in the root_path action that redirects if current_user exists.
RootController.rb
def index
if current_user
redirect_to dashboard_path
else
super #or whatever
end
end
You could also use a before_filter, if you wanted to add the redirect to many unprotected actions.
I'm using Omniauth and this method has worked well for me. If you're working with a different strategy, I'm sure you could modify it.
After they log in, just redirect them to root_path and it will take them to dashboard_path or whatever other default you set in the routes file below.
Set up your helper and callback methods in the app controller:
# application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def authenticate_user!
unless current_user
redirect_to root_url
end
end
end
Put the before_filter in the restricted controller to catch unauthorized people:
# dashboard_controller.rb
class DashboardController < ActionController::Base
before_filter :authenticate_user!
def index
end
# rest of controller here
end
Add these two items to the route. The first one will not be executed if the condition is not met. If it is, then the top one takes precedence and all root_url calls will go to the dashboard
# routes.rb
YourAppName::Application.routes.draw do
root :to => 'dashboard#index', :conditions => lambda{ |req| !req.session["user_id"].blank? }
root :to => 'static_page#index'
# the rest of your routes
end
I think you're solution is more complex than necessary. Why don't you just do something simple like this on the action that the login form is posted to:
def login
// logic to check whether login credentials authorize user to sign in
// say 'user_signed_in?' is boolean to determine whether user has successfully signed in
redirect_to(user_signed_in? ? dashboard_path : root_path)
end
The login form is on every pages (top/sidebar) or you have a login page?
If is on every pages, you can use request.referrer to know where the user came from.
You can control this with a before_filter on your application controller.
I'm not sure whether or not you're using an after_filter or before_filter somewhere for your redirects but you might be able to use a skip_filter in your login controller. Then put in your custom redirect as a filter within that controller.
Skip before_filter in Rails

Devise: Sign Up Page as Welcome/Landing Page then to User Profile

Using devise, how do i make my Sign Up as my landing/welcome page and then after sign up they go inside the site to the Profile/signed in area?
I am trying to figure out how to make it like where Facebook their is two sides of the website; the Outside (sign up, about,contact,etc) and The Inside (Profile,sign out,etc) for Users only after sign up or sign in.
Thank you.
P.S. I am new at Ruby on Rails and creating applications but i did do the authentication system with the Rails 3 Tutorial, i understand most things to start using Devise, i jst dont know where to start with this situation.
I was going to use 2 application layouts, 1 before sign up which is layouts/welcome.html.erb with PagesController (about,terms,etc) and the other for signed in users which will be layouts/application.html.erb with ApplicationController (profile,news,add,etc), is this the best steps?
in your routes.rb:
root :to => 'welcome#index'
Where welcome is the controller and index is the action.
In your application controller:
def after_sign_in_path_for(user)
"/url_you_want_to_redirect_to/"
end
This my new and updated way using Rails 3.1.0 and Devise 1.5.0:
routes.rb
root :to => "pages#redirect_to_sign_up"
devise_for :users do
get "welcome" => "devise/registrations#new", :as => :new_user_registration
get "account_settings" => "devise/registrations#edit"
get "sign_in" => "devise/sessions#new"
get "sign_out" => "devise/sessions#destroy"
get "new_password", :to => "devise/passwords#new"
end
match 'home', :to => "user_pages#home"
namespace :user do
root :to => "user_pages#home"
end
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
protected
def after_sign_in_path_for(resource)
stored_location_for(:user) || root_path
end
private
def after_sign_out_path_for(resource)
stored_location_for(:user) || root_path
end
end
pages_controller.rb
class PagesController < ApplicationController
def redirect_to_sign_up
if signed_in?.blank?
redirect_to new_user_registration_path
else
redirect_to home_path
end
end
end
user_pages_controller.rb
class UserPagesController < ApplicationController
before_filter :authenticate_user!
def home
end
def profile
end
end
I find it easiest to root to the desired authenticated landing page and just use a before_filter to force the user to sign in/sign up first via a before_filter.
In this case, let's say your "signed in area" is a controller/action called profile/index.
In your routes.rb, set the root to profile/index.
root :to => 'profile#index'
Then in your profile_controller, set the following before_filter.
before_filter :authenticate_user!
This will automatically push a logged in user (or one that logged in earlier and set a Remember Me cookie) straight to the profile page. Any unauthenticated users will automatically end up on Sign In. You'll want a link (or separate tab) on that page to Sign Up as well.
On the root page check to see if the user is signed in, and redirect based on that.
redirect_to sign_up_path if current_user.nil?
Alternatively, you could render different templates instead of a redirect, but I think it's cleaner to have a 1:1 mapping between urls and pages.
Another approach is to modify devise. Edit devise_gem_path/lib/devise/failure_app.rb and replace the two occurrences in the redirect_url method of:
:"new_#{scope}_session_path"
with:
:"new_#{scope}_registration_path"
A less destructive solution would make redirect_url's response more configurable.

Resources