devise-security 0.16.0 → 0.18.0

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.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +3 -1
  3. data/README.md +18 -7
  4. data/app/controllers/devise/paranoid_verification_code_controller.rb +26 -12
  5. data/app/controllers/devise/password_expired_controller.rb +22 -5
  6. data/config/locales/bg.yml +42 -0
  7. data/config/locales/by.yml +1 -0
  8. data/config/locales/cs.yml +5 -0
  9. data/config/locales/de.yml +3 -0
  10. data/config/locales/en.yml +2 -1
  11. data/config/locales/es.yml +12 -0
  12. data/config/locales/fa.yml +1 -0
  13. data/config/locales/fr.yml +14 -2
  14. data/config/locales/hi.yml +1 -0
  15. data/config/locales/it.yml +1 -0
  16. data/config/locales/ja.yml +12 -0
  17. data/config/locales/nl.yml +1 -0
  18. data/config/locales/pt.yml +1 -0
  19. data/config/locales/ru.yml +1 -0
  20. data/config/locales/tr.yml +25 -1
  21. data/config/locales/uk.yml +1 -0
  22. data/config/locales/zh_CN.yml +1 -0
  23. data/config/locales/zh_TW.yml +1 -0
  24. data/lib/devise-security/controllers/helpers.rb +23 -11
  25. data/lib/devise-security/hooks/expirable.rb +3 -3
  26. data/lib/devise-security/hooks/paranoid_verification.rb +1 -3
  27. data/lib/devise-security/hooks/password_expirable.rb +1 -3
  28. data/lib/devise-security/hooks/session_limitable.rb +4 -4
  29. data/lib/devise-security/models/compatibility/active_record_patch.rb +4 -3
  30. data/lib/devise-security/models/compatibility/mongoid_patch.rb +3 -2
  31. data/lib/devise-security/models/database_authenticatable_patch.rb +18 -10
  32. data/lib/devise-security/models/expirable.rb +6 -5
  33. data/lib/devise-security/models/paranoid_verification.rb +2 -2
  34. data/lib/devise-security/models/password_archivable.rb +3 -3
  35. data/lib/devise-security/models/secure_validatable.rb +57 -20
  36. data/lib/devise-security/orm/mongoid.rb +1 -1
  37. data/lib/devise-security/patches.rb +14 -8
  38. data/lib/devise-security/routes.rb +2 -3
  39. data/lib/devise-security/validators/password_complexity_validator.rb +53 -26
  40. data/lib/devise-security/version.rb +1 -1
  41. data/lib/devise-security.rb +9 -3
  42. data/lib/generators/devise_security/install_generator.rb +3 -5
  43. data/lib/generators/templates/devise_security.rb +6 -1
  44. data/test/controllers/test_paranoid_verification_code_controller.rb +133 -0
  45. data/test/controllers/test_password_expired_controller.rb +87 -33
  46. data/test/controllers/test_security_question_controller.rb +25 -19
  47. data/test/dummy/app/controllers/overrides/paranoid_verification_code_controller.rb +7 -0
  48. data/test/dummy/app/controllers/overrides/password_expired_controller.rb +17 -0
  49. data/test/dummy/app/controllers/widgets_controller.rb +3 -0
  50. data/test/dummy/app/models/application_user_record.rb +2 -1
  51. data/test/dummy/app/models/mongoid/confirmable_fields.rb +2 -0
  52. data/test/dummy/app/models/mongoid/database_authenticable_fields.rb +4 -3
  53. data/test/dummy/app/models/mongoid/expirable_fields.rb +2 -0
  54. data/test/dummy/app/models/mongoid/lockable_fields.rb +2 -0
  55. data/test/dummy/app/models/mongoid/mappings.rb +4 -2
  56. data/test/dummy/app/models/mongoid/omniauthable_fields.rb +2 -0
  57. data/test/dummy/app/models/mongoid/paranoid_verification_fields.rb +2 -0
  58. data/test/dummy/app/models/mongoid/password_archivable_fields.rb +2 -0
  59. data/test/dummy/app/models/mongoid/password_expirable_fields.rb +2 -0
  60. data/test/dummy/app/models/mongoid/recoverable_fields.rb +2 -0
  61. data/test/dummy/app/models/mongoid/registerable_fields.rb +4 -2
  62. data/test/dummy/app/models/mongoid/rememberable_fields.rb +2 -0
  63. data/test/dummy/app/models/mongoid/secure_validatable_fields.rb +2 -0
  64. data/test/dummy/app/models/mongoid/security_questionable_fields.rb +2 -0
  65. data/test/dummy/app/models/mongoid/session_limitable_fields.rb +2 -0
  66. data/test/dummy/app/models/mongoid/timeoutable_fields.rb +2 -0
  67. data/test/dummy/app/models/mongoid/trackable_fields.rb +2 -0
  68. data/test/dummy/app/models/mongoid/validatable_fields.rb +2 -0
  69. data/test/dummy/app/models/paranoid_verification_user.rb +26 -0
  70. data/test/dummy/app/models/password_expired_user.rb +26 -0
  71. data/test/dummy/app/models/user.rb +5 -5
  72. data/test/dummy/app/models/widget.rb +1 -3
  73. data/test/dummy/app/mongoid/one_user.rb +5 -5
  74. data/test/dummy/app/mongoid/user_on_engine.rb +2 -2
  75. data/test/dummy/app/mongoid/user_on_main_app.rb +2 -2
  76. data/test/dummy/app/mongoid/user_with_validations.rb +3 -3
  77. data/test/dummy/app/mongoid/user_without_email.rb +7 -4
  78. data/test/dummy/config/application.rb +3 -7
  79. data/test/dummy/config/boot.rb +1 -1
  80. data/test/dummy/config/environment.rb +1 -1
  81. data/test/dummy/config/environments/test.rb +1 -0
  82. data/test/dummy/config/initializers/devise.rb +1 -5
  83. data/test/dummy/config/locales/en.yml +10 -0
  84. data/test/dummy/config/routes.rb +3 -1
  85. data/test/dummy/config.ru +1 -1
  86. data/test/dummy/db/migrate/20120508165529_create_tables.rb +5 -5
  87. data/test/dummy/lib/shared_expirable_columns.rb +1 -0
  88. data/test/dummy/lib/shared_security_questions_fields.rb +1 -0
  89. data/test/dummy/lib/shared_user.rb +17 -6
  90. data/test/dummy/lib/shared_user_without_omniauth.rb +12 -3
  91. data/test/dummy/lib/shared_verification_fields.rb +1 -0
  92. data/test/dummy/log/test.log +39637 -16086
  93. data/test/i18n_test.rb +22 -0
  94. data/test/integration/test_paranoid_verification_code_workflow.rb +53 -0
  95. data/test/integration/test_password_expirable_workflow.rb +2 -2
  96. data/test/integration/test_session_limitable_workflow.rb +5 -3
  97. data/test/orm/active_record.rb +7 -7
  98. data/test/support/integration_helpers.rb +18 -12
  99. data/test/test_compatibility.rb +2 -0
  100. data/test/test_complexity_validator.rb +247 -37
  101. data/test/test_database_authenticatable_patch.rb +146 -0
  102. data/test/test_helper.rb +7 -8
  103. data/test/test_install_generator.rb +1 -1
  104. data/test/test_paranoid_verification.rb +8 -9
  105. data/test/test_password_archivable.rb +34 -11
  106. data/test/test_password_expirable.rb +27 -27
  107. data/test/test_secure_validatable.rb +265 -107
  108. data/test/test_secure_validatable_overrides.rb +185 -0
  109. data/test/test_session_limitable.rb +9 -9
  110. data/test/tmp/config/initializers/{devise-security.rb → devise_security.rb} +6 -1
  111. data/test/tmp/config/locales/devise.security_extension.by.yml +1 -0
  112. data/test/tmp/config/locales/devise.security_extension.cs.yml +5 -0
  113. data/test/tmp/config/locales/devise.security_extension.de.yml +3 -0
  114. data/test/tmp/config/locales/devise.security_extension.en.yml +2 -1
  115. data/test/tmp/config/locales/devise.security_extension.es.yml +12 -0
  116. data/test/tmp/config/locales/devise.security_extension.fa.yml +1 -0
  117. data/test/tmp/config/locales/devise.security_extension.fr.yml +14 -2
  118. data/test/tmp/config/locales/devise.security_extension.hi.yml +21 -20
  119. data/test/tmp/config/locales/devise.security_extension.it.yml +1 -0
  120. data/test/tmp/config/locales/devise.security_extension.ja.yml +12 -0
  121. data/test/tmp/config/locales/devise.security_extension.nl.yml +1 -0
  122. data/test/tmp/config/locales/devise.security_extension.pt.yml +1 -0
  123. data/test/tmp/config/locales/devise.security_extension.ru.yml +1 -0
  124. data/test/tmp/config/locales/devise.security_extension.tr.yml +25 -1
  125. data/test/tmp/config/locales/devise.security_extension.uk.yml +1 -0
  126. data/test/tmp/config/locales/devise.security_extension.zh_CN.yml +1 -0
  127. data/test/tmp/config/locales/devise.security_extension.zh_TW.yml +1 -0
  128. metadata +82 -41
  129. data/lib/devise-security/patches/confirmations_controller_captcha.rb +0 -23
  130. data/lib/devise-security/patches/confirmations_controller_security_question.rb +0 -26
  131. data/lib/devise-security/patches/passwords_controller_captcha.rb +0 -22
  132. data/lib/devise-security/patches/passwords_controller_security_question.rb +0 -25
  133. data/lib/devise-security/patches/registrations_controller_captcha.rb +0 -35
  134. data/lib/devise-security/patches/sessions_controller_captcha.rb +0 -26
  135. data/lib/devise-security/patches/unlocks_controller_captcha.rb +0 -22
  136. data/lib/devise-security/patches/unlocks_controller_security_question.rb +0 -25
  137. data/test/dummy/app/controllers/foos_controller.rb +0 -0
  138. data/test/dummy/app/models/secure_user.rb +0 -9
  139. data/test/dummy/lib/shared_user_without_email.rb +0 -28
  140. data/test/dummy/log/development.log +0 -883
