Validating against blank password in Rails - ruby-on-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'

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.

Ruby on Rails has_secure_password validation gets errors on update

I have working on a Ruby on Rails application and have a problem. I've user model in my domain and it has a has_secure_password field like this:
class User < ApplicationRecord
validates :name, presence: true
has_secure_password
validates :password, presence: true
validates :password, length: { in: 6..20, message: 'Password shoul be minimum 6 characters'}
end
And I save the user object with this function:
def self.create_with_params(email,name,password)
user = User.new()
user.provider= :mail
user.name=name
user.email=email
user.password=password
user.activate_status=false
user.save(validate: true);
return user;
end
That's ok too.
But when I want to update a field in my user model. I get validation error for password field.
#user = User.find_by(email: params[:email]) # this record has a valid password_digest field in database.
#user.name = 'John'
#user.save
puts #user.errors.full_messages
It prints:
"Password shoul be minimum 6 characters".
Note: I have password_digest field in my users table.
I think when I query users table password_digest field couldn't be filled in to password field on model.
Any suggestions?
I think when I query Users table password_digest field couldn't be
filled in to password field on model.
Correct, the password is not stored, so when you query User you don't get the password field, yet your model asks for it every time with:
validates :password, presence: true
So, you will need to provide password before saving #user to make it work, but i guess that is not what you want, so just remove that validation from your model,
You won't need to validate password every time, just upon creating, and that validation is done with has_secure_password.
Also, for the same reason, you will need to update length validation to:
validates :password, length: { in: 6..20, message: 'Password shoul be minimum 6 characters'}, allow_nil: true

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.

validates password on user model not working in edit view

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.

Rails Conditional Validations

I am working on a project and need some help on where to begin. I have three pages
Update User
Create User
Admin User Password Change (like a Hard Reset Password for but only the admin can reset the user's password)
Change Password
On Create User first name, last name, username, password, and password confirmation are mandatory.
On Update User just first name, last name and username are mandatory.
On Admin User Password Change and Change Password, just password and password confirmation are mandatory.
How would you go about doing this? I don't think this is possible through models using validates_presence_of with an if because there are too many scenarios. Any help or guidance would be appreciated. Also, I am pretty new to Rails if you can't already tell.
You can pass conditionals to your validations:
validates :password, :confirmation => true, :presence => true
validates :first_name, :last_name, :username, :presence => true
validate :admin_user_password_change?
Of course you'd have to define what the admin_user_password_change? method would be to determine if it is an admin user changing a password.
UPDATE
The admin_user_password_change? method might be something like:
def admin_user_password_change?
unless self.admin? && self.password.present? && self.password_confirmation.present?
self.errors.add(:admin_password_change, "password and password_confirmation are required.")
end
end
As for How would it communicate with the controller?, it wouldn't directly. But if any of the conditions in the method are false (e.g. self.admin? && self.password.present? && self.password_confirmation.present?), an error will be added to the instance of User and the instance won't save in the controller.
Setting some fields to new values doesn't unset other fields; just because you're only updating some fields in one action doesn't mean the other fields will be unset, so long as they start in a consistent state.
Just add your validations. It will work fine.
You can tell to your validation work only on certain cenarios only using:
The create:
validates :first_name, :last_name, :username, presence: true, on: :create
The update:
validates :password, presence: true, on: :update
Take a look at on.
For validation based on context take a look at Context Validations

Resources