Skip to content

Commit ebe9e57

Browse files
committed
Enhance has_secure_password to also generate a password_salt method
1 parent 18ae56a commit ebe9e57

File tree

4 files changed

+38
-1
lines changed

4 files changed

+38
-1
lines changed

activemodel/CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
* `has_secure_password` now generates an `#{attribute}_salt` method that returns the salt
2+
used to compute the password digest. The salt will change whenever the password is changed,
3+
so it can be used to create single-use password reset tokens with `generates_token_for`:
4+
5+
```ruby
6+
class User < ActiveRecord::Base
7+
has_secure_password
8+
9+
generates_token_for :password_reset, expires_in: 15.minutes do
10+
password_salt&.last(10)
11+
end
12+
end
13+
```
14+
15+
*Lázaro Nixon*
16+
117
* Improve typography of user facing error messages. In English contractions,
218
the Unicode APOSTROPHE (`U+0027`) is now RIGHT SINGLE QUOTATION MARK
319
(`U+2019`). For example, "can't be blank" is now "can’t be blank".

activemodel/lib/active_model/secure_password.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ def initialize(attribute)
170170
attribute_digest.present? && BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
171171
end
172172

173+
# Returns the salt, a small chunk of random data added to the password before it's hashed.
174+
define_method("#{attribute}_salt") do
175+
attribute_digest = public_send("#{attribute}_digest")
176+
attribute_digest.present? ? BCrypt::Password.new(attribute_digest).salt : nil
177+
end
178+
173179
alias_method :authenticate, :authenticate_password if attribute == :password
174180
end
175181
end

activemodel/test/cases/secure_password_test.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,21 @@ class SecurePasswordTest < ActiveModel::TestCase
265265
assert_equal false, @user.authenticate(" ")
266266
end
267267

268+
test "password_salt" do
269+
@user.password = "secret"
270+
assert_equal @user.password_digest.salt, @user.password_salt
271+
end
272+
273+
test "password_salt should return nil when password is nil" do
274+
@user.password = nil
275+
assert_nil @user.password_salt
276+
end
277+
278+
test "password_salt should return nil when password digest is nil" do
279+
@user.password_digest = nil
280+
assert_nil @user.password_salt
281+
end
282+
268283
test "Password digest cost defaults to bcrypt default cost when min_cost is false" do
269284
ActiveModel::SecurePassword.min_cost = false
270285

activerecord/lib/active_record/token_for.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ module ClassMethods
6767
#
6868
# generates_token_for :password_reset, expires_in: 15.minutes do
6969
# # Last 10 characters of password salt, which changes when password is updated:
70-
# BCrypt::Password.new(password_digest).salt[-10..]
70+
# password_salt&.last(10)
7171
# end
7272
# end
7373
#

0 commit comments

Comments
 (0)