@@ -1,161 +1,319 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'test_helper'
4
- require 'rails_email_validator'
5
4
 
6
5
  class TestSecureValidatable < ActiveSupport::TestCase
7
6
  class User < ApplicationRecord
8
- devise :database_authenticatable, :password_archivable,
9
- :paranoid_verification, :password_expirable, :secure_validatable
7
+ devise :database_authenticatable, :secure_validatable
10
8
  include ::Mongoid::Mappings if DEVISE_ORM == :mongoid
11
9
  end
12
10
 
13
- test 'email cannot be blank' do
14
- msg = "Email can't be blank"
15
- user = User.create password: 'passWord1', password_confirmation: 'passWord1'
11
+ class EmailNotRequiredUser < User
12
+ protected
16
13
 
17
- assert_equal(false, user.valid?)
18
- assert_equal([msg], user.errors.full_messages)
19
- assert_raises(ORMInvalidRecordException) do
20
- user.save!
14
+ def email_required?
15
+ false
21
16
  end
22
17
  end
23
18
 
19
+ test 'email cannot be blank upon creation' do
20
+ user = User.new(
21
+ password: 'Password1!', password_confirmation: 'Password1!'
22
+ )
23
+
24
+ assert user.invalid?
25
+ assert_equal(["Email can't be blank"], user.errors.full_messages)
26
+ end
27
+
28
+ test 'email can be blank upon creation if email not required' do
29
+ user = EmailNotRequiredUser.new(
30
+ password: 'Password1!', password_confirmation: 'Password1!'
31
+ )
32
+
33
+ assert user.valid?
34
+ end
35
+
36
+ test 'email cannot be updated to be blank' do
37
+ user = User.new(
38
+ email: 'bob@microsoft.com',
39
+ password: 'Password1!',
40
+ password_confirmation: 'Password1!'
41
+ )
42
+
43
+ assert user.valid?
44
+
45
+ user.email = nil
46
+
47
+ assert user.invalid?
48
+ assert_equal(["Email can't be blank"], user.errors.full_messages)
49
+ end
50
+
51
+ test 'email can be updated to be blank if email not required' do
52
+ user = EmailNotRequiredUser.new(
53
+ email: 'bob@microsoft.com',
54
+ password: 'Password1!',
55
+ password_confirmation: 'Password1!'
56
+ )
57
+
58
+ assert user.valid?
59
+
60
+ user.email = nil
61
+
62
+ assert user.valid?
63
+ end
64
+
24
65
  test 'email must be valid' do
