user3755529
user3755529

Reputation: 1196

Rails has_secure_password saves successfully but then gives nil password - validation fails

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

Answers (1)

rmlockerd
rmlockerd

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

Related Questions