I need to seed an user with encrypted password and I'm not using Devise. So I tried this :
user = UserManager::User.new({ :name => 'a', :surname => 'a', :email => 'a', :active => true, :password_hash => 'password', :password_salt => 'password'})
user.save
But this is not right to put password_hash and password_salt like this and I found that I have to put password and password_confirmation instead
user = UserManager::User.new({ :name => 'a', :surname => 'a', :email => 'a', :active => true, :password => 'password', :password_confirmation => 'password'})
user.save
But these 2 fields are unknow because they are not in the database, so how can I do to encrypt the password with seed ?
EDIT
User model
attr_accessor :password
has_secure_password
before_save :encrypt_password
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
You don't need the attribute accessor for password. You get that for free when you use has_secure_password.
To seed users, I would recommend using Hartl's approach from his tutorial.
User model
Add a method for generating the password digest manually:
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
Seed file
User.create!(name: 'foo',
email: 'foo#bar.com',
password_digest: #{User.digest('foobar')} )
In your model:
class User < ApplicationRecord
has_secure_password
end
In the seeds.rb file:
User.create(username: "username",
...
password_digest: BCrypt::Password.create('Your_Password'))
You can create a hash of password something like following:
require 'digest/sha1'
encrypted_password= Digest::SHA1.hexdigest(password)
You can use this code in your seeds.rb file to encrypt the password. But it seems that you have already written a method encrypt_password. Now, you can call this method in before_save callback. So each time, a user is about to be saved in database, encrypt_password will be called.
Related
How do you skip user confirmation in development in devise.
I have set up the production environment to send emails with SendGrid, but now I have done that it won't let me log in.
Thanks for your time!
create User in console:
user = User.create(
:first_name => 'admin',
:last_name => 'admin',
:email => 'foo...#email.com',
:password => 'password1',
:password_confirmation => 'password1'
).skip_confirmation!
# Devise :doc:
# If you don't want confirmation to be sent on create, neither a code
# to be generated, call skip_confirmation!
or in model:
after_create :skip_conf!
def skip_conf!
self.confirm! if Rails.env.development?
end
Another way:
User.new(email: 'xx#xx.xx', password: '12345678').confirm!
BTW, you can skip :password_confirmation at all, because #confirm! skips validation if record does not persisted yet.
I am using devise gem and while creating user I want to skip confimation! and skip confimation email for example:
User.create(:first_name => "vidur", :last_name => "punj").confirm!.skip_confirmation!
But it skips only confirmation and doesn't skip sending confirmation email. Can any one give me an idea to skip both.
You need to call skip_confirmation! before you save the record.
Try
user = User.new(:first_name => "blah")
user.skip_confirmation!
user.save
You are calling User.create before skip_confirmation!, you need to call User.new and user.save later.
Try
user = User.new(:first_name => "vidur", :last_name => "punj")
user.skip_confirmation!
user.save!
set the confirmed_at field
User.create!(confirmed_at: Time.now, email: 'test#example.com', ...)
useful in seeds.rb
User.create_with(name: "Mr T", company: "Comp X", password: 'rubyrubyruby', password_confirmation: 'rubyrubyruby', confirmed_at: Time.now).find_or_create_by!( email: 'test#example.com')
Got the solution:
#user=User.new(:first_name => "vidur", :last_name => "punj")
#user.skip_confirmation!
#user.confirm!
#user.save
If you are confused where to write skip_confirmation! method in controller as you have not generated devise controllers yet then:
Write this in your User model
before_create :my_method
def my_method
self.skip_confirmation!
end
Now simply use:
user = User.new(:first_name => "Gagan")
user.save
after_create: :skip_confirmation_notification
- check here
If you want to skip_confirmation_notification on certain condition only, use a proc
If you don't need confirmations at all, you can remove the :confirmable symbol in your model.
I just finished Hartl's Rails Tutorial book and I'm using his account authentication logic in my first rails app. However, when I make a new user account and set it as an admin account by toggling the admin attribute in the console (e.g. User.find(5).toggle!(:admin)), the encrypted password that is stored in the DB gets modified. Why?
Here's the user model logic...
class User < ActiveRecord::Base
#virtual attributes
attr_accessor :password
#attrs modifiable by the outside world
attr_accessible :name, :email, :password, :password_confirmation
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
#validations
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => true
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
#class method that authenticates a user, used to create a session cookie
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
#used to authenticate a signed user from a signed cookie
def self.authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
return nil if user.nil?
return user if user.salt == cookie_salt
end
#callback that occurs before a record is successfully saved (meaning it has a valud password)
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
private
#self keyword is required when assigning to a instance attribute
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
and here's what the behavior looks like...
ruby-1.9.2-p180 :018 > User.last
=> #<User id: 12, name: "Test User A", email: "testusera#website.com", created_at: "2011-03-28 17:47:42", updated_at: "2011-03-28 17:47:42", salt: "23ca99e9848336a078e05ce9a8d904f9dfdff30dc7a38586f22...", admin: false, monthly_score: nil, encrypted_password: "50d17e6d6b581cfcfe84b61feb318705978cdf6c435626d10aa...">
ruby-1.9.2-p180 :019 > User.last.toggle!(:admin)
=> true
ruby-1.9.2-p180 :020 > User.last
=> #<User id: 12, name: "Test User A", email: "testusera#website.com", created_at: "2011-03-28 17:47:42", updated_at: "2011-03-28 17:49:06", salt: "23ca99e9848336a078e05ce9a8d904f9dfdff30dc7a38586f22...", admin: true, monthly_score: nil, encrypted_password: "5d6e17f7aa73925a0099da45807f5994fa8c368a5a12d187a7d...">
Thanks so much for your help!
try to change your before_save method:
def encrypt_password
if password.present?
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
end
UPD. or you can make it little shorter
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password) if password.present?
end
Not sure what is going on specifically. Could be that the password is getting encrypted again using a different salt (which is based on the time). Try adding debugger to the first line of encrypt_password and then run the same code from the console to see if the password is getting encrypted when you run toggle!
There's something odd going on in your code. A salt should be independent of the password, but your (Hartl's?) make_salt method says:
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
This might have been the source of your nil problem, since you were accessing password inside make_salt; in any case this is bad crypto since it amounts to using Time.now as a "random" salt, which is much easier to crack (build rainbow tables for).
You should instead be using a good random number generator, e.g. Ruby's built-in SecureRandom:
def make_salt
SecureRandom.hex(64)
end
Why such a long salt? According to https://crackstation.net/hashing-security.htm, "To make it impossible for an attacker to create a lookup table for every possible salt, the salt must be long. A good rule of thumb is to use a salt that is the same size as the output of the hash function. For example, the output of SHA256 is 256 bits (32 bytes), so the salt should be at least 32 random bytes." I don't want to use SecureRandom.random_bytes(32) to avoid potential database string encoding problems with non-ascii characters, and 64 random hex characters comprise 32 random bytes, which I think counts as the same entropy.
I am using faker to generate sample data. I have this as follows:
require 'faker'
namespace :db do
desc "Fill database with sample data"
task :populate => :environment do
Rake::Task['db:reset'].invoke
User.create!(:name => "rails",
:email => "example#railstutorial.org",
:password => "foobar",
:password_confirmation => "foobar")
99.times do |n|
#name = Faker::Name.name
name = "rails#{n+1}"
email = "example-#{n+1}#railstutorial.org"
password = "password"
user = User.create!(:name => name,
:email => email,
:password => password,
:password_confirmation => password)
end
end
end
The problem is that I have a couple of after_save callbacks that are not being called when the User is created. Why is that? Thanks
The methods:
after_save :create_profile
def create_profile
self.build_profile()
end
In all my reading, it seems that save! bypasses any custom before_save, on_save or after_save filters you have defined. The source code for create! reveals that it invokes save!. Unless you absolutely NEED the bang version, why are you using it? Try removing all your bang methods and just invoking the non-bang versions:
[1..100].each do |n|
name = "rails#{n+1}"
email = "example-#{n+1}#railstutorial.org"
password = "password"
user = User.new(:name => name, :email => email, :password => password, :password_confirmation => password)
if !user.save
puts "There was an error while making #{user.inspect}"
end
end
I have User model which used for omni-auth authentication. I have set it up so that different providers (like facebook, google accounts ) can be used for signing in. If the email is already in the database while signing up using these providers ,user is signed into that account.
class User
include Mongoid::Document
embeds_many :providers
field :email,type: String, default: ""
end
class Provider
include Mongoid::Document
field :name, type: String
field :uid, type: String
embedded_in :user
end
And I am using from_omniauth method to find user based on the auth info.
def from_omniauth(auth)
user=User.find_by(email: auth.info.email)
return user if user
where(:"providers.name" => auth.provider, :"provider.uid" => auth.uid ).first_or_create do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
end
end
But I am getting an error when it cant find a user and tries to create one.
Mongoid::Errors::UnknownAttribute:
message:
Attempted to set a value for 'providers.name' which is not allowed on the model User.
But why does it consider this as a dynamic field generation as I have already defined the associations. I tried find_or_initialize method also but same error.
Can someone help me figure it out. Thanks in advance.
There's some magic going on here.
When you are invoking #first_or_create on the above criteria:
where(:"providers.name" => auth.provider, :"provider.uid" => auth.uid ).first_or_create do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
end
This is actually getting de-sugared into something more like this:
found_user_with_provider = where(:"providers.name" => auth.provider, :"provider.uid" => auth.uid ).first
if found_user_with_provider.nil?
new_user = User.new
new_user.set(:"providers.name", auth.provider)
new_user.set(:"providers.uid", auth.uid)
new_user.email = auth.info.email
new_user.password = Devise.friendly_token[0,20]
else
found_user_with_provider.email = auth.info.email
found_user_with_provider.password = Devise.friendly_token[0,20]
end
Notice the attempt to set the value of "provider.name" and "provider.uid" as attributes on the user instance - which isn't actually what you want, and is what Mongoid is complaining about.
You actually probably want something more like this:
def from_omniauth(auth)
user = User.find_by(email: auth.info.email)
return user if user
found_user_with_provider = where(:"providers.name" => auth.provider, :"provider.uid" => auth.uid ).first
if found_user_with_provider
found_user_with_provider.update_attributes!(
:email => auth.info.email,
:password => Devise.friendly_token[0,20]
)
return found_user_with_provider
end
User.create!({
:providers => [
Provider.new({
:name => auth.provider,
:uid => auth.uid
})
],
:email => auth.info.email,
:password => Devise.friendly_token[0,20]
})
end
Of course there's still one more problem, and that's that you are setting a #password attribute on your user instance. Make sure to add:
field :password,type: String, default: ""
To your user model - otherwise you'll get the same complaint about dynamic fields as before - just this time about a different field.