25
- msg = 'Email is invalid'
26
- user = User.create email: 'bob', password: 'passWord1', password_confirmation: 'passWord1'
27
- assert_equal(false, user.valid?)
28
- assert_equal([msg], user.errors.full_messages)
29
- assert_raises(ORMInvalidRecordException) do
30
- user.save!
31
- end
66
+ user = User.new(
67
+ email: 'bob', password: 'Password1!', password_confirmation: 'Password1!'
68
+ )
69
+
70
+ assert user.invalid?
71
+ assert_equal(['Email is invalid'], user.errors.full_messages)
32
72
  end
33
73
 
34
74
  test 'validate both email and password' do
35
- msgs = ['Email is invalid', 'Password must contain at least one upper-case letter']
36
- user = User.create email: 'bob@@foo.tv', password: 'password1', password_confirmation: 'password1'
37
- assert_equal(false, user.valid?)
75
+ user = User.new(
76
+ email: 'bob',
77
+ password: 'password1!',
78
+ password_confirmation: 'password1!'
79
+ )
80
+
81
+ assert user.invalid?
82
+ assert_equal(
83
+ [
84
+ 'Email is invalid',
85
+ 'Password must contain at least one upper-case letter'
86
+ ],
87
+ user.errors.full_messages
88
+ )
89
+ end
90
+
91
+ test 'password cannot be blank upon creation' do
92
+ user = User.new(email: 'bob@microsoft.com')
93
+
94
+ msgs = ["Password can't be blank"]
95
+
96
+ msgs << "Encrypted password can't be blank" if DEVISE_ORM == :mongoid
97
+
98
+ assert user.invalid?
38
99
  assert_equal(msgs, user.errors.full_messages)
