I'm hoping to reproduce Sequel's validates_min_length
errors for a Bcrypt-encrypted password. I can't use the validation as it will test the password hash, rather than the un-encrypted text.
I'm having difficulty getting the password=
method to produce the desired errors.
require 'sequel'
DB = Sequel.sqlite
DB.create_table(:users) do
primary_key :id
String :name, null: false, unique: true
String :password, null: false
end
class User < Sequel::Model
plugin :validation_helpers
def validate
super
validates_presence [:name,:password]
validates_unique [:name]
validates_min_length 8, :password
end
end
IRB:
irb(main):001:0> u=User.new(name: 'foobar', password: 'Pa55w0rd')
=> #<User @values={:name=>"foobar", :password=>"Pa55w0rd"}>
irb(main):002:0> u.valid?
=> true
irb(main):003:0> u.password=nil
=> nil
irb(main):004:0> u
=> #<User @values={:name=>"foobar", :password=>nil}>
irb(main):005:0> u.valid?
=> false
irb(main):007:0> u.errors
=> {:password=>["is not present", "is shorter than 8 characters"]}
irb(main):008:0> u.password='foo'
=> "foo"
irb(main):009:0> u
=> #<User @values={:name=>"foobar", :password=>"foo"}>
irb(main):010:0> u.valid?
=> false
irb(main):011:0> u.errors
=> {:password=>["is shorter than 8 characters"]}
require 'sequel'
require 'bcrypt'
DB = Sequel.sqlite
DB.create_table(:users) do
primary_key :id
String :name, null: false, unique: true
String :password_hash, null: false
end
class User < Sequel::Model
plugin :validation_helpers
include BCrypt
def validate
super
validates_presence [:name,:password]
validates_unique [:name]
end
def password
# check for :password_hash existence to ensure that validates_presence :password works correctly
@password ||= Password.new(password_hash) if password_hash
end
def password=(new_password)
# add validation errors
errors.add(:password, 'is shorter than 8 characters') if new_password==nil || new_password.length < 8
if new_password == nil
@password = nil
else
@password = Password.create(new_password)
end
self.password_hash = @password
end
end
IRB:
irb(main):001:0> u=User.new(name: 'foobar', password: 'Pa55w0rd')
=> #<User @values={:name=>"foobar", :password_hash=>"$2a$10$K3UALPYz/bb5bdrGmbq22eRM31A6rU3kqkbzcU4.6J5APQVSqxQo6"}>
irb(main):002:0> u.valid?
=> true
irb(main):003:0> u.password=nil
=> nil
irb(main):004:0> u
=> #<User @values={:name=>"foobar", :password_hash=>nil}>
irb(main):005:0> u.valid?
=> false
irb(main):006:0> u.errors
=> {:password=>["is not present"]}
irb(main):007:0> u.password='foo'
=> "foo"
irb(main):009:0> u
=> #<User @values={:name=>"foobar", :password_hash=>"$2a$10$lA6fsKXSvl5cd.Zl53qEqOzxk1LPehvGujWaXwcf1//IUc82CmowC"}>
irb(main):008:0> u.valid?
=> true
Both invalid passwords (nil
,'foo') are missing the is shorter than 8 characters
error.
What am I missing?
Versions:
$ sequel --version
sequel 5.7.1
This works as desired:
require 'sequel'
require 'bcrypt'
DB = Sequel.sqlite
DB.create_table(:users) do
primary_key :id
String :name, null: false, unique: true
String :password_hash, null: false
end
class User < Sequel::Model
plugin :validation_helpers
include BCrypt
def validate
super
validates_presence [:name,:password]
validates_unique [:name]
errors.add(:password, 'is shorter than 8 characters') if @password==nil || @password.length < 8
end
def password
Password.new(password_hash) if password_hash
end
def password=(new_password)
# uncomment to prevent bad passwords from changing a good password; probably needs to include a non-terminating error message
# return if new_password==nil || new_password.length < 8
# store clear-text password for validation
@password = new_password
#modify password hash accordingly
self.password_hash = if new_password then Password.create(new_password) else nil end
end
end