My Rails app uses data from legacy database. Imported users from this DB contain duplicate emails. Authentication doing with email+password (and it's a unique combination in DB).
Devise uses method find_for_database_authentication to find user. However params don't contain password (just login name).
What can I do?
try this :
find_user = User.where("email = ?", params["user"]["email"]).first
find_user.valid_password?(params["user"]["password"])
You can search this way
in the User model override the find method:
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
email = conditions.delete(:email)
pwd = conditions.delete(:password)
encrypted_pwd = User.new(password: pwd).encrypted_password
where(conditions).where(["lower(email) = :email AND encrypted_password = :pwd", { :email => email.strip.downcase, :pwd => encrypted_pwd }]).first
end
And probably config/initializers/devise.rb will require smth like:
config.authentication_keys = [ :email, :password ]
Related
I am using Devise and Rails 4.
I am working with login screen. In login screen i'm passing username/email and password but I have to check whether any one of the field username or email matched with any user in system.
Tried following condition to find user with username or email but it will be throwing an error for devise valid password method.
Condition:
user = User.where(["username = :value OR email = :value", { :value => username.downcase }])
after this i'm checking user password as like user.valid_password?(password)
Error: NoMethodError (undefined method `valid_password?' for #)
For following condition valid_password? will be working fine
user = User.find_by(email: email.downcase)
But i have to check both username and password in ::find_by method like as follows
user = User.find_by("email= email.downcase OR username= email.downcase")
Is there some way I can accomplish something like above?
This is most wise confuse error, when an single instance is mixed up wish a relation, you simply forgot #first select from the relation, since where returns relation, rather than find_by, which returns a record for single match. So just rewrite (for rails 5):
user = User.where(email: email.downcase).or(username: email.downcase).first
and for Rails 4 with arel:
users = User.arel_table
user = User.where(users[:email].eq(downcase).or(users[:username].eq(email.downcase))).first
or with partially plain SQL:
user = User.where(["username = :value OR email = :value", { :value => username.downcase }]).first
This question already has answers here:
Devise `find_first_by_auth_conditions` method explanation
(3 answers)
Closed 8 years ago.
I am having a tough time understanding this code from Devise, even though I've read the documentation and done some research.
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
signin = conditions.delete(:signin)
where(conditions).where(["lower(username) = :value OR lower(email) =
:value", {:value => signin.downcase }]).first
end
Please explain the components of this portion of the above method:
where(conditions).where(["lower(username) = :value OR lower(email) =
:value", {:value => signin.downcase }]).first
# arg is Hash, so assign to variable and downcase
x = warden_conditions[:signin].downcase
# create duplicate to preserve orig
c = warden_conditions.dup
# delete `:signin`
c.delete(:signin)
# if email, only search for email
if x =~ /^[a-zA-Z0-9._-]+#[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/
y = self.where(c).where(:email => x) # self is implied, but optional--so we use it here for clarity
# if not email, only search for name
else
y = self.where(c).where(:username => x)
end
# y is array, so should be only one AR obj or empty array considering they are unique
# `Array#first` will return `nil` or AR obj
return y.first
regex via:
validate email with regex jquery
The above code considers all previous records for columns email and username to be stored as lowercase as follows:
before_save :downcase_fields
def downcase_fields
self.email.downcase
self.username.downcase
end
In my app I am using the switch_user (https://github.com/flyerhzm/switch_user) gem to allow admins to login as another user. The gem has the ability to log back in as an admin, but I am having a hard time conceptualizing how to do it.
Here is my config:
SwitchUser.setup do |config|
# provider may be :devise, :authlogic, :clearance, :restful_authentication, :sorcery, or :session
config.provider = :devise
# available_users is a hash,
# key is the model name of user (:user, :admin, or any name you use),
# value is a block that return the users that can be switched.
config.available_users = { :user => lambda { User.all } }
# available_users_identifiers is a hash,
# keys in this hash should match a key in the available_users hash
# value is the name of the identifying column to find by,
# defaults to id
# this hash is to allow you to specify a different column to
# expose for instance a username on a User model instead of id
config.available_users_identifiers = { :user => :id }
# available_users_names is a hash,
# keys in this hash should match a key in the available_users hash
# value is the column name which will be displayed in select box
config.available_users_names = { :user => :email }
# controller_guard is a block,
# if it returns true, the request will continue,
# else the request will be refused and returns "Permission Denied"
# if you switch from "admin" to user, the current_user param is "admin"
config.controller_guard = lambda { |current_user, request, original_user|
current_user.school_admin? || original_user.school_admin?
}
# view_guard is a block,
# if it returns true, the switch user select box will be shown,
# else the select box will not be shown
# if you switch from admin to "user", the current_user param is "user"
config.view_guard = lambda { |current_user, request, original_user|
current_user.school_admin? || original_user.school_admin?
}
# redirect_path is a block, it returns which page will be redirected
# after switching a user.
config.redirect_path = lambda { |request, params| '/' }
# helper_with_guest is a boolean value, if it set to false
# the guest item in the helper won't be shown
config.helper_with_guest = true
# false = login from one scope to another and you are logged in only in both scopes
# true = you are logged only into one scope at a time
config.login_exclusive = true
# switch_back allows you to switch back to a previously selected user. See
# README for more details.
config.switch_back = true
end
Their README says you can have these links in your view
<%= link_to user.login, "/switch_user?scope_identifier=user_#{user.id}" %>
<%= link_to admin.login, "/switch_user?scope_identifier=admin_#{admin.id}" %>
But there is no way to load the "original user" to check to see if you need to display the admin login link.. anyone else have experience using this gem?
You can access original user from your controllers by doing
provider = SwitchUser::Provider.init(self)
provider.original_user
Cheers
I had similar issues with switch user and its switching back option, so at the end I am trying to implement something by myself.
I'm using this as a starting point, I hope it helps you as well.
Using a name as key, how do we validate the name when registering by ignoring case while still remembering the case when displaying?
In config/initializers/devise.rb, setting config.case_insensitive_keys = [ :name ] seems to lowercase the entire name before registering.
Example: some dude names himself TheFourthMusketeer.
The views will display TheFourthMusketeer, not thefourthmusketeer
No new user can register under, say, tHEfourthMUSKETEER
What you might try is to not set :name as case insensitive, which will properly save the case-sensitive name in the database:
config.case_insensitive_keys = []
Then, override the find_first_by_auth_conditions class method on User to find the user by their name. Note that this code will vary depending on the database (below is using Postgres):
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions).where("lower(name) = ?", login.downcase).first
else
where(conditions).first
end
end
Doing this, a User.find_for_authentication(login: 'thefourthmusketeer') will properly return the record with a name of "TheFourthMusketeer".
See https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address for an explanation of overriding this method.
The accepted answer is incomplete because it's still case-sensitive on registration. So for example, 'username' and 'USERNAME' could both register successfully, but only the first would be able to login.
Disable case-insensitive keys in config/initializers/devise.rb (this can also be model-specific so check there too):
config.case_insensitive_keys = []
Overwrite the find_first_by_auth_conditions method of models/user.rb:
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:username)
where(conditions).where(["lower(username) = :value", { :value => login.downcase }]).first
else
where(conditions).first
end
end
...and also set validates_uniqueness_of in models/user.rb:
validates_uniqueness_of :username, :case_sensitive => false
So there you have it: case-insensitive authentication, with case-insensitive registration, that preserves case, in the database.
I'm using the omniauth-linkedin gem for my rails application:
https://github.com/skorks/omniauth-linkedin
It should be taking into account new LinkedIn permissions request procedures and scopes, but I am not having success obtaining either email addresses or full profiles (anything other than the default profile overviews).
Here is my omniauth.rb file:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :linkedin, ENV['LINKEDIN_KEY'], ENV['LINKEDIN_SECRET'], :scope => 'r_fullprofile r_emailaddress'
end
The full profile and email address request are going through to LinkedIn initially, before the user enters her/his credentials. However, the scope requests are lost once the user signs in, and only the default profile overview information is accessible.
I think my problem has something to do with having to specifically request those fields within the scopes that I want, as is mentioned on the gem page (https://github.com/skorks/omniauth-linkedin). Still, I have tried adding specific field requests to my omniauth.rb file with no success (email field request below):
Rails.application.config.middleware.use OmniAuth::Builder do
provider :linkedin, ENV['LINKEDIN_KEY'], ENV['LINKEDIN_SECRET'], :scope => 'r_fullprofile+r_emailaddress',
:fields => ["id", "email-address", "first-name", "last-name", "headline", "industry", "picture-url", "public-profile-url", "location", "positions", "educations"]
end
Apparently, I am supposed to request the default fields of the profile overview in addition to the non-default fields like email-address, positions, and educations in order to access these latter non-default fields.
LinkedIn email-address should be straightforward, as it is not a structured object like positions or educations are, and I believe I am missing code for specific fields within positions and educations that I want (not sure how i would write that and would love some input on that as well). I am using my new API keys, so I'm not sure what the problem may be. Do I need some kind of special permission from LinkedIn? Help is appreciated! Thank you.
Also, here is the relevant code for my auth controller:
require 'linkedin'
class AuthController < ApplicationController
def auth
client = LinkedIn::Client.new(ENV['LINKEDIN_KEY'], ENV['LINKEDIN_SECRET'])
request_token = client.request_token(:oauth_callback =>
"http://#{request.host_with_port}/callback")
session[:rtoken] = request_token.token
session[:rsecret] = request_token.secret
redirect_to client.request_token.authorize_url
end
def callback
client = LinkedIn::Client.new(ENV['LINKEDIN_KEY'], ENV['LINKEDIN_SECRET'])
if current_user.atoken.nil? && current_user.asecret.nil?
pin = params[:oauth_verifier]
atoken, asecret = client.authorize_from_request(session[:rtoken], session[:rsecret], pin)
current_user.atoken = atoken
current_user.asecret = asecret
current_user.uid = client.profile(:fields => ["id"]).id
flash.now[:success] = 'Signed in with LinkedIn.'
elsif current_user.atoken && current_user.asecret
client.authorize_from_access(current_user.atoken, current_user.asecret)
flash.now[:success] = 'Signed in with LinkedIn.'
end
Not sure if this is the most elegant solution, but this worked for me
I just added the config with the scope passed in the request_token_path
LINKEDIN_CONFIGURATION = { :site => 'https://api.linkedin.com',
:authorize_path => '/uas/oauth/authenticate',
:request_token_path =>'/uas/oauth/requestToken?scope=r_basicprofile+r_emailaddress+r_network+r_contactinfo',
:access_token_path => '/uas/oauth/accessToken' }
client = LinkedIn::Client.new(LINKEDIN_API_KEY, LINKEDIN_SECRET_KEY, LINKEDIN_CONFIGURATION )
and the rest of the code is the same.
Be sure to change the client constructor in the callback method as well.