Rails - Devise, Email Case Insensitivity - ruby-on-rails

I'm using Devise with login credentials: email/password - no usernames
I just noticed that the login process is case sensitive for emails. so if you register with bob#apPle.com, and then try to log in with Bob#apple.com you get an error. Very confusing.
How can I make devise log people in with their email/password, and the email being case insensitive?

You can easily fix the issue like below.
# config/initializers/devise.rb
Devise.setup do |config|
config.case_insensitive_keys = [:email, :username]
end

One option is to override the find method used by devise. Something like:
# User.rb
before_save do
self.email.downcase! if self.email
end
def self.find_for_authentication(conditions)
conditions[:email].downcase!
super(conditions)
end

I added this to my User model to store it case-sensitive but make it case-insensitive during sign in:
def self.find_for_database_authentication(conditions = {})
self.where("LOWER(email) = LOWER(?)", conditions[:email]).first || super
end
It works on Heroku.
By the way, this is just a temporary fix as the issue has been resolved and this will be the default behavior on Devise 1.2. See this pull request for details.

I also had some solution which making work with email is case-insensitive for all Devise controllers (functionality):
class ApplicationController < ActionController::Base
...
...
prepend_before_filter :email_to_downcase, :only => [:create, :update]
...
...
private
...
...
def email_to_downcase
if params[:user] && params[:user][:email]
params[:user][:email] = params[:user][:email].downcase
end
end
...
...
end
I know it is not the best solution: it involves another controllers of another models and executes code which is not necessary for them. But it was just makeshift and it works (at least for me ;) ).
Kevin and Andres, thanks for your answers. It is really good solutions and useful. I wanted to vote them up, but I haven't enough reputation yet. So, I just tell 'thanks' to you. ;)
Lets wait for Devise 1.2

Devise addresses the issue here:
https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address

Related

Allow devise login with alias_attribute