39
- assert_raises(ORMInvalidRecordException) { user.save! }
100
+ end
101
+
102
+ test 'password cannot be updated to be blank' do
103
+ user = User.new(
104
+ email: 'bob@microsoft.com',
105
+ password: 'Password1!',
106
+ password_confirmation: 'Password1!'
107
+ )
108
+
109
+ assert user.valid?
110
+
111
+ user.password = nil
112
+ user.password_confirmation = nil
113
+
114
+ assert user.invalid?
115
+ assert_equal(["Password can't be blank"], user.errors.full_messages)
116
+ end
117
+
118
+ test 'password_confirmation must match password' do
119
+ user = User.new(
120
+ email: 'bob@microsoft.com',
121
+ password: 'Password1!',
122
+ password_confirmation: 'not the same password'
123
+ )
124
+
125
+ assert user.invalid?
126
+ assert_equal(
127
+ ["Password confirmation doesn't match Password"],
128
+ user.errors.full_messages
129
+ )
130
+ end
131
+
132
+ test 'password_confirmation cannot be blank' do
133
+ user = User.new(
134
+ email: 'bob@microsoft.com',
135
+ password: 'Password1!',
136
+ password_confirmation: ''
137
+ )
138
+
139
+ assert user.invalid?
140
+ assert_equal(
141
+ ["Password confirmation doesn't match Password"],
142
+ user.errors.full_messages
143
+ )
144
+ end
145
+
146
+ test 'password_confirmation can be skipped' do
147
+ user = User.new(
148
+ email: 'bob@microsoft.com',
149
+ password: 'Password1!',
150
+ password_confirmation: nil
151
+ )
152
+
153
+ assert user.valid?
40
154
  end
41
155
 
42
156
  test 'password must have capital letter' do
43
- msgs = ['Password must contain at least one upper-case letter']
44
- user = User.create email: 'bob@microsoft.com', password: 'password1', password_confirmation: 'password1'
45
- assert_equal(false, user.valid?)
46
- assert_equal(msgs, user.errors.full_messages)
47
- assert_raises(ORMInvalidRecordException) { user.save! }
157
+ user = User.new(
158
+ email: 'bob@microsoft.com',
159
+ password: 'password1',
160
+ password_confirmation: 'password1'
161
+ )
162
+
163
+ assert user.invalid?
164
+ assert_equal(
165
+ ['Password must contain at least one upper-case letter'],
166
+ user.errors.full_messages
167
+ )
48
168
  end
49
169
 
50
170
  test 'password must have lowercase letter' do
51
- msg = 'Password must contain at least one lower-case letter'
52
- user = User.create email: 'bob@microsoft.com', password: 'PASSWORD1', password_confirmation: 'PASSWORD1'
53
- assert_equal(false, user.valid?)
54
- assert_equal([msg], user.errors.full_messages)
55
- assert_raises(ORMInvalidRecordException) { user.save! }
171
+ user = User.new(
172
+ email: 'bob@microsoft.com',
173
+ password: 'PASSWORD1',
174
+ password_confirmation: 'PASSWORD1'
175
+ )
176
+
177
+ assert user.invalid?
178
+ assert_equal(
179
+ ['Password must contain at least one lower-case letter'],
180
+ user.errors.full_messages
181
+ )
56
182
  end
57
183
 
58
184
  test 'password must have number' do
59
- msg = 'Password must contain at least one digit'
60
- user = User.create email: 'bob@microsoft.com', password: 'PASSword', password_confirmation: 'PASSword'
61
- assert_equal(false, user.valid?)
62
- assert_equal([msg], user.errors.full_messages)
63
- assert_raises(ORMInvalidRecordException) { user.save! }
185
+ user = User.new(
186
+ email: 'bob@microsoft.com',
187
+ password: 'PASSword',
188
+ password_confirmation: 'PASSword'
189
+ )
190
+
191
+ assert user.invalid?
192
+ assert_equal(
193
+ ['Password must contain at least one digit'],
194
+ user.errors.full_messages
195
+ )
64
196
  end
65
197
 
