When I login with a username and password by BCrypt checks no problem, everything is fine.
But when I go through the process of recovering password and try to login with the new password the BCrypt never returns true.
The code I have is as follows:
before_save :encrypt_password
before_update :encrypt_password
def authenticate
player = Player.find_by(mail: self.mail)
unless player.nil?
current_password = BCrypt::Password.new(player.password)
if current_password == self.password
player
else
nil
end
end
end
private
def encrypt_password
unless self.password.nil?
self.password = BCrypt::Password.create(self.password)
end
I'm using rails 4
You don't need the before_update callback.
When creating a new record (user in this case), only before_save is triggered. So you get the right behavior.
But when updating a record, both before_update and before_save are triggered, which means your password column is encrypted twice. That's why you get unexpected behavior.
Check this page for more information about callbacks.
What's more, I think it's a bad idea to make password a real column in database. All you need is a column called encrypted_password in database and making password a virtual attribute.
So you can write encrypt_password method like this:
def encrypt_password
unless self.password.nil?
self.encrypt_password = BCrypt::Password.create(self.password)
end
Which gave you no chance to make a mistake like you just made.
Related
I have a method adjusts an auto-generated email for a user as part of a before_validation callback:
def update_device_email
binding.pry
self.email = username.gsub(/\s/, '_') + FAKE_EMAIL_SUFFIX
self.email_confirmation = email
end
For some reason though when I hit the binding.pry call, it shows me a downcased version of the email, which then returns to normal after I call gsub:
Does anyone know why this may be happening?
You should be checking what the username returns prior to the changes being made to self.email
I am working on a rails 3.2.13 project. I am using devise plugin (devise gem 3.2.2, 1.4.2) for authentication. Using this plugin, how can I validate the current_password field while changing the old password to a new one? Or else, please suggest how I can achieve this by encrypting the given string and matching it with the password already saved without using the devise plugin.
E.g.: One user has encrypted_password like below:
"$2a$10$VrawKYj6zp10XUxbixVzE.7d4QgYjQn9aiuzAuP7fp3PZOLMP5wbu"
while changing the password, if I enter a current_password, it should match the string above (encrypted_password == current_password). How can I validate this?
I believe you need to break your problem down into the following steps:
Determine if the old_password is actually the user's current password.
To do this, you can call:
User.find_by_id([SOME_ID]).valid_password?(old_password)
If this returns true, then you can move on to the next step to begin changing of the password. If it doesn't, then the old_password is incorrect, and you should not allow the changing of password.
The implementation of valid_password? can be found in the Devise gem's /lib/devise/models/database_authenticatable.rb file (at around Line 40). You could use this implementation to roll your own code for validating a password. But, Devise pretty much does it for you if you call valid_password?, so rolling your own seems unnecessary.
If old_password is valid, then verify that new_password matches confirm_new_password.
if (new_password == confirm_new_password)
.
.
.
end
If these match, then set the new password by doing the following:
u = User.find_by_id([SOME ID])
u.password = new_password
u.password_confirmation = confirm_new_password
u.save
You can verify that the password has been changed by:
u.valid_password?(new_password)
Update user with current_password validation:
#user.update_with_password(account_update_params)
# account_update_params - should have :current_password, :password, :password_confirmation
It is default behaviour in Devise::RegistrationsController. If you want update user without password, you should overwrite controller's action
class UsersController < Devise::RegistrationsController
def update_resource(resource, params)
# resource.update_with_password(params)
resource.update_attributes(params)
end
end
Do I understand you right what you want allow users login with encrypted and unencrypted (usual) password?
We have:
user.valid_password?('Password2').should
code on github
So we can overwrite it inside models/user.rb
def valid_password?(password)
encrypted_password == password || super(password)
end
My client wants all user data encrypted, so I've created a before_save and after_find call back that will encrypt certain properties using Gibberish:
# user.rb
before_save UserEncryptor.new
after_find UserEncryptor.new
# user_encryptor.rb
class UserEncryptor
def initialize
#cipher = Gibberish::AES.new("password")
end
def before_save(user)
user.first_name = encrypt(user.first_name)
user.last_name = encrypt(user.last_name)
user.email = encrypt(user.email) unless not user.confirmed? or user.unconfirmed_email
end
def after_find(user)
user.first_name = decrypt(user.first_name)
user.last_name = decrypt(user.last_name)
user.email = decrypt(user.email) unless not user.confirmed? or user.unconfirmed_email
end
private
def encrypt(value)
#cipher.enc(value)
end
def decrypt(value)
#cipher.dec(value)
end
end
Well, when the user first signs up using Devise, the model looks about like it should. But then once the user confirms, if I inspect the user, the first_name and last_name properties look to have been encrypted multiple times. So I put a breakpoint in the before_save method and click the confirmation link, and I see that it's getting executed three times in a row. The result is that the encrypted value gets encrypted again, and then again, so next time we retrieve the record, and every time thereafter, we get a twice encrypted value.
Now, why the heck is this happening? It's not occurring for other non-devise models that are executing the same logic. Does Devise have the current_user cached in a few different places, and it saves the user in each location? How else could a before_save callback be called 3 times before the next before_find is executed?
And, more importantly, how can I successfully encrypt my user data when I'm using Devise? I've also had problems with attr_encrypted and devise_aes_encryptable so if I get a lot of those suggestions then I guess I have some more questions to post :-)
I solved my problem with the help of a coworker.
For encrypting the first and last name, it was sufficient to add a flag to the model indicating whether or not it's been encrypted. That way, if multiple saves occur, the model knows it's already encrypted and can skip that step:
def before_update(user)
unless user.encrypted
user.first_name = encrypt(user.first_name)
user.last_name = encrypt(user.last_name)
user.encrypted = true
end
end
def after_find(user)
if user.encrypted
user.first_name = decrypt(user.first_name)
user.last_name = decrypt(user.last_name)
user.encrypted = false
end
end
For the email address, this was not sufficient. Devise was doing some really weird stuff with resetting cached values, so the email address was still getting double encrypted. So instead of hooking into the callbacks to encrypt the email address, we overrode some methods on the user model:
def email_before_type_cast
super.present? ? AES.decrypt(super, KEY) : ""
end
def email
return "" unless self[:email]
#email ||= AES.decrypt(self[:email], KEY)
end
def email=(provided_email)
self[:email] = encrypted_email(provided_email)
#email = provided_email
end
def self.find_for_authentication(conditions={})
conditions[:email] = encrypted_email(conditions[:email])
super
end
def self.find_or_initialize_with_errors(required_attributes, attributes, error=:invalid)
attributes[:email] = encrypted_email(attributes[:email]) if attributes[:email]
super
end
def self.encrypted_email decrypted_email
AES.encrypt(decrypted_email, KEY, {:iv => IV})
end
This got us most of the way there. However, my Devise models are reconfirmable, so when I changed a user's email address and tried to save, the reconfirmable module encountered something funky, the record got saved like a hundred times or so, and then I got a stack overflow and a rollback. We found that we needed to override one more method on the user model to do the trick:
def email_was
super.present? ? AES.decrypt(super, KEY) : ""
end
Now all of our personally identifiable information is encrypted! Yay!
I'm trying to convert an existing Admin model to Devise. We already have a password hash but it's obviously not Devise compatible. What I would like to do is accept the login form and check the provided password against the encrypted password. If it's not correct, use the old hash to check the password and if it matches, empty the old password_hash field and set Devise's password to the provided password and save the model.
What's the best way to move forward? I suspect that I need to override something, perhaps in a custom controller, but I'm not entirely sure how to proceed.
You can let Devise do the "hard work" of encrypting the password with the new crypt scheme, as shown in https://gist.github.com/1704632:
class User < ActiveRecord::Base
alias :devise_valid_password? :valid_password?
def valid_password?(password)
begin
super(password)
rescue BCrypt::Errors::InvalidHash
return false unless Digest::SHA1.hexdigest(password) == encrypted_password
logger.info "User #{email} is using the old password hashing method, updating attribute."
self.password = password
true
end
end
end
Using the bcrypt encryptor in Devise, this is what I ended up doing with my legacy data:
In models/user.rb
# Because we have some old legacy users in the database, we need to override Devises method for checking if a password is valid.
# We first ask Devise if the password is valid, and if it throws an InvalidHash exception, we know that we're dealing with a
# legacy user, so we check the password against the SHA1 algorithm that was used to hash the password in the old database.
alias :devise_valid_password? :valid_password?
def valid_password?(password)
begin
devise_valid_password?(password)
rescue BCrypt::Errors::InvalidHash
Digest::SHA1.hexdigest(password) == encrypted_password
end
end
As you can see, devise throws an InvalidHash exception when it encounters an invalid hash, which it would do when authenticating a legacy user.
I use this to fall back to the hashing-algorithm used to create the original legacy hash.
It doesn't change the password though, but that could simply be added to the method if needed.
First you need to copy password_salt and encrypted_password to your new object model
Using this because I have to export my database User to another application and old,
app are using devise 1.0.x and new app using 2.1.x
Class User < ActiveRecord::Base
alias :devise_valid_password? :valid_password?
def valid_password?(password)
begin
devise_valid_password?(password)
rescue BCrypt::Errors::InvalidHash
salt = password_salt
digest = nil
10.times { digest = ::Digest::SHA1.hexdigest('--' << [salt, digest, password, nil].flatten.join('--') << '--') }
digest
return false unless digest == encrypted_password
logger.info "User #{email} is using the old password hashing method, updating attribute."
self.password = password
self.password_salt = nil # With this you will knew what object already using the new authentication by devise
self.save
true
end
end
end
If you're moving from SHA512, the solution is a bit more involved than moeffju's SHA1 solution:
def valid_password?(password)
if has_legacy_password?
return false unless valid_legacy_password?(password)
convert_legacy_password!(password)
true
else
super(password)
end
end
protected
def has_legacy_password?
password_salt.present?
end
def convert_legacy_password!(password)
self.password = password
self.password_salt = nil
self.save
end
def valid_legacy_password?(password)
stretches = 10
salt = password_salt
pepper = nil
digest = pepper
stretches.times do
tokens = [salt, digest, password, pepper]
digest = Digest::SHA512.hexdigest('--' << tokens.flatten.join('--') << '--')
end
Devise.secure_compare(encrypted_password, digest)
end
Be sure to replace stretches and pepper with the values you used to encrypt the passwords.
follow Thomas Dippel instructions i have made a gist that update password:
https://gist.github.com/1578362
# Because we have some old legacy users in the database, we need to override Devises method for checking if a password is valid.
# We first ask Devise if the password is valid, and if it throws an InvalidHash exception, we know that we're dealing with a
# legacy user, so we check the password against the SHA1 algorithm that was used to hash the password in the old database.
#SOURCES OF SOLUTION:
# http://stackoverflow.com/questions/6113375/converting-existing-password-hash-to-devise
# https://github.com/binarylogic/authlogic/blob/master/lib/authlogic/crypto_providers/sha512.rb
# https://github.com/plataformatec/devise/blob/master/lib/devise/encryptors/authlogic_sha512.rb
alias :devise_valid_password? :valid_password?
def valid_password?(password)
debugger
begin
devise_valid_password?(password)
rescue BCrypt::Errors::InvalidHash
stretches = 20
digest = [password, self.password_salt].flatten.join('')
stretches.times {digest = Digest::SHA512.hexdigest(digest)}
if digest == self.encrypted_password
#Here update old Authlogic SHA512 Password with new Devise ByCrypt password
# SOURCE: https://github.com/plataformatec/devise/blob/master/lib/devise/models/database_authenticatable.rb
# Digests the password using bcrypt.
# Default strategy for Devise is BCrypt
# def password_digest(password)
# ::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
# end
self.encrypted_password = self.password_digest(password)
self.save
return true
else
# If not BCryt password and not old Authlogic SHA512 password Dosn't my user
return false
end
end
end
I have this in my user.rb:
attr_accessor :old_password
def validate
unless password.nil?
errors.add_to_base("Old password entered incorrect") unless self.valid_password? old_password
end
end
I have old_password as a a virtual attribute that has to be validated as matching with the current before updating to a new password. My problem is that upon correct entering ( password == password confirmation and self.valid_password? old_password ) an error will yield and pass me back to the form. The strange part is that the data will actually be updated in the database, and it will not on wrong input; although it will yield the very same error ("Old password entered incorrect"). What on earth am I doing wrong?
Alright, found the problem.
I used a custom update attributes, which used to look like:
def custom_update_attributes(userparams, updater)
unless updater.may_change_user_role_name? self
userparams.delete(:role_name)
userparams.delete(:active)
end
self.update_attributes(userparams)
save
end
The obvious problem here is save, even though I changed my def validate to def validate_on_update this validation has been running on the save action too, and since no old_password is supplied, error will be yielded.