Devise Session Controller. Custom routing on failure - ruby-on-rails

Right now I'm extending the Devise sessions controller. Everything is working fine except that when the password is typed wrong or it can't find the user. it tries to redirect_to the sessions#new. I do not want that for my case I want it to redirect to a custom route because this is coming from a different view than the sessions new view. I know about after_sign_in_path_for but I'm not sure that is what I want because my particular case is the warden.authenticate! method. In the auth_options there is a recall hash. That is where I want to customize my route. Here is my sessions controller.
Session Controller
class SessionsController < Devise::SessionsController
def create
super do |user|
if params[:user][:invitation_id].present?
invitation = Invitation.find(params[:user][:invitation_id])
if !user.accounts.exists?(id: invitation.account.id)
user.accounts << invitation.account
flash[:tip_off] = "You now have access to your project \"#{invitation.account.name}\""
else
flash[:tip_off] = "You are already a member of #{invitation.account.name}"
end
cookies[:account_id] = invitation.account.id
invitation.destroy
user.save
end
end
end
end
As I stated before this works fine I just need a custom routes upon failure of password or email. Right now it goes to sessions#new, that does not work for my case. Any help would be great! Thanks

You can override recall value on auth_option method to redirect to another route on failure.
def auth_option
{ scope: resource_name, recall: "#{controller_path}#another_new_path" }
end

Related

Rails devise disable password recovery for certain user types