66
- test 'password must have minimum length' do
67
- msg = 'Password is too short (minimum is 7 characters)'
68
- user = User.create email: 'bob@microsoft.com', password: 'Pa3zZ', password_confirmation: 'Pa3zZ'
69
- assert_equal(false, user.valid?)
70
- assert_equal([msg], user.errors.full_messages)
71
- assert_raises(ORMInvalidRecordException) { user.save! }
198
+ test 'password must meet minimum length' do
199
+ user = User.new(
200
+ email: 'bob@microsoft.com',
201
+ password: 'Pa3zZ',
202
+ password_confirmation: 'Pa3zZ'
203
+ )
204
+
205
+ assert user.invalid?
206
+ assert_equal(
207
+ ['Password is too short (minimum is 7 characters)'],
208
+ user.errors.full_messages
209
+ )
72
210
  end
73
211
 
74
- test 'duplicate email validation message is added only once' do
212
+ test "new user can't use existing user's email" do
75
213
  options = {
76
- email: 'test@example.org',
77
- password: 'Test12345',
78
- password_confirmation: 'Test12345',
214
+ email: 'bob@microsoft.com',
215
+ password: 'Password1!',
216
+ password_confirmation: 'Password1!'
79
217
  }
80
- SecureUser.create!(options)
81
- user = SecureUser.new(options)
82
- refute user.valid?
83
- assert_equal DEVISE_ORM == :active_record ? ['Email has already been taken'] : ['Email is already taken'], user.errors.full_messages
84
- end
218
+ User.create!(options)
219
+ user = User.new(options)
85
220
 
86
- test 'password can not equal email for new user' do
87
- msg = 'Password must be different than the email.'
88
- user = User.create email: 'bob@microsoft.com', password: 'bob@microsoft.com', password_confirmation: 'bob@microsoft.com'
89
- refute user.valid?
90
- assert_includes(user.errors.full_messages, msg)
91
- assert_raises(ORMInvalidRecordException) { user.save! }
221
+ assert user.invalid?
222
+ assert_equal(['Email has already been taken'], user.errors.full_messages)
92
223
  end
93
224
 
94
- test 'password can not equal case sensitive version of email for new user' do
95
- msg = 'Password must be different than the email.'
96
- user = User.create email: 'bob@microsoft.com', password: 'BoB@microsoft.com', password_confirmation: 'BoB@microsoft.com'
97
- refute user.valid?
98
- assert_includes(user.errors.full_messages, msg)
99
- assert_raises(ORMInvalidRecordException) { user.save! }
225
+ test "new user can't use existing user's email with different casing" do
226
+ options = {
227
+ email: 'bob@microsoft.com',
228
+ password: 'Password1!',
229
+ password_confirmation: 'Password1!'
230
+ }
231
+ User.create!(options)
232
+ options[:email] = 'BOB@MICROSOFT.COM'
233
+ user = User.new(options)
234
+
235
+ assert user.invalid?
236
+ assert_equal(['Email has already been taken'], user.errors.full_messages)
100
237
  end
101
238
 
102
- test 'password can not equal email with spaces for new user' do
103
- msg = 'Password must be different than the email.'
104
- user = User.create email: 'bob@microsoft.com', password: 'bob@microsoft.com ', password_confirmation: 'bob@microsoft.com '
105
- refute user.valid?
106
- assert_includes(user.errors.full_messages, msg)
107
- assert_raises(ORMInvalidRecordException) { user.save! }
239
+ test 'password cannot equal email for new user' do
240
+ user = User.new(
241
+ email: 'Bob1@microsoft.com',
242
+ password: 'Bob1@microsoft.com',
243
+ password_confirmation: 'Bob1@microsoft.com'
244
+ )
245
+
246
+ assert user.invalid?
247
+ assert_equal(
248
+ ['Password must be different than the email.'],
249
+ user.errors.full_messages
250
+ )
108
251
  end
109
252
 
110
- test 'password can not equal case sensitive version of email with spaces for new user' do
111
- msg = 'Password must be different than the email.'
112
- user = User.create email: 'bob@microsoft.com', password: ' BoB@microsoft.com ', password_confirmation: ' BoB@microsoft.com '
113
- refute user.valid?
114
- assert_includes(user.errors.full_messages, msg)
115
- assert_raises(ORMInvalidRecordException) { user.save! }
253
+ test 'password cannot equal case sensitive version of email for new user' do
254
+ user = User.new(
255
+ email: 'bob1@microsoft.com',
256
+ password: 'BoB1@microsoft.com',
257
+ password_confirmation: 'BoB1@microsoft.com'
258
+ )
259
+
260
+ assert user.invalid?
261
+ assert_equal(
262
+ ['Password must be different than the email.'],
263
+ user.errors.full_messages
264
+ )
116
265
  end
