Reputation: 1196
I have a User model with has_secure_password
and validates :password, presence: true
.
then the following happens:
$ rails console
> u = User.create(name: 'user', password: 'password')
=> #<User id: ...
> u.password
=> 'password'
> u.save
=> true
> exit
then I run the console again and..
$ rails console
> u = User.find_by(name: 'user')
=> #<User id: ...
> u.password
=> nil
> u.valid?
=> false
I don't understand why the password becomes null after all went right the first time.
The same happens in the development app, I can log a user in with omniauth_google, a password is created with SecureRandom.hex(15)
. When I try to logout and log the same user back in by omniauth the user is found but user.valid?
gives a false
because the :passowrd mysteriously became nil
, compromising the log in.
here is the code for User, perhaps I se something wrong here?
class User < ApplicationRecord
has_many :notes
has_many :goings
has_many :cafes, through: :goings
has_secure_password
has_secure_password validations: false
validates_uniqueness_of :name, {message: "%{value} name already exist"}
validates :email, presence: { message: "%{attribute} must be given" }
validates :password, presence: {
message: 'You must enter a password'},
length: {minimum: 2,
message: 'Your password must contain at least 2 characters'
}
validates :name, presence: { message: "%{attribute} must be given" }
def self.from_omniauth(response)
User.find_or_create_by(uid: response[:uid], provider: response[:provider]) do |u|
u.name = response[:info][:name]
u.email = response[:info][:email]
u.password = SecureRandom.hex(15)
end
end
end
Upvotes: 1
Views: 658
Reputation: 4136
The trick is that with has_secure_password
, password
doesn't really exist as a persisted model attribute. It only exists to handle the original password for encryption; after that it is password_digest
all the way (hence why .password
returns nil
on existing objects.
Rails itself 'cheats' on the validations, only validating presence and length of password_digest
(GitHub):
# This ensures the model has a password by checking whether the password_digest
# is present, so that this works with both new and existing records. However,
# when there is an error, the message is added to the password attribute instead
# so that the error message will make sense to the end-user.
validate do |record|
record.errors.add(:password, :blank) unless record.password_digest.present?
end
validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
Your custom validations work fine on initial creation when there is a clear-text password to validate. But after that, there isn't one, so your presence check fails (and your other checks are irrelevant). To fix, you can add on: create
to your password validations so that they only run in the case of a new record.
Upvotes: 1