I am trying to build an API that deals with an existing mobile app. The mobile App uses username but it is really just an email and instead of reworking devise to have a confusing system of username which really is email. I wanted to use alias_attribute. It seems to work for everything but the login.
The JSON comes up as
{ "username":"test12345#gmail.com", "password":"password" }
and I permit the keys
devise_parameter_sanitizer.permit(:sign_in, keys: [:username, :password])
but it fails at
self.resource = warden.authenticate!(auth_options)
It seems like warden is ignoring the alias_attribute. If I change the username to email it works fine. I have tried overwriting the database auth function on the model but it never gets to that line.
from what I can tell, it's way easier to override the default sessions_controller behaviour
inside you sessions controller:
def create
if params[:user][:username]
params[:user][:email] = params[:user][:username]
end
super
end
or if yo don't want to overrride the create method then add
before_action :override_params, only: :create
def override_params
if params[:user][:username]
params[:user][:email] = params[:user][:username]
end
end
it's not nice, but should do the trick. And if you don't have to rewrite the insides of devise then I'd give it a go.
Otherwise you would have to do
add new authentication key to devise initializer
(inside config/initializers/devise.rb add config.authentication_keys = [:email, :username])
then you would have to override find_for_database_authnetication in User model to search the user by the username param value (check https://github.com/plataformatec/devise/blob/master/lib/devise/models/database_authenticatable.rb#L231). Because alias_attribute doesn't really help you in this scenario
probably some other steps as well
All in all, the dirty method seems simpler and more straightforward
EDIT:
also, checkout this page https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign_in-using-their-username-or-email-address
maybe it will be of some help

How to extend Devise SessionsController#create when using allow_unconfirmed_access?

In my Rails project I am using Devise and I've turned on allow_unconfirmed_access in the devise initializer like this:
config.allow_unconfirmed_access_for = 1.day
This works great. If a user has not confirmed their account via the confirmation email, they can still login for 24 hours. After this, if they attempt to login, Devise handles it and gives a flash message and returns a 401, disallowing the login.
I want to be able to hook into this and add a step to auto-resend the confirmation email but I can't figure out for the life of me where to do it.
You can extend the Devise::SessionsController to add this behavior:
class Users::SessionsController < Devise::SessionsController
before_action :resend_confirmation_email_if_unsent, on: :create
def resend_confirmation_email
#user = resource # resource is a devise controller helper method
unless #user.active_for_authentication?
#user.resend_confirmation_instructions # Resets token
# Or, if you don't want to reset the tokn
# #user.send_confirmation_instructions
end
# ....
end
I know this is an old question, but I thought I might as well answer it as I was dealing with the same situation.
Anthony E's answer is almost correct, but it missed the fact that resource is not defined before the create action starts, thus resource is nil at that moment. My solution was this:
class Users::SessionsController < Devise::SessionsController
before_action :resend_confirmation_email_if_needed, on: :create
def resend_confirmation_email_if_needed
#user = resource_class.find_by_email(resource_params[:email])
unless #user.nil? || #user.active_for_authentication?
#user.resend_confirmation_instructions
end
end
end
I'm not sure if it's a good idea to retrieve the user this way. It would be much easier if super do |resource| worked for this, but it only runs upon successful login, which is not the case.
Hope this helps!

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

Ruby on Rails Devise code after login

I have an RoR app using Devise for logins. There is some code that is executed when a new User record is created, by being put in the user.rb file as an after_create call/macro/whatever. I need to make this code run after each login, instead of running on new user creation.
With some Googling, it seems that one option is to place Warden callbacks in the devise.rb code. My questions are:
Is this right, and/or is there a better way to do this?
If this is the right approach ...
Should the Warden::Manager... method defs go in devise.rb inside of Devise.setup, or after it?
Is after_authentication the callback I should use? I'm just checking to see if a directory based on the user's name exists, and if not, creating it.
Just subclass Devise's sessions controller and put your custom behaviour there:
# config/routes.rb
devise_for :users, :controllers => { :sessions => "custom_sessions" }
And then create your controller like this:
# app/controllers/custom_sessions_controller.rb
class CustomSessionsController < Devise::SessionsController
## for rails 5+, use before_action, after_action
before_filter :before_login, :only => :create
after_filter :after_login, :only => :create
def before_login
end
def after_login
end
end
I found that using the Devise Warden hook cleanly allowed after login event trapping by looking for the ":set_user" :event.
In user.rb:
class User < ApplicationRecord
Warden::Manager.after_set_user do |user, auth, opts|
if (opts[:scope] == :user && opts[:event] == :set_user)
# < Do your after login work here >
end
end
end
I think this is an duplicate question. Yes you can execute code after every successful log in. you could write the code in your ApplicationController. Also have a look at 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)
Your Code Here
end
Reference Can I execute custom actions after successful sign in with Devise?
You could also inherit from devise session's class and use after_filter for logins.
UPDATED 2022
Warden now has built-in callbacks for executing your own code on after_authentication:
Warden::Manager.after_authentication do |user, _auth, _opts|
TelegramService.send("#{account.name} just logged in")
end
Source: https://github.com/wardencommunity/warden/wiki/Callbacks#after_authentication

Redirect Devise before_filter :authenticate_user to sign in path

I'm using devise and have a quick question. How can I redirect the :authenticate_user! before_filter to the user sign up page instead of sign in? I've been going through https://github.com/plataformatec/devise/blob/master/lib/devise/controllers/helpers.rb but haven't had much luck figuring out a solution.
I had a similar issue where I needed to redirect to the signup if the user was not logged in.
I fixed it by adding a method to the application_controller.rb and using it as a before filter in the other controllers.
Keep in mind that is is more of a temporary solution because it skips a bunch of deviseĀ“s abstractions.
before_filter :auth_user
def auth_user
redirect_to new_user_registration_url unless user_signed_in?
end
You're going to have to create a custom FailureApp that inherits from Devise's FailureApp as seen here: https://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-when-the-user-can-not-be-authenticated
I added a wiki page showing the correct way to do this with a failure app (as Steven initially hinted at):
Redirect to new registration (sign up) path if unauthenticated
The key is to override the route method, like so:
# app/lib/my_failure_app.rb
class MyFailureApp < Devise::FailureApp
def route(scope)
:new_user_registration_url
end
end
and then have Devise use your failure app:
# config/initializers/devise.rb
config.warden do |manager|
manager.failure_app = MyFailureApp
end
This approach is preferable to overriding authenticate_user! in your controller because it won't clobber a lot of "behind the scenes" stuff Devise does, such as storing the attempted URL so the user can be redirected after successful sign in.
With multiple user types
If you have Admin and User Devise resources, you'll probably want to keep the default "new session" functionality for admins. You can do so quite easily by checking what type of scope is being processed:
# app/lib/my_failure_app.rb
class MyFailureApp < Devise::FailureApp
def route(scope)
scope.to_sym == :user ? :new_user_registration_url : super
end
end

Resources