117
266
 
118
- test 'password can not equal email for existing user' do
119
- user = User.create email: 'bob@microsoft.com', password: 'pAs5W0rd!Is5e6Ure', password_confirmation: 'pAs5W0rd!Is5e6Ure'
267
+ test 'password cannot equal email with spaces for new user' do
268
+ user = User.new(
269
+ email: 'Bob1@microsoft.com',
270
+ password: 'Bob1@microsoft.com ',
271
+ password_confirmation: 'Bob1@microsoft.com '
272
+ )
120
273
 
121
- msg = 'Password must be different than the email.'
122
- user.password = 'bob@microsoft.com'
123
- user.password_confirmation = 'bob@microsoft.com'
124
- refute user.valid?
125
- assert_includes(user.errors.full_messages, msg)
126
- assert_raises(ORMInvalidRecordException) { user.save! }
274
+ assert user.invalid?
275
+ assert_equal(
276
+ ['Password must be different than the email.'],
277
+ user.errors.full_messages
278
+ )
127
279
  end
128
280
 
129
- test 'password can not equal case sensitive version of email for existing user' do
130
- user = User.create email: 'bob@microsoft.com', password: 'pAs5W0rd!Is5e6Ure', password_confirmation: 'pAs5W0rd!Is5e6Ure'
281
+ test 'password cannot equal case sensitive version of email with spaces '\
282
+ 'for new user' do
283
+ user = User.new(
284
+ email: 'Bob1@microsoft.com',
285
+ password: ' boB1@microsoft.com ',
286
+ password_confirmation: ' boB1@microsoft.com '
287
+ )
131
288
 
132
- msg = 'Password must be different than the email.'
133
- user.password = 'BoB@microsoft.com'
134
- user.password_confirmation = 'BoB@microsoft.com'
135
- refute user.valid?
136
- assert_includes(user.errors.full_messages, msg)
137
- assert_raises(ORMInvalidRecordException) { user.save! }
289
+ assert user.invalid?
290
+ assert_equal(
291
+ ['Password must be different than the email.'],
292
+ user.errors.full_messages
293
+ )
138
294
  end
139
295
 
140
- test 'password can not equal email with spaces for existing user' do
141
- user = User.create email: 'bob@microsoft.com', password: 'pAs5W0rd!Is5e6Ure', password_confirmation: 'pAs5W0rd!Is5e6Ure'
296
+ test 'new password cannot equal current password' do
297
+ user = User.create(
298
+ email: 'bob@microsoft.com',
299
+ password: 'Password1!',
300
+ password_confirmation: 'Password1!'
301
+ )
302
+
303
+ user.password = 'Password1!'
142
304
 
143
- msg = 'Password must be different than the email.'
144
- user.password = 'bob@microsoft.com '
145
- user.password_confirmation = 'bob@microsoft.com '
146
- refute user.valid?
147
- assert_includes(user.errors.full_messages, msg)
148
- assert_raises(ORMInvalidRecordException) { user.save! }
305
+ assert user.invalid?
306
+ assert_equal(
307
+ ['Password must be different than the current password.'],
308
+ user.errors.full_messages
309
+ )
149
310
  end
150
311
 
151
- test 'password can not equal case sensitive version of email with spaces for existing user' do
152
- user = User.create email: 'bob@microsoft.com', password: 'pAs5W0rd!Is5e6Ure', password_confirmation: 'pAs5W0rd!Is5e6Ure'
312
+ test 'should not be included in objects with invalid API' do
313
+ error = assert_raise RuntimeError do
314
+ class ::Dog; include Devise::Models::SecureValidatable; end
315
+ end
153
316
 
154
- msg = 'Password must be different than the email.'
155
- user.password = ' BoB@microsoft.com '
156
- user.password_confirmation = ' BoB@microsoft.com '
157
- refute user.valid?
158
- assert_includes(user.errors.full_messages, msg)
159
- assert_raises(ORMInvalidRecordException) { user.save! }
317
+ assert_equal('Could not use SecureValidatable on Dog', error.message)
160
318
  end
161
319
  end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestSecureValidatableOverrides < ActiveSupport::TestCase
