提交 9b63bf1d 编写于 作者: U Unathi Chonco

Remove method for regenerating a token, and update `#authenticate`.

This change now creates a method `#authenticate_XXX` where XXX is
the configured attribute name on `#has_secure_password`. `#authenticate`
is now an alias to this method when the attribute name is the default
'password'
上级 86a48b4d
* Allows configurable attribute name for `#has_secure_password`. This
still defaults to an attribute named 'password', causing no breaking
change. Also includes a convenience method `#regenerate_XXX` where
+XXX+ is the name of the custom attribute name, eg:
change. There is a new method `#authenticate_XXX` where XXX is the
configured attribute name, making the existing `#authenticate` now an
alias for this when the attribute is the default 'password'.
Example:
class User < ActiveRecord::Base
has_secure_password :activation_token, validations: false
end
user = User.new()
user.regenerate_activation_token
user.activation_token # => "ME7abXFGvzZWJRVrD6Et0YqAS6Pg2eDo"
user.activation_token_digest # => "$2a$10$0Budk0Fi/k2CDm2PEwa3Be..."
The existing `#authenticate` method now allows specifying the attribute
to be authenticated, but defaults to 'password', eg:
user.authenticate('ME7abXFGvzZWJRVrD6Et0YqAS6Pg2eDo', :activation_token) # => user
user.activation_token = "a_new_token"
user.activation_token_digest # => "$2a$10$0Budk0Fi/k2CDm2PEwa3Be..."
user.authenticate_activation_token('a_new_token') # => user
*Unathi Chonco*
......
......@@ -48,15 +48,14 @@ module ClassMethods
# user.save # => false, confirmation doesn't match
# user.password_confirmation = 'mUc3m00RsqyRe'
# user.save # => true
# user.regenerate_activation_token
# user.activation_token # => "ME7abXFGvzZWJRVrD6Et0YqAS6Pg2eDo"
# user.activation_token = "a_new_token"
# user.activation_token_digest # => "$2a$10$0Budk0Fi/k2CDm2PEwa3BeXO5tPOA85b6xazE9rp8nF2MIJlsUik."
# user.save # => true
# user.authenticate('notright') # => false
# user.authenticate('mUc3m00RsqyRe') # => user
# user.authenticate_activation_token('a_new_token') # => user
# User.find_by(name: 'david').try(:authenticate, 'notright') # => false
# User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe') # => user
# user.authenticate('ME7abXFGvzZWJRVrD6Et0YqAS6Pg2eDo', :activation_token) # => user
def has_secure_password(attribute = :password, validations: true)
# Load bcrypt gem only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
......@@ -70,18 +69,6 @@ def has_secure_password(attribute = :password, validations: true)
attr_reader attribute
# Encrypts the password into the +password_digest+ attribute, only if the
# new password is not empty.
#
# class User < ActiveRecord::Base
# has_secure_password validations: false
# end
#
# user = User.new
# user.password = nil
# user.password_digest # => nil
# user.password = 'mUc3m00RsqyRe'
# user.password_digest # => "$2a$10$4LEA7r4YmNHtvlAvHhsYAeZmk/xeUVtMTYqwIvYY76EW5GUqDiP4."
define_method("#{attribute}=") do |unencrypted_password|
if unencrypted_password.nil?
self.send("#{attribute}_digest=", nil)
......@@ -96,11 +83,22 @@ def has_secure_password(attribute = :password, validations: true)
instance_variable_set("@#{attribute}_confirmation", unencrypted_password)
end
define_method("regenerate_#{attribute}") do
self.send("#{attribute}=", self.class.generate_unique_secure_token)
# Returns +self+ if the password is correct, otherwise +false+.
#
# class User < ActiveRecord::Base
# has_secure_password validations: false
# end
#
# user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
# user.save
# user.authenticate_password('notright') # => false
# user.authenticate_password('mUc3m00RsqyRe') # => user
define_method("authenticate_#{attribute}") do |unencrypted_password|
attribute_digest = send("#{attribute}_digest")
BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
end
include InstanceMethodsOnActivation
alias_method :authenticate, :authenticate_password if attribute == :password
if validations
include ActiveModel::Validations
......@@ -117,28 +115,6 @@ def has_secure_password(attribute = :password, validations: true)
validates_confirmation_of attribute, allow_blank: true
end
end
# SecureRandom::base64 is used to generate a 24-character unique token, so collisions are highly unlikely.
def generate_unique_secure_token
SecureRandom.base64(24)
end
end
module InstanceMethodsOnActivation
# Returns +self+ if the password is correct, otherwise +false+.
#
# class User < ActiveRecord::Base
# has_secure_password validations: false
# end
#
# user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
# user.save
# user.authenticate('notright') # => false
# user.authenticate('mUc3m00RsqyRe') # => user
def authenticate(unencrypted_password, attribute = :password)
attribute_digest = send("#{attribute}_digest")
BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
end
end
end
end
......@@ -189,8 +189,8 @@ class SecurePasswordTest < ActiveModel::TestCase
assert !@user.authenticate("wrong")
assert @user.authenticate("secret")
assert !@user.authenticate("wrong", :activation_token)
assert @user.authenticate("new_token", :activation_token)
assert !@user.authenticate_activation_token("wrong")
assert @user.authenticate_activation_token("new_token")
end
test "Password digest cost defaults to bcrypt default cost when min_cost is false" do
......@@ -219,13 +219,4 @@ class SecurePasswordTest < ActiveModel::TestCase
@user.password = "secret"
assert_equal BCrypt::Engine::MIN_COST, @user.password_digest.cost
end
test "regenerate attribute method" do
old_digest = @user.activation_token_digest
@user.regenerate_activation_token
assert_not_nil @user.activation_token
assert_not_nil @user.activation_token_digest
assert_not_equal old_digest, @user.activation_token_digest
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册