has_editable_password 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NWY1Mjc0ZDJhZDQyNzA0ZWMxNDNhMDdkMjgwNDRmODY2ZTU2NmQ3Ng==
4
+ ZjYyMDNiNzRhNWQ2MjI0NGI3YWU1MGEwZWI3ZjZiZTg2MjkxNjFhNQ==
5
5
  data.tar.gz: !binary |-
6
- Njg3MmE4ZjVhM2RkNmU3ZWQxMjE5OTYxZDU5MjRkMWMyOWJmZDQyNA==
6
+ NTIzMmE0ODliNzM4YjBhMTc3MjY5ZjI0NjViY2FiNDNkZjg5YTI5Mw==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MzBmMWFjNjljY2Y1Y2IzNzU0NDcyYjc0OWY2NTJjNjU3ZjBlOWNkNzczNjM5
10
- YzYyZWQxZDQ5NzQ2ZTZmZjg3NDM1M2Y5NzM3MDY2N2EzNDU3YjdhM2M1MTdm
11
- ZTNhZmFkYzZiNzk3NWE0MTRiMmQ4OTE0ZDBlNTdkOGQwYThhOTU=
9
+ Y2Y4ODU1OTUzY2RhOTU1NjJkNzE0NDYzMTU5NmIwNmI2OTUyMjQ3ZjE3M2Q1
10
+ M2MzMjIzZWUxOGIyOTU0NmQwNzVhNmNiNmIyMjFlMTJmYzdjOGI2YmUzN2I1
11
+ ODg2MDM1YmE3OGE5MDc5MTEyYzM5OTBiZTcyOWQ3YmQ0ZmMwMmE=
12
12
  data.tar.gz: !binary |-
13
- YzUwMjRjOTYzZmRlYzA1NWZlM2Y0OWZmMmM4MDFjZDM5ODA5MWNiMjVmNDE0
14
- NTMwMDYwYTAwZjMyYTNlYWFkNjZmZjc3ZTlkYzRiYjkzMmNlMjY3NmZhYmZj
15
- NjNmNjYxNTkzNjNlYWYzMjJjMmM5NGVhYzE4Yjk3YmViZjRjODg=
13
+ YzNhMzQ5YmQ2MTE5YWZiNGE2MzQzODMwZjEyMzc3YTNkNzg5NDNkZTJhM2Iz
14
+ NjBmMDFmNjI2ZjQ0YjczMmMzZWVjZmIyMDkwZjkzMDU4YmY3MjFhZWMzODZk
15
+ MzYxZmJmMDI4YzE5NjY4NWQ1ZDdhNjY3MzcxNWYzMTRkMzMwODg=
@@ -2,6 +2,9 @@ require 'active_support/concern'
2
2
  require 'active_model/secure_password'
3
3
  require 'securerandom'
4
4
 
5
+ ##
6
+ # Just include this module into your model to have all of its nice features
7
+ # :)
5
8
  module HasEditablePassword
6
9
  extend ActiveSupport::Concern
7
10
  include ActiveModel::SecurePassword
@@ -14,17 +17,13 @@ module HasEditablePassword
14
17
 
15
18
  validate :password_change, on: :update, if: :password_digest_changed?
16
19
 
20
+ ##
21
+ # Overrides the has_secure_password implementation to provide nice features:
22
+ # * Password backup
23
+ # * Password update timestamp
17
24
  def password=(value)
18
25
  @old_password_digest = password_digest unless @old_password_digest or password_digest.blank?
19
-
20
- unless password_digest.blank? or password_digest_changed?
21
- self.previous_password_digest = password_digest if respond_to? :previous_password_digest=
22
- end
23
-
24
- unless password_digest_changed?
25
- self.password_digest_updated = Time.now if respond_to? :password_digest_updated=
26
- end
27
-
26
+ changing_password
28
27
  super(value)
29
28
  end
30
29
  end
@@ -36,11 +35,11 @@ module HasEditablePassword
36
35
  # +password_recovery_token_creation+ to the current time.
37
36
  # Unless specified it calls +save+ to store the token in the database.
38
37
  #
39
- # options[:length] - this is the length of the SecureRandom string generated
40
- # as the token. Since the token is base64_encoded it will be longer than
41
- # that. Default is 32.
42
- # options[:save] - you can use this if you don't want save to be called.
43
- # generate_recovery_token(save: false)
38
+ # * +:length+ - this is the length of the SecureRandom string generated
39
+ # as the token. Since the token is base64_encoded it will be longer than
40
+ # that. Default is 32.
41
+ # * +:save+ - you can use this if you don't want save to be called.
42
+ # generate_recovery_token(save: false)
44
43
  #
45
44
  def generate_recovery_token(options = {})
46
45
  token = SecureRandom.urlsafe_base64(options.delete(:length) || 32)