6
+ class ::CustomClassPasswordValidator < DeviseSecurity::PasswordComplexityValidator
7
+ def patterns
8
+ super.merge(letter: /\p{Alpha}/)
9
+ end
10
+ end
11
+
12
+ class ::CustomInstancePasswordValidator < DeviseSecurity::PasswordComplexityValidator
13
+ # Add a pattern for alphanumeric characters. See
14
+ # [en.yml](file:///./test/dummy/config/locales/en.yml) for translations used in
15
+ # tests.
16
+ def patterns
17
+ super.merge(alnum: /\p{Alnum}/)
18
+ end
19
+ end
20
+
21
+ class User < ApplicationRecord
22
+ devise :database_authenticatable, :secure_validatable
23
+ include ::Mongoid::Mappings if DEVISE_ORM == :mongoid
24
+ end
25
+
26
+ class ClassLevelOverrideUser < User
27
+ self.allow_passwords_equal_to_email = true
28
+ self.email_validation = false
29
+ self.password_complexity = { symbol: 1, letter: 1 }
30
+ self.password_complexity_validator = 'custom_class_password_validator'
31
+ self.password_length = 10..100
32
+ end
33
+
34
+ class InstanceLevelOverrideUser < ClassLevelOverrideUser
35
+ def allow_passwords_equal_to_email
36
+ true
37
+ end
38
+
39
+ def email_validation
40
+ false
41
+ end
42
+
43
+ def password_complexity
44
+ { symbol: 2, alnum: 1 }
45
+ end
46
+
47
+ def password_length
48
+ 11..100
49
+ end
50
+
51
+ def password_complexity_validator
52
+ 'CustomInstancePasswordValidator'
53
+ end
54
+ end
55
+
56
+ test 'email equal to password can be overridden at the class level' do
57
+ user = ClassLevelOverrideUser.new(
58
+ email: 'bob1!@microsoft.com',
59
+ password: 'bob1!@microsoft.com',
60
+ password_confirmation: 'bob1!@microsoft.com'
61
+ )
62
+
63
+ assert user.valid?
64
+ end
65
+
66
+ test 'email equal to password can be overridden at the instance level' do
67
+ user = InstanceLevelOverrideUser.new(
68
+ email: 'bob1!@microsoft.com',
69
+ password: 'bob1!@microsoft.com',
70
+ password_confirmation: 'bob1!@microsoft.com'
71
+ )
72
+
73
+ assert user.valid?
74
+ end
75
+
76
+ test 'email validation can be overridden at the class level' do
77
+ user = ClassLevelOverrideUser.new(
78
+ email: 'bob1!@f.com',
79
+ password: 'Pa3zZ1!!aaaaaa',
80
+ password_confirmation: 'Pa3zZ1!!aaaaaa'
81
+ )
82
+
83
+ assert user.valid?
84
+ end
85
+
86
+ test 'email validation can be overridden at the instance level' do
87
+ user = InstanceLevelOverrideUser.new(
88
+ email: 'bob1!@f.com',
89
+ password: 'Pa3zZ1!!aaaaaa',
90
+ password_confirmation: 'Pa3zZ1!!aaaaaa'
91
+ )
92
+
93
+ assert user.valid?
94
+ end
95
+
96
+ test 'password complexity can be overridden at the class level' do
97
+ user = ClassLevelOverrideUser.new(
98
+ email: 'bob@microsoft.com',
99
+ password: 'PASSwordddd',
100
+ password_confirmation: 'PASSwordddd'
101
+ )
102
+
103
+ assert user.invalid?
104
+ assert_equal(
105
+ ['Password must contain at least one punctuation mark or symbol'],
106
+ user.errors.full_messages
107
+ )
108
+ end
109
+
110
+ test 'password complexity can be overridden at the instance level' do
111
+ user = InstanceLevelOverrideUser.new(
112
+ email: 'bob@microsoft.com',
113
+ password: 'PASSwordddd',
114
+ password_confirmation: 'PASSwordddd'
115
+ )
116
+
117
+ assert user.invalid?
118
+ assert_equal(
119
+ ['Password must contain at least 2 punctuation marks or symbols'],
120
+ user.errors.full_messages
121
+ )
122
+ end
123
+
124
+ test 'password length can be overridden at the class level' do
125
+ user = ClassLevelOverrideUser.new(
126
+ email: 'bob@microsoft.com',
127
+ password: 'Pa3zZ1!',
128
+ password_confirmation: 'Pa3zZ1!'
129
+ )
130
+
131
+ assert user.invalid?
132
+ assert_equal(
133
+ ['Password is too short (minimum is 10 characters)'],
134
+ user.errors.full_messages
135
+ )
136
+ end
137
+
138
+ test 'password length can be overridden at the instance level' do
139
+ user = InstanceLevelOverrideUser.new(
140
+ email: 'bob@microsoft.com',
141
+ password: 'Pa3zZ1!!',
142
+ password_confirmation: 'Pa3zZ1!!'
143
+ )
144
+
145
+ assert user.invalid?
146
+ assert_equal(
147
+ ['Password is too short (minimum is 11 characters)'],
148
+ user.errors.full_messages
149
+ )
150
+ end
151
+
152
+ test 'password validator can be overridden at the instance level' do
153
+ password = '!' * 11 # 11 characters, all symbols
154
+ user = InstanceLevelOverrideUser.new(
155
+ email: 'bob@microsoft.com',
156
+ password: password,
157
+ password_confirmation: password
158
+ )
159
+
160
+ assert user.invalid?
161
+ # This validation error only occurs when the CustomInstancePasswordValidator
162
+ # is used.
163
+ assert_equal(
164
+ ['Password must contain at least one letter or number'],
165
+ user.errors.full_messages
166
+ )
167
+ end
168
+
169
+ test 'password validator can be overridden at the class level' do
170
+ password = '!' * 10 # 10 characters, all symbols
171
+ user = ClassLevelOverrideUser.new(
172
+ email: 'bob@microsoft.com',
173
+ password: password,
174
+ password_confirmation: password
175
+ )
176
+
177
+ assert user.invalid?
178
+ # This validation error only occurs when the CustomClassPasswordValidator
179
+ # is used.
180
+ assert_equal(
181
+ ['Password must contain at least one letter'],
182
+ user.errors.full_messages
183
+ )
184
+ end
185
+ end
@@ -10,15 +10,15 @@ class TestSessionLimitable < ActiveSupport::TestCase
10
10
  end
