has_editable_password 0.1.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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