@@ -51,11 +50,12 @@ module HasEditablePassword
51
50
  end
52
51
 
53
52
  ##
54
- # Returns true if the +recovery_token+ matches with the stored one and the
53
+ # Returns true if the +token+ matches with the stored one and the
55
54
  # token creation time is less than 24 hours ago
56
55
  #
57
- def valid_recovery_token?
58
- recovery_token_match? and !recovery_token_expired?
56
+ # If +token+ is +nil+, the stored token is compared with +@recovery_token+
57
+ def valid_recovery_token?(token = nil)
58
+ recovery_token_match?(token) and !recovery_token_expired?
59
59
  end
60
60
 
61
61
  ##
@@ -75,22 +75,56 @@ module HasEditablePassword
75
75
  end
76
76
 
77
77
  private
78
+ # True if the token has been updated more than 24 hours ago
78
79
  def recovery_token_expired?
79
80
  # 86400 = seconds in a day
80
81
  (Time.now - self.password_recovery_token_creation).round >= 86400
81
82
  end
82
83
 
83
- def recovery_token_match?
84
- BCrypt::Password.new(self.password_recovery_token) == @recovery_token
84
+ ##
85
+ # Compares password_recovery_token with:
86
+ # * @recovery_token if +token+ is nil
87
+ # * +token+ otherwise
88
+ #
89
+ # True if password_recovery_token matches.
90
+ # False if password_recovery_token is nil
91
+ # False if @recovery_token (or +token+) do not match the stored token
92
+ def recovery_token_match?(token = nil)
93
+ BCrypt::Password.new(self.password_recovery_token) == (token || @recovery_token)
85
94
  rescue
86
95
  false
87
96
  end
88
97
 
98
+ ##
99
+ # True if a valid recovery token or current password have been set
100
+ #
89
101
  def allow_password_change?
90
102
  valid_recovery_token? or current_password_match?
91
103
  end
92
104
 
105
+ ##
106
+ # Validation called on :update when the password_digest is touched.
107
+ # Sets an error on password unless the current_password or a valid recovery_token is set
93
108
  def password_change
94
109
  errors[:password] << 'Unauthorized to change the password' unless allow_password_change?
95
110
  end
111
+
112
+ def changing_password
113
+ unless password_digest_changed?
114
+ update_previous_digest
115
+ update_digest_timestamp
116
+ end
117
+ end
118
+
119
+ def update_previous_digest
120
+ if respond_to?(:previous_password_digest=) and !password_digest.blank?
121
+ self.previous_password_digest = password_digest
122
+ end
123
+ end
124
+
125
+ def update_digest_timestamp
126
+ if respond_to? :password_digest_updated=
127
+ self.password_digest_updated = Time.now
128
+ end
129
+ end
96
130
  end
data/lib/version.rb CHANGED
@@ -1 +1 @@
1
- VERSION = '0.1.1'
1
+ VERSION = '0.2.1'
@@ -136,23 +136,37 @@ describe HasEditablePassword do
136
136
  end
137
137
 
138
138
  context 'the creation is less than 24 hours ago' do
139
- it 'returns false if the token is a random string' do
140
- user.generate_recovery_token
141
- user.recovery_token = "deadbeef"
142
- expect(user.valid_recovery_token?).to be_false
139
+ context 'the argument is nil' do
140
+ it 'returns false if the stored token is a random string' do
141
+ user.generate_recovery_token
142
+ user.recovery_token = "deadbeef"
143
+ expect(user.valid_recovery_token?).to be_false
144
+ end
145
+
146
+ context 'the stored token is a base64 string' do
147
+ it 'returns false if the token does not match' do
148
+ user.generate_recovery_token
149
+ user.recovery_token = SecureRandom.urlsafe_base64
150
+ expect(user.valid_recovery_token?).to be_false
151
+ end
152
+
153
+ it 'returns true if the token matches' do
154
+ token = user.generate_recovery_token
155
+ user.recovery_token = token
156
+ expect(user.valid_recovery_token?).to be_true
157
+ end
158
+ end
143
159
  end
144
160
 
145
- context 'the token is a base64 string' do
161
+ context 'the argument is not nil' do
146
162
  it 'returns false if the token does not match' do
147
163
  user.generate_recovery_token
148
- user.recovery_token = SecureRandom.urlsafe_base64
149
- expect(user.valid_recovery_token?).to be_false
164
+ expect(user.valid_recovery_token?(SecureRandom.urlsafe_base64)).to be_false
150
165
  end
151
166
 
152
167
  it 'returns true if the token matches' do
153
168
  token = user.generate_recovery_token
154
- user.recovery_token = token
155
- expect(user.valid_recovery_token?).to be_true
169
+ expect(user.valid_recovery_token?(token)).to be_true
156
170
  end
157
171
  end
158
172
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: has_editable_password
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Francesco Boffa