11
11
 
12
12
  test 'check is not skipped by default' do
13
- user = User.create email: 'bob@microsoft.com', password: 'password1', password_confirmation: 'password1'
14
- assert_equal(false, user.skip_session_limitable?)
13
+ user = User.new(email: 'bob@microsoft.com', password: 'password1', password_confirmation: 'password1')
14
+ assert_not(user.skip_session_limitable?)
15
15
  end
16
16
 
17
17
  test 'default check can be overridden by record instance' do
18
- modified_user = ModifiedUser.create email: 'bob2@microsoft.com', password: 'password1', password_confirmation: 'password1'
19
- assert_equal(true, modified_user.skip_session_limitable?)
18
+ modified_user = ModifiedUser.new(email: 'bob2@microsoft.com', password: 'password1', password_confirmation: 'password1')
19
+ assert(modified_user.skip_session_limitable?)
20
20
  end
21
-
21
+
22
22
  class SessionLimitableUser < User
23
23
  devise :session_limitable
24
24
  include ::Mongoid::Mappings if DEVISE_ORM == :mongoid
@@ -34,7 +34,7 @@ class TestSessionLimitable < ActiveSupport::TestCase
34
34
  assert_nil user.unique_session_id
35
35
  user.update_unique_session_id!('unique_value')
36
36
  user.reload
37
- assert_equal user.unique_session_id, 'unique_value'
37
+ assert_equal('unique_value', user.unique_session_id)
38
38
  end
39
39
 
40
40
  test '#update_unique_session_id!(value) updates invalid record atomically' do
@@ -45,13 +45,13 @@ class TestSessionLimitable < ActiveSupport::TestCase
45
45
  assert_nil user.unique_session_id
46
46
  user.update_unique_session_id!('unique_value')
47
47
  user.reload
48
- assert_equal user.email, 'bob@microsoft.com'
49
- assert_equal user.unique_session_id, 'unique_value'
48
+ assert_equal('bob@microsoft.com', user.email)
49
+ assert_equal('unique_value', user.unique_session_id)
50
50
  end
51
51
 
52
52
  test '#update_unique_session_id!(value) raises an exception on an unpersisted record' do
53
53
  user = User.create
54
- assert !user.persisted?
54
+ assert_not user.persisted?
55
55
  assert_raises(Devise::Models::Compatibility::NotPersistedError) { user.update_unique_session_id!('unique_value') }
56
56
  end
57
57
  end
@@ -7,7 +7,9 @@ Devise.setup do |config|
7
7
  # Should the password expire (e.g 3.months)
8
8
  # config.expire_password_after = false
9
9
 
10
- # Need 1 char of A-Z, a-z and 0-9
10
+ # Need 1 char each of: A-Z, a-z, 0-9, and a punctuation mark or symbol
11
+ # You may use "digits" in place of "digit" and "symbols" in place of
12
+ # "symbol" based on your preference
11
13
  # config.password_complexity = { digit: 1, lower: 1, symbol: 1, upper: 1 }
12
14
 
13
15
  # How many passwords to keep in archive
@@ -44,4 +46,7 @@ Devise.setup do |config|
44
46
 
45
47
  # Allow password to equal the email
46
48
  # config.allow_passwords_equal_to_email = false
49
+
50
+ # paranoid_verification will regenerate verification code after failed attempt
51
+ # config.paranoid_code_regenerate_after_attempt = 10
47
52
  end