In my Rails project I have different types of users one of which has the user_status :admin, which has full rights to edit content unlike the rest of the users. For obvious reasons I want to add additional security for these types of users, in particular, completely disable password recovery.
What is the correct way of overriding standard Devise password recovery (:recoverable Devise module) methods so that when a user tries to get a reset password link for a user which is an admin user (user_status == "admin") the system gives back the "standard email not found" message?
This is somewhat like the unanswered question: Restrict Devise password recovery to only certain users
Thank you in advance.
The method I chose and that worked for me was overriding the send_reset_password_instructions method of the User model by adding the following to models/user.rb:
def send_reset_password_instructions
return false if self.user_status == 'admin'
super
end
This makes Devise not do anything in case the email belongs to an admin account.
For any future viewers, here's another way to do it. Vitaly's example did work for me, but I was still getting the "Your password email has been sent." notice (I wanted a separate alert to flash), so I went another route.
Extending the Devise::PasswordsController was the easiest solution for me:
class Devise::Extends::PasswordsController < Devise::PasswordsController
def create
if some_condition?
redirect_to :root
flash[:alert] = 'You cannot reset your password, buddy.'
else
super
end
end
Then, in routes.rb:
devise_for :users, controllers: { passwords: 'devise/extends/passwords' }
That will direct your app to the extended controller, then hit the devise controller ("super") if your condition is not met.
Not tested, but I think you can overwrite the reset_password! in the User model as follows:
def reset_password!(new_password, new_password_confirmation)
return false if user_status == 'admin'
super
end
This prevents the password from being reset if the user is an admin.
I don't know if this is the best method to override, there are more devise recoverable methods that are candidate to be overwritten in your User model, ie send_reset_password_instructions. Check the manual for all the interesting methods.
Snippet above from Keller Martin works pretty well!
Some minor issues I faced are the following:
If you got uninitialized constant Devise::Extends (NameError) (probably it's just due to old ruby version?) then you can just use nested modules definition.
If you need to allow some action to run for non authenticated user then you can skip the filter.
Below is updated snippet.
module Devise
module Extends
class PasswordsController < Devise::PasswordsController
skip_before_filter :authenticate_user!, :only => [ :edit ]
def edit
redirect_to "https://www.google.com/"
end
end
end
end

How to pass a query param to Devise's login page

I have a Devise customisation issue that I'm struggling with.
We have a query parameter included in some email links that need to be passed to the login URL if the user is not already authenticated when they click on the link. For example, if the email link is:
http://my.host.com/my/path/1234?x=y&foo=bar
I'd like unauthenticated users to be redirected to
http://my.host.com/login/?foo=bar
i.e., one specific query param needs to be passed – I don't care about any others on the login form, but if all the query params have to be passed I could live with that.
I've scoured SO, and the docs & source for both Devise and Warden, but can't find a straightforward way of doing this. Apologies if I'm missing something obvious.
In the end, we got the effect we wanted by going down a very slightly different route.
Rather than including the param in the query URL of the redirect to the login, we were able to modify the code concerned to accept a session parameter instead.
Then, in application_controller.rb:
class ApplicationController < ActionController::Base
before_filter :persist_login_param
...
def persist_login_param
session[:foo] = params[:foo]
end
end
And in the sessions controller, we act upon the value of the session parameter, discarding it once done.
Slightly messy, but it works and gets code pushed out to production now, which is the important thing!
Can use unless statement in before_action, then unauthentication users can see any devise controller actions
class ApplicationController < ActionController::Base
...
before_filter :sending_params
before_action :check_auth, unless: :devise_controller?
...
protected
def check_auth
unless user_signed_in?
redirect_to new_user_session_path(sending_params)
end
end
def sending_params
params.permit(:first_param, :second_param)
end
...
And can access to params without generation devise controllers by generate devise views and use in views
<%= params[:first_param] %>
If need send params to omniauth controller, you can change link in devise views
<%= link_to 'Sign in by provider', omniauth_authorize_path(:user, :provider, first_param: params['first_param']) %>
Where :user - name devise model, :provider - name omniauth provider (:facebook i.e.)
In omniauth controller you can use
def first_param
request.env['omniauth.params']['first_param'] ||= ''
end
For an unauthenticated user, in order to pass specific params foo=bar out of http://my.host.com/my/path/1234?x=y&foo=bar to your login, perform as below:
Assuming the above email link goes to action myaction then add the following code to myaction
def myaction
if member_signed_in?
redirect_to root_path
else
## If user is unauthenticated direct them to login page with specific params
redirect_to new_member_session_path(foo: params[:foo])
end
end
Above action will pass params foo to the devise login page and the actual link in browser address bar would look as below:
http://my.host.com/login?foo=bar ## Without / after login
You can access the param by overridding Devise::SessionsController. For Example :
class Users::SessionsController < Devise::SessionsController
...
def new
#foo = params[:foo]
super
end
...
end

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

Set a session variable in devise on sign in

I'd like to set a session variable once a user signs in based on a certain field in the User table. I don't want to have to create a custom Devise controller if I don't have to. Is there a way? Or will I have to go the custom controller route?
There is a callback after_sign_in_path_for, you can add it in your ApplicationController
protected
def after_sign_in_path_for(resource)
session[:domain_prefix] = current_user.domain_prefix
user_path(resource)
end
Dont forget return the path in the last line of method, otherwise the callback will redirect the request to content of session[:domain_prefix]
How about this one:
The first resource I'd look at is http://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-on-successful-sign-in. Also, check out How to redirect to a specific page on successful sign up using rails devise gem? for some ideas.
You can do something like:
def after_sign_in_path_for(resource_or_scope)
session[:account_type] = current_user.account_type
end
You can implement this method in your ApplicationController or in a custom RegistrationsController.

Custom Devise controller

I would like to customize my registrations controller for Devise in Rails. I understand that you must create a controller like this:
class AccountsController < Devise::SessionsController
def create
super
end
end
Well, that's all very good. But then let's say I want to fully control what happens in my #create action. How do I do that? How do I manually create a model and pass it all the params? Would Account.create(params[:account]) handle it smoothly? Is there some internal stuff going on I should know about or is my only option to call #super inside the action?
As long as you fulfil your required fields you can call Account.create in your example, I'm pretty sure the default Devise required fields are login, password and password_confirmation
We do this in a CRUD screen for creating devise users,
#admin = Admin.new(params[:admin])
if #admin.save
redirect_to admin_admins_path, :notice => 'New Administrator has been added'
else
render :action => "new"
end
and you don't want to extend the Devise session controller, a normal controller extending ApplicationController is fine or you can extend Devise::RegistrationsController and overwrite the methods you want to tweak in a registrations_controller.rb file
You can also have a look at the source on Github, if you want to be sure you're overriding things properly, and be sure you're not missing any processing...
https://github.com/plataformatec/devise/tree/master/app/controllers

Resources