validates password on user model not working in edit view - ruby-on-rails

I'm a bit confused. Before I added email validation functionality to my sample app, my validation looked like this in the /app/models/user.rb:
validates :password, length: { minimum: 6 }
Once I implemented my signup email confirmation functionality, I needed to add this to the validation:
validates :password, length: { minimum: 6 }, if: :validates_password?
...
def validates_password?
password.present? || password_confirmation.present?
end
because I would get a validation error when the user clicked on the link inside the email to verify their email address. Upon making this change to the password validation, my email worked! However, my test for the invalid information in my edit action of the users controller began to fail. Then I noticed that the edit form no longer has an error even though they do not type the password in. So I added some debug statements that you can see in the image below:
"user password present" is evaluated as #user.password.present?
"user password_confirmation present" is evaluated as #user.password_confirmation.present?
"user is valid" is evaluated as #user.valid?
"validate_password?" is evaluated as #user.password.present? || #user.password_confirmation.present?
How is this happening? How is my user valid by passing valid? but the validate_password? method is returning false???
Can someone help me please?

From the comments:
validates :password, length: { minimum: 6 }, if: :validates_password?
is saying "I'm going to make sure the password length is a minimum of 6 characters if validates_password? is true.
If the user doesn't fill in password/password_confirmation, then validates_password? is false so:
validates :password, length: { minimum: 6 }, if: :validates_password?
never fires. That's what is happening in this case.

Related

Why password validation does not work after adding password recovery?

