Reputation: 4976
I'm trying to migrate a ton of users from an old database. To do this, I'm using activerecord-import and trying to save all my user data directly to DB (bypassing the User model).
My issue: I need to take the old user's plain-text password, encrypt it, and store directly to the DB. I know how to generate a password using Devise, but am wondering if there's a way to get a hashed password that I can store directly to the database.
Hoping to do:
new_hashed_password = Devise.awesome_encrypting_method(old_user.password)
Then store "new_hashed_password" directly into the DB without going through the model. I dug around in Devise and found this:
def password_digest(password)
::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
end
@@stretches defaults to 10 (lib/devise.rb:71) and isn't overridden by my initializer
@@pepper defaults to nil (lib/devise.rb:148) and isn't overridden by my initializer
I thought I could manually re-create password_digest() but I think I'm missing something fundamental about Bcrypt because even with setting password and stretches, the resulting hash is different every time.
Any ideas? Thanks for your help!
Upvotes: 39
Views: 43208
Reputation: 6426
Use the devise encryptor. It check if there is already a pepper
set on the class.
ref: https://www.rubydoc.info/github/plataformatec/devise/Devise/Encryptor
user = User.last
encrypted_password = Devise::Encryptor.digest(user.class, 'somePass'])
user.update(encrypted_password: encrypted_password)
validate with
user.valid_password?('somePass')
=> true
Upvotes: 0
Reputation:
None of the other answers above worked for me, so here is what I did:
user.valid_password?(plain_password)
Upvotes: 7
Reputation: 9077
You should do it like this:
password = 'the secret password'
new_hashed_password = User.new(:password => password).encrypted_password
This is much better than using BCrypt directly as it abstracts away how passwords are generated from your code, making it easier to understand, and also immune to changes in how devise constructs encrypted passwords. Your code should not, and has no reason to know anything about that.
Upvotes: 75
Reputation: 4524
An alternative method is: User.new.send(:password_digest, 'xxx')
Upvotes: 4
Reputation: 4976
Good news and bad news.
The following works to create your user's password manually.
pepper = nil
cost = 10
encrypted_password = ::BCrypt::Password.create("#{password}#{pepper}", :cost => cost).to_s
You can find your pepper and cost in your devise initializer. This method was confirmed using Devise's "valid_password?" method.
The entire reason I was trying to avoid "User.new(password: password).encrypted_password" was because of speed. It's terribly slow. With all my other pieces of my import task, I've intentionally avoided this.
But as it turns out, the major cost here is not instantiating a User object -- but BCrypt itself. There is very little noticeable speed boost when using BCrypt directly because it's intentionally designed to be slow.
My final answer: suck it up, run the rake script, go find a beverage.
Upvotes: 19
Reputation: 2188
Assuming you have a mysql database with a "users" table and a "password" column And an ActiveRecord model class called "user" that is hooked up to devise
Create an ActiveRecord model class in your app app/models/old_user.rb
OldUser < ActiveRecord::Base
set_table :users
establish_connection :database => "old_database", :user => "old user", :adapter => "mysql"
end
then create a rake task: app/lib/tasks/migrate_users.rake
task :migrate_users => :environment do
OldUser.find_each do |old_user|
u = User.new(:email => old_user.email, :password => old_user.password, :password_confirmation => old_user.password);
#if your using confirmation
u.skip_confirmation!
u.save!
end
end
Modify as necessary (make sure you're saving any app-specific user attributes)
Then$ rake migrate_users
Upvotes: 1