devise-security 0.15.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.
- checksums.yaml +4 -4
- data/LICENSE.txt +3 -1
- data/README.md +43 -24
- data/app/controllers/devise/paranoid_verification_code_controller.rb +26 -12
- data/app/controllers/devise/password_expired_controller.rb +23 -10
- data/config/locales/bg.yml +42 -0
- data/config/locales/by.yml +2 -0
- data/config/locales/cs.yml +6 -0
- data/config/locales/de.yml +4 -0
- data/config/locales/en.yml +3 -1
- data/config/locales/es.yml +13 -0
- data/config/locales/fa.yml +2 -0
- data/config/locales/fr.yml +15 -2
- data/config/locales/hi.yml +22 -20
- data/config/locales/it.yml +2 -0
- data/config/locales/ja.yml +13 -0
- data/config/locales/nl.yml +2 -0
- data/config/locales/pt.yml +2 -0
- data/config/locales/ru.yml +2 -0
- data/config/locales/tr.yml +26 -1
- data/config/locales/uk.yml +2 -0
- data/config/locales/zh_CN.yml +2 -0
- data/config/locales/zh_TW.yml +2 -0
- data/lib/devise-security/controllers/helpers.rb +25 -13
- data/lib/devise-security/hooks/expirable.rb +3 -3
- data/lib/devise-security/hooks/paranoid_verification.rb +1 -3
- data/lib/devise-security/hooks/password_expirable.rb +1 -3
- data/lib/devise-security/hooks/session_limitable.rb +10 -6
- data/lib/devise-security/models/compatibility/active_record_patch.rb +4 -3
- data/lib/devise-security/models/compatibility/mongoid_patch.rb +3 -2
- data/lib/devise-security/models/database_authenticatable_patch.rb +18 -10
- data/lib/devise-security/models/expirable.rb +6 -5
- data/lib/devise-security/models/paranoid_verification.rb +2 -2
- data/lib/devise-security/models/password_archivable.rb +3 -3
- data/lib/devise-security/models/secure_validatable.rb +62 -11
- data/lib/devise-security/orm/mongoid.rb +1 -1
- data/lib/devise-security/patches.rb +14 -8
- data/lib/devise-security/routes.rb +2 -3
- data/lib/devise-security/validators/password_complexity_validator.rb +53 -26
- data/lib/devise-security/version.rb +1 -1
- data/lib/devise-security.rb +15 -6
- data/lib/generators/devise_security/install_generator.rb +4 -6
- data/{test/tmp/config/initializers/devise-security.rb → lib/generators/templates/devise_security.rb} +9 -1
- data/test/controllers/test_paranoid_verification_code_controller.rb +133 -0
- data/test/controllers/test_password_expired_controller.rb +122 -99
- data/test/controllers/test_security_question_controller.rb +19 -37
- data/test/dummy/app/controllers/overrides/paranoid_verification_code_controller.rb +7 -0
- data/test/dummy/app/controllers/overrides/password_expired_controller.rb +17 -0
- data/test/dummy/app/controllers/widgets_controller.rb +3 -0
- data/test/dummy/app/models/application_user_record.rb +2 -1
- data/test/dummy/app/models/mongoid/confirmable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/database_authenticable_fields.rb +4 -3
- data/test/dummy/app/models/mongoid/expirable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/lockable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/mappings.rb +4 -2
- data/test/dummy/app/models/mongoid/omniauthable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/paranoid_verification_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/password_archivable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/password_expirable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/recoverable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/registerable_fields.rb +4 -2
- data/test/dummy/app/models/mongoid/rememberable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/secure_validatable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/security_questionable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/session_limitable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/timeoutable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/trackable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/validatable_fields.rb +2 -0
- data/test/dummy/app/models/paranoid_verification_user.rb +26 -0
- data/test/dummy/app/models/password_expired_user.rb +26 -0
- data/test/dummy/app/models/user.rb +5 -5
- data/test/dummy/app/models/widget.rb +1 -3
- data/test/dummy/app/mongoid/one_user.rb +5 -5
- data/test/dummy/app/mongoid/user_on_engine.rb +2 -2
- data/test/dummy/app/mongoid/user_on_main_app.rb +2 -2
- data/test/dummy/app/mongoid/user_with_validations.rb +3 -3
- data/test/dummy/app/mongoid/user_without_email.rb +7 -4
- data/test/dummy/config/application.rb +3 -7
- data/test/dummy/config/boot.rb +1 -1
- data/test/dummy/config/environment.rb +1 -1
- data/test/dummy/config/environments/test.rb +4 -13
- data/test/dummy/config/initializers/devise.rb +1 -5
- data/test/dummy/config/initializers/migration_class.rb +1 -8
- data/test/dummy/config/locales/en.yml +10 -0
- data/test/dummy/config/mongoid.yml +1 -1
- data/test/dummy/config/routes.rb +3 -1
- data/test/dummy/config.ru +1 -1
- data/test/dummy/db/migrate/20120508165529_create_tables.rb +5 -5
- data/test/dummy/lib/shared_expirable_columns.rb +1 -0
- data/test/dummy/lib/shared_security_questions_fields.rb +1 -0
- data/test/dummy/lib/shared_user.rb +17 -6
- data/test/dummy/lib/shared_user_without_omniauth.rb +12 -3
- data/test/dummy/lib/shared_verification_fields.rb +1 -0
- data/test/dummy/log/test.log +44592 -1151
- data/test/i18n_test.rb +22 -0
- data/test/integration/test_paranoid_verification_code_workflow.rb +53 -0
- data/test/integration/test_password_expirable_workflow.rb +2 -6
- data/test/integration/test_session_limitable_workflow.rb +5 -3
- data/test/orm/active_record.rb +7 -7
- data/test/orm/mongoid.rb +2 -1
- data/test/support/integration_helpers.rb +10 -22
- data/test/support/mongoid.yml +1 -1
- data/test/test_compatibility.rb +2 -0
- data/test/test_complexity_validator.rb +247 -37
- data/test/test_database_authenticatable_patch.rb +146 -0
- data/test/test_helper.rb +11 -12
- data/test/test_install_generator.rb +2 -2
- data/test/test_paranoid_verification.rb +8 -9
- data/test/test_password_archivable.rb +34 -11
- data/test/test_password_expirable.rb +27 -27
- data/test/test_secure_validatable.rb +284 -50
- data/test/test_secure_validatable_overrides.rb +185 -0
- data/test/test_session_limitable.rb +9 -9
- data/{lib/generators/templates/devise-security.rb → test/tmp/config/initializers/devise_security.rb} +9 -1
- data/test/tmp/config/locales/devise.security_extension.by.yml +50 -0
- data/test/tmp/config/locales/devise.security_extension.cs.yml +46 -0
- data/test/tmp/config/locales/devise.security_extension.de.yml +4 -0
- data/test/tmp/config/locales/devise.security_extension.en.yml +3 -1
- data/test/tmp/config/locales/devise.security_extension.es.yml +22 -9
- data/test/tmp/config/locales/devise.security_extension.fa.yml +2 -0
- data/test/tmp/config/locales/devise.security_extension.fr.yml +15 -2
- data/test/tmp/config/locales/devise.security_extension.hi.yml +43 -0
- data/test/tmp/config/locales/devise.security_extension.it.yml +2 -0
- data/test/tmp/config/locales/devise.security_extension.ja.yml +13 -0
- data/test/tmp/config/locales/devise.security_extension.nl.yml +2 -0
- data/test/tmp/config/locales/devise.security_extension.pt.yml +2 -0
- data/test/tmp/config/locales/devise.security_extension.ru.yml +2 -0
- data/test/tmp/config/locales/devise.security_extension.tr.yml +26 -1
- data/test/tmp/config/locales/devise.security_extension.uk.yml +2 -0
- data/test/tmp/config/locales/devise.security_extension.zh_CN.yml +2 -0
- data/test/tmp/config/locales/devise.security_extension.zh_TW.yml +42 -0
- metadata +65 -45
- data/lib/devise-security/orm/active_record.rb +0 -20
- data/lib/devise-security/patches/confirmations_controller_captcha.rb +0 -23
- data/lib/devise-security/patches/confirmations_controller_security_question.rb +0 -26
- data/lib/devise-security/patches/passwords_controller_captcha.rb +0 -22
- data/lib/devise-security/patches/passwords_controller_security_question.rb +0 -25
- data/lib/devise-security/patches/registrations_controller_captcha.rb +0 -35
- data/lib/devise-security/patches/sessions_controller_captcha.rb +0 -26
- data/lib/devise-security/patches/unlocks_controller_captcha.rb +0 -22
- data/lib/devise-security/patches/unlocks_controller_security_question.rb +0 -25
- data/lib/devise-security/schema.rb +0 -66
- data/test/dummy/app/controllers/foos_controller.rb +0 -0
- data/test/dummy/app/models/secure_user.rb +0 -9
- data/test/dummy/lib/shared_user_without_email.rb +0 -28
@@ -1,85 +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, :
|
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
|
-
|
14
|
-
|
15
|
-
user = User.create password: 'passWord1', password_confirmation: 'passWord1'
|
11
|
+
class EmailNotRequiredUser < User
|
12
|
+
protected
|
16
13
|
|
17
|
-
|
18
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
212
|
+
test "new user can't use existing user's email" do
|
75
213
|
options = {
|
76
|
-
email: '
|
77
|
-
password: '
|
78
|
-
password_confirmation: '
|
214
|
+
email: 'bob@microsoft.com',
|
215
|
+
password: 'Password1!',
|
216
|
+
password_confirmation: 'Password1!'
|
79
217
|
}
|
80
|
-
|
81
|
-
user =
|
82
|
-
|
83
|
-
|
218
|
+
User.create!(options)
|
219
|
+
user = User.new(options)
|
220
|
+
|
221
|
+
assert user.invalid?
|
222
|
+
assert_equal(['Email has already been taken'], user.errors.full_messages)
|
223
|
+
end
|
224
|
+
|
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)
|
237
|
+
end
|
238
|
+
|
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
|
+
)
|
251
|
+
end
|
252
|
+
|
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
|
+
)
|
265
|
+
end
|
266
|
+
|
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
|
+
)
|
273
|
+
|
274
|
+
assert user.invalid?
|
275
|
+
assert_equal(
|
276
|
+
['Password must be different than the email.'],
|
277
|
+
user.errors.full_messages
|
278
|
+
)
|
279
|
+
end
|
280
|
+
|
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
|
+
)
|
288
|
+
|
289
|
+
assert user.invalid?
|
290
|
+
assert_equal(
|
291
|
+
['Password must be different than the email.'],
|
292
|
+
user.errors.full_messages
|
293
|
+
)
|
294
|
+
end
|
295
|
+
|
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!'
|
304
|
+
|
305
|
+
assert user.invalid?
|
306
|
+
assert_equal(
|
307
|
+
['Password must be different than the current password.'],
|
308
|
+
user.errors.full_messages
|
309
|
+
)
|
310
|
+
end
|
311
|
+
|
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
|
316
|
+
|
317
|
+
assert_equal('Could not use SecureValidatable on Dog', error.message)
|
84
318
|
end
|
85
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.
|
14
|
-
|
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.
|
19
|
-
|
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
|
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
|
49
|
-
assert_equal user.unique_session_id
|
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
|
-
|
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
|
data/{lib/generators/templates/devise-security.rb → test/tmp/config/initializers/devise_security.rb}
RENAMED
@@ -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
|
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
|
@@ -41,4 +43,10 @@ Devise.setup do |config|
|
|
41
43
|
|
42
44
|
# Time period for account expiry from last_activity_at
|
43
45
|
# config.expire_after = 90.days
|
46
|
+
|
47
|
+
# Allow password to equal the email
|
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
|
44
52
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
by:
|
2
|
+
errors:
|
3
|
+
messages:
|
4
|
+
taken_in_past: 'ужо раней выкарыстоўваўся.'
|
5
|
+
equal_to_current_password: 'павінен адрознівацца ад сучаснага пароля.'
|
6
|
+
equal_to_email: 'павінна адрознівацца ад электроннай пошты.'
|
7
|
+
password_complexity:
|
8
|
+
digit:
|
9
|
+
one: 'павінен утрымліваць хоць адну лічбу'
|
10
|
+
few: 'павінен утрымліваць хоць %{count} лічбы'
|
11
|
+
many: 'павінен утрымліваць хоць %{count} лічбы'
|
12
|
+
other: 'павінен утрымліваць хоць %{count} лічбы'
|
13
|
+
lower:
|
14
|
+
one: 'павінен утрымліваць хоць адну маленькую літару'
|
15
|
+
few: 'павінен утрымліваць хоць %{count} малыx літары'
|
16
|
+
many: 'павінен утрымліваць хоць %{count} малыx літары'
|
17
|
+
other: 'павінен утрымліваць хоць %{count} малыx літары'
|
18
|
+
symbol:
|
19
|
+
one: 'павінен утрымліваць хоць адзін знак пунктуацыі або сімвал'
|
20
|
+
few: 'павінен утрымліваць хоць %{count} знака пунктуацыі або сімвала'
|
21
|
+
many: 'павінен утрымліваць хоць %{count} знака пунктуацыі або сімвала'
|
22
|
+
other: 'павінен утрымліваць хоць %{count} знака пунктуацыі або сімвала'
|
23
|
+
upper:
|
24
|
+
one: 'павінен утрымліваць хоць адну вялікую літару'
|
25
|
+
few: 'павінен утрымліваць хоць %{count} вялікіx літары'
|
26
|
+
many: 'павінен утрымліваць хоць %{count} вялікіx літары'
|
27
|
+
other: 'павінен утрымліваць хоць %{count} вялікіx літары'
|
28
|
+
devise:
|
29
|
+
invalid_captcha: 'Уведзены няправільны код капчы.'
|
30
|
+
invalid_security_question: 'Адказ на сакрэтнае пытанне быў няправільны.'
|
31
|
+
paranoid_verify:
|
32
|
+
code_required: 'Калі ласка, увядзіце код, атрыманы ад нашай каманды падтрымкі'
|
33
|
+
paranoid_verification_code:
|
34
|
+
updated: Код спраўджання прыняты
|
35
|
+
show:
|
36
|
+
submit_verification_code: 'Увод кода пацверджання'
|
37
|
+
verification_code: 'Код пацверджання'
|
38
|
+
submit: 'Адправіць'
|
39
|
+
password_expired:
|
40
|
+
updated: 'Ваш новы пароль захаваны.'
|
41
|
+
change_required: 'Ваш пароль састарэў. Калі ласка, усталюйце новы.'
|
42
|
+
show:
|
43
|
+
renew_your_password: 'Змена пароля'
|
44
|
+
current_password: 'Сучасны пароль'
|
45
|
+
new_password: 'Новы пароль'
|
46
|
+
new_password_confirmation: 'Пацвердзіце новы пароль'
|
47
|
+
change_my_password: 'Змяніць пароль'
|
48
|
+
failure:
|
49
|
+
session_limited: 'Вашы параметры ўваходу выкарыстоўваюцца ў іншым браўзэры. Калі ласка, аўтарызуйцеся зноў, каб працягнуць у гэтым браўзэры.'
|
50
|
+
expired: 'Ваш уліковы запіс састарэў з-за неактыўнасці. Калі ласка, звяжыцеся з адміністратарам.'
|