Faced a problem not clear to me.
I used password validation during registration.
def pass_val
if password_digest.count("a-z") <= 0 || password_digest.count("A-Z") <= 0
errors.add(:password, "must contain 1 small letter, 1 capital letter and minimum 8 symbols")
end
end
validates :password_digest, presence: true,
length: { minimum: 8 }
I made it possible to recover the password for the user, and validation just stopped working.
I changed all password_digest in validation to just password and validation worked again.
BUT, after that, password recovery disappeared, which began to curse for validation.
NoMethodError in PasswordResetsController#create
undefined method `count' for nil:NilClass
If I change all password again to password_digest, validation will disappear, but the recovery password will work again.
UPDATE
Using password - validation in the console works (it also works on the site. Passwords recovery does not work).
Using password_digest - validation in the console also works (it doesn’t work on the site, the recovery password works).
UPDATE 2
Moreover, an error occurs during password recovery when a letter is sent to the mail (before sending), and not exactly during the password change.
UPDATE 3
create method in PasswordResetsController:
def create
author = Author.find_by_email(params[:email])
author.send_password_reset if author
flash[:success] = "Email sent with password reset instructions."
redirect_to root_path
end
UPDATE 4
send password_reset:
def send_password_reset
confirmation_token
self.password_reset_sent_at = Time.zone.now
save!
AuthorMailer.password_reset(self).deliver!
end
But the letter is not sent, crashes up to this point.
I want to emphasize that I either use password_digest in validation and validation does not work, and password recovery works. Or I use password in validation and validation works, but password recovery does not.
Also, it’s worth noting that the create method in PasswordResetsController is not used to change the password, but to send an email to the email. I can’t understand why validation swears at him.
Seems like you already found a solution yourself. However I'm not sure you understand why this is happening.
Let's start of with the important fact that password_digest doesn't contain the password but rather a hashed version of the password. (The result of a hash function is also called a digest.) You'll want to add the validations to the password attribute, since that contains the actual password.
When you assign a new password to an author both the password and password digest will be assigned a new value. You could imagine the password setter to look something like this:
def password=(password)
#password = password
#password_digest = do_some_hash_magic(password)
end
Because password is set when you assign it to the author you're able to validate the contents of it.
Another important thing to know is that the actual password is never saved into the database, only the password digest is (for security reasons). For this reason when you (re)load the instance from the database password is set to nil, because the application no longer knows what it is.
Instead of skipping the validation with save(validate: false), I suggest using the :allow_nil option or making the validation conditional to only trigger the validation when the password is not nil.
validates :password, length: { in: 8..255 }, allow_nil: true
validates :password, format: { with: /\p{Lower}/, message: :no_lower }, allow_nil: true
validates :password, format: { with: /\p{Upper}/, message: :no_upper }, allow_nil: true
validates :password, format: { with: /\p{Digit}/, message: :no_digit }, allow_nil: true
validates :password, format: { with: /[\p{S}\p{P}]/, message: :no_special }, allow_nil: true
Alternatively you can be more explicit and use the :on option with a custom context to only trigger trigger the validations when you want them to:
validates :password, length: { in: 8..255 }, on: :password
validates :password, format: { with: /\p{Lower}/, message: :no_lower }, on: :password
validates :password, format: { with: /\p{Upper}/, message: :no_upper }, on: :password
validates :password, format: { with: /\p{Digit}/, message: :no_digit }, on: :password
validates :password, format: { with: /[\p{S}\p{P}]/, message: :no_special }, on: :password
The context doesn't have to be named after an attribute, using :foo instead of :password would also be just fine. You can then run these validations by passing a context when calling valid? or save.
author.valid?(:password)
author.save(context: :password)
If you use Rails 5 or higher you can pass multiple contexts as an array if you want to run a mix of validations. author.valid?([:create, :password])
For additional info about the regexes used check out the Ruby regexp documentation.
The error was in the send_password_reset method.
Added (: validate => false) to this method and it worked.
def send_password_reset
confirmation_token
self.password_reset_sent_at = Time.zone.now
save!(:validate => false)
AuthorMailer.password_reset(self).deliver!
end
In vallidation i use password.

Validating against blank password in Rails

I added password password validations to my User model:
validates :password, presence: true
validates :password, confirmation: { case_sensitive: true }
but then when I wanted to update other fields on users, those validations were rendering the transaction invalid, as password was not present.
Through a bit of research, I realised that I could skip those validations if password wasn't present:
validates :password, presence: true, if: :validate_password?
validates :password, confirmation: { case_sensitive: true }, if: :validate_password?
def validate_password?
password.present? || password_confirmation.present?
end
However, now when I submit a blank password and password confirmation, validate_password? returns false. I don't really understand what's happening, because
#user.update_attributes(password_reset_edit_params) returns true
where
password_reset_edit_params is <ActionController::Parameters {"password"=>"", "password_confirmation"=>""} permitted: true>
but inside
def validate_password?
password.present? || password_confirmation.present?
end
password and password_confirmation evaluate to nil, and my #user password doesn't update to an empty string.
I should mention that I'm using Bcrypt, and #user.password actually will always evaluate as nil, whereas a password_digest is available.
So what is the solution? Ultimately I guess my question is simply:
How can ignore password validations when I'm not attempting to submit a password, but also permit those validations upon submission of an empty string?
It occurs to me that I could add a condition in the controller that doesn't allow empty strings to be passed, but there's gotta be a neat way to fix this issue.
Any help is much appreciated. Thanks!
If you use Bcrypt and digest, you can use in your model for example
has_secure_password
validates :password, length: { minimum: 8 }, allow_blank: true
In this case validation will work only for setting and changing password.
If you don't change password you don't need to enter password.
Another way
validates :password, presence: true, on: :create
In Rails, you are not validating the param, you are validating the model.
Do not "skip validation if the password is not present". That's the purpose of validation. Validation is meant to validate the model, not the params. If the model has an empty password, and the password's presence is required, it is an invalid model. Always.
but then when I wanted to update other fields on users, those validations were rendering the transaction invalid, as password was not present.
It is not because the password is not present, it is because the instance of the User does not have a password in it. The way it should work is:
User is created with password and validated: User.create(name: "Me", password: "something that isn't nil")
User is updated: User.update(name: "New name") # password is inside the model still so it is still valid
That's how it works. If you want a user to be able to have a nil / empty password, then you should not attempt to place the presence validation in the model.
Ultimately I'm just validating password formatting and confirmation on the User model:
validates :password, confirmation: true,
length: {within: 6..40},
allow_blank: true
and sanitising input data against blank password / password_confirmation in the controller.
if password_reset_edit_params[:password].blank? || password_reset_edit_params[:password_confirmation].blank?
#user.errors[:password] << "can't be blank" if password_reset_edit_params[:password].blank?
#user.errors[:password_confirmation] << "can't be blank" if password_reset_edit_params[:password_confirmation].blank?
render 'edit'

Error using FactoryGirl with has_secure_password in feature spec

Also using mongoid if that's relevant.
When I create a user with factory girl, it says the user is valid. However, when I access that user via ActiveRecord, is says it's invalid. Here's an overview of the issue:
user = create(:user, :activated)
user.valid? # => true
User.count # => 1
db_user = User.first
db_user == user # => true
db_user.valid? # => false
db_user.errors.count # => 0
# it only shows the error messages once I try to modify an attribute
db_user.email = "user#example.com"
db_user.save # => false
db_user.errors # => #messages={:password=>["is too short (minimum is 6 characters)", "can't be blank"]}
Here's my factory:
FactoryGirl.define do
factory :user do
name { Faker::Name.name }
email { "#{name.gsub(/[^0-9a-zA-Z]/i, '')}#example.com" }
phone { Faker::Base.numerify("#{"#" * 10}") }
admin false
password "password"
password_confirmation "password"
end
end
Here's the relevant parts of the User model:
class User
include Mongoid::Document
include ActiveModel::SecurePassword
field :name, type: String
field :email, type: String
validates :password, length: { minimum: 6 },
presence: true
has_secure_password
It is important to understand how has_secure_password works internally:
When a user is created or its password should be changed, its password (and – depending on the configuration – a password_confirmation) attribute has to be set. But internally the password field is not stored in the database, but its value is hashed and stored in a database field named password_digest.
That means: When you load an existing user from the database, its password attribute will be nil (but password_digest should still be present). Therefore it will not be valid when you check such a user for the presence of a password.
To avoid this problem only validate for the presence of a password when creating a new user or if the user has a password that is not blank which means the user tries to update its password:
# there must be a password on create
validates :password, presence: true, on: :create
# the password must follow conventions when present (of example on update)
validates :password, length: { minimum: 6 }, allow_blank: true
You're always running your additional validation. has_secure_password already checks for the password. So you don't need the presence validation.
You could simply make your validation conditional
validates :password, length: { minimum: 6 }, if: Proc.new{|u| u.password_changed? }
Should work I think using ActiveModel::Dirty
If not you can just run the validation if it's present.
validates :password, length: { minimum: 6 }, if: Proc.new{|u| u.password.present? }
I decided to take a look at the "Rails Tutorial - Adding a secure password" section by Michael Hartl. I saw that he wrote the code with has_secure_password before validates, as shown below:
has_secure_password
validates :password, presence: true, length: { minimum: 6 }
Try changing the order of and see if that works. I have note tested it but this is one of the main resources I used when I was starting out and it worked for me in the past. Good Luck.

How to confirm (not verify) an email address with devise in Rails 4?

I'm using devise and I'm trying to add an email_confirmation input, I want my users to type their email address twice in order to make sure that they didn't make any mistake (like the password_confirmation input). I have searched for a solution for days but all I can find is how to "verify" an email address. How would the validation work ? Any answer/suggestion will be greatly appreciated.
To confirm their email address, add the field email_confirmation to the form:
run rails generate devise:views so that devise views are available within the application.
add email_confirmation to the devise form.
Then allow this parameter to be passed to devise:
https://github.com/plataformatec/devise#strong-parameters
The last step is to add the validation to User model (or the model you use with devise):
validates :email, confirmation: true
You'll want to use a regular expression validation. Something like this should do the trick:
validates :email,
format: { with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, message: 'Invalid email' }
That's assuming that by "verify" you mean simply "check that the thing the user entered looks like a valid email".
There's an additional step of verification, not taken by most apps, which checks that the email not only looks like a valid email, but actually is an email address that exists in real life. If that's what you're after, check out https://github.com/kamilc/email_verifier
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }

Rails validation best practice

I have this validation for my user:
validates :password,
presence: true,
confirmation: true,
length: { minimum: 6 },
:on => :create
This is obvious. When I'm creating (registering) a new user, I want to fill up their password hence that's why presence: true.
Case 1) When the user wants to update his account (lets say change his username), the form has only the email and username fields. That's ok and the validation is ok.
Case 2) He forgot his password and I send him his "forgotten password link" and he is on the page where he is creating his new password. The form has these two fields: password and password confirmation. However, he leaves both of these fields empty and submits the form. The validation passes because it's only :on => create! because of case 1)
I can not add :on => update because the case 1) wouldn't pass, because there is no password field.
What should I do in this situation? What is the best practice or what is the real word solution to this "problem"?
What I have done for this situation is instead of using on: :create, I use a virtual attribute that I set only when setting/changing the password. Something like this:
validates :password, if: :changing_password?
attr_accessor :password_scenario
def changing_password?
self.password_scenario.present?
end
Then in your controller, you would simply set password_scenario to true whenever you are requiring password to be present.

Resources