重现 Sequel 的 validates_min_length 验证

Reproduce Sequel's validates_min_length validation

我希望重现 Sequel 的 validates_min_length Bcrypt 加密密码错误。我无法使用验证,因为它将测试密码哈希,而不是未加密的文本。

我很难让 password= 方法产生所需的错误。

未加密密码逻辑

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=>"a$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=>"a$lA6fsKXSvl5cd.Zl53qEqOzxk1LPehvGujWaXwcf1//IUc82CmowC"}>
irb(main):008:0> u.valid?
=> true

两个无效密码 (nil,'foo') 都缺少 is shorter than 8 characters 错误。

我错过了什么?

版本:

$ sequel --version
sequel 5.7.1

@password 实际上并不短于 8 个字符。 BCrypt::Password 是字符串的子类,它的长度与密码哈希值相同。如果您想使用验证来确保密码的大小,则必须在 password= 中设置 @password

我不是 100% 确定功能,但这应该适合你。

def validate
  super
  validates_presence [:name,:password]
  validates_unique [:name]
  validates_min_length 8, :password
end

def password=(new_password)
  return unless @password = new_password and @password.to_s.length >= 8
  self.password_hash = Password.create(password)
end

我添加了实际长度验证来验证 valid? 将 return 为假。此外,我稍微清理了 password= 方法。 所以现在 password= 有一个保护子句,如果密码无效(如 "less than 8 characters" 定义的那样),它只是 returns。 否则我们通过 Password.create 创建一个新的 password_hash 并将其分配给 @password_hash.

请注意: 我建议的代码和您的代码之间存在功能性变化,即当 "new_password" 无效时 @password_hash 不会被覆盖。这对我来说似乎违反直觉,无效的密码可能会覆盖现有的和有效的 password_hash,因此我在实施中发生了变化。

话虽这么说,您最好的选择实际上是这样做

class User < Sequel::Model
  plugin :validation_helpers
  include BCrypt
  attr_accessor :password
  def validate
    super
    validates_presence [:name,:password]
    validates_unique [:name]
    validates_min_length 8, :password
  end

  def before_save
    self.password_hash = Password.create(password)
  end
end

这避免了所有操作,并且只会在模型通过验证检查时创建 password_hash(这是您实际需要的)

这按预期工作:

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