devise-security 0.12.0 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +3 -1
- data/README.md +199 -65
- data/app/controllers/devise/paranoid_verification_code_controller.rb +28 -12
- data/app/controllers/devise/password_expired_controller.rb +34 -10
- data/app/views/devise/paranoid_verification_code/show.html.erb +4 -4
- data/app/views/devise/password_expired/show.html.erb +6 -6
- data/config/locales/bg.yml +42 -0
- data/config/locales/by.yml +50 -0
- data/config/locales/cs.yml +46 -0
- data/config/locales/de.yml +33 -7
- data/config/locales/en.yml +26 -1
- data/config/locales/es.yml +31 -6
- data/config/locales/fa.yml +42 -0
- data/config/locales/fr.yml +42 -0
- data/config/locales/hi.yml +43 -0
- data/config/locales/it.yml +36 -4
- data/config/locales/ja.yml +42 -0
- data/config/locales/nl.yml +42 -0
- data/config/locales/pt.yml +42 -0
- data/config/locales/ru.yml +50 -0
- data/config/locales/tr.yml +42 -0
- data/config/locales/uk.yml +50 -0
- data/config/locales/zh_CN.yml +42 -0
- data/config/locales/zh_TW.yml +42 -0
- data/lib/devise-security/controllers/helpers.rb +74 -51
- data/lib/devise-security/hooks/expirable.rb +6 -4
- data/lib/devise-security/hooks/paranoid_verification.rb +3 -3
- data/lib/devise-security/hooks/password_expirable.rb +5 -3
- data/lib/devise-security/hooks/session_limitable.rb +31 -14
- data/lib/devise-security/models/active_record/old_password.rb +5 -0
- data/lib/devise-security/models/compatibility/active_record_patch.rb +41 -0
- data/lib/devise-security/models/compatibility/mongoid_patch.rb +32 -0
- data/lib/devise-security/models/compatibility.rb +8 -15
- data/lib/devise-security/models/database_authenticatable_patch.rb +20 -10
- data/lib/devise-security/models/expirable.rb +14 -7
- data/lib/devise-security/models/mongoid/old_password.rb +21 -0
- data/lib/devise-security/models/paranoid_verification.rb +4 -2
- data/lib/devise-security/models/password_archivable.rb +19 -8
- data/lib/devise-security/models/password_expirable.rb +103 -48
- data/lib/devise-security/models/secure_validatable.rb +69 -12
- data/lib/devise-security/models/security_questionable.rb +2 -0
- data/lib/devise-security/models/session_limitable.rb +19 -2
- data/lib/devise-security/orm/mongoid.rb +7 -0
- data/lib/devise-security/patches/controller_captcha.rb +2 -0
- data/lib/devise-security/patches/controller_security_question.rb +3 -1
- data/lib/devise-security/patches.rb +16 -8
- data/lib/devise-security/rails.rb +2 -0
- data/lib/devise-security/routes.rb +4 -3
- data/lib/devise-security/validators/password_complexity_validator.rb +62 -0
- data/lib/devise-security/version.rb +3 -1
- data/lib/devise-security.rb +23 -11
- data/lib/generators/devise_security/install_generator.rb +6 -6
- data/lib/generators/templates/devise_security.rb +52 -0
- data/test/{test_captcha_controller.rb → controllers/test_captcha_controller.rb} +2 -0
- data/test/controllers/test_paranoid_verification_code_controller.rb +133 -0
- data/test/controllers/test_password_expired_controller.rb +164 -0
- data/test/controllers/test_security_question_controller.rb +66 -0
- data/test/dummy/Rakefile +3 -1
- data/test/dummy/app/assets/config/manifest.js +3 -0
- data/test/dummy/app/controllers/application_controller.rb +2 -0
- data/test/dummy/app/controllers/captcha/sessions_controller.rb +2 -0
- 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/security_question/unlocks_controller.rb +2 -0
- data/test/dummy/app/controllers/widgets_controller.rb +9 -0
- data/test/dummy/app/models/application_record.rb +10 -2
- data/test/dummy/app/models/application_user_record.rb +12 -0
- data/test/dummy/app/models/captcha_user.rb +7 -2
- data/test/dummy/app/models/mongoid/confirmable_fields.rb +15 -0
- data/test/dummy/app/models/mongoid/database_authenticable_fields.rb +18 -0
- data/test/dummy/app/models/mongoid/expirable_fields.rb +13 -0
- data/test/dummy/app/models/mongoid/lockable_fields.rb +15 -0
- data/test/dummy/app/models/mongoid/mappings.rb +15 -0
- data/test/dummy/app/models/mongoid/omniauthable_fields.rb +13 -0
- data/test/dummy/app/models/mongoid/paranoid_verification_fields.rb +12 -0
- data/test/dummy/app/models/mongoid/password_archivable_fields.rb +11 -0
- data/test/dummy/app/models/mongoid/password_expirable_fields.rb +12 -0
- data/test/dummy/app/models/mongoid/recoverable_fields.rb +13 -0
- data/test/dummy/app/models/mongoid/registerable_fields.rb +21 -0
- data/test/dummy/app/models/mongoid/rememberable_fields.rb +12 -0
- data/test/dummy/app/models/mongoid/secure_validatable_fields.rb +13 -0
- data/test/dummy/app/models/mongoid/security_questionable_fields.rb +15 -0
- data/test/dummy/app/models/mongoid/session_limitable_fields.rb +12 -0
- data/test/dummy/app/models/mongoid/timeoutable_fields.rb +11 -0
- data/test/dummy/app/models/mongoid/trackable_fields.rb +16 -0
- data/test/dummy/app/models/mongoid/validatable_fields.rb +9 -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/security_question_user.rb +9 -4
- data/test/dummy/app/models/user.rb +16 -1
- data/test/dummy/app/models/widget.rb +4 -0
- data/test/dummy/app/mongoid/admin.rb +31 -0
- data/test/dummy/app/mongoid/one_user.rb +58 -0
- data/test/dummy/app/mongoid/shim.rb +25 -0
- data/test/dummy/app/mongoid/user_on_engine.rb +41 -0
- data/test/dummy/app/mongoid/user_on_main_app.rb +41 -0
- data/test/dummy/app/mongoid/user_with_validations.rb +37 -0
- data/test/dummy/app/mongoid/user_without_email.rb +38 -0
- data/test/dummy/config/application.rb +13 -11
- data/test/dummy/config/boot.rb +3 -1
- data/test/dummy/config/environment.rb +3 -1
- data/test/dummy/config/environments/test.rb +6 -13
- data/test/dummy/config/initializers/devise.rb +6 -3
- data/test/dummy/config/initializers/migration_class.rb +3 -6
- data/test/dummy/config/locales/en.yml +10 -0
- data/test/dummy/config/mongoid.yml +6 -0
- data/test/dummy/config/routes.rb +8 -3
- data/test/dummy/config.ru +3 -1
- data/test/dummy/db/migrate/20120508165529_create_tables.rb +17 -6
- data/test/dummy/db/migrate/20150402165590_add_verification_columns.rb +2 -0
- data/test/dummy/db/migrate/20150407162345_add_verification_attempt_column.rb +2 -0
- data/test/dummy/db/migrate/20160320162345_add_security_questions_fields.rb +2 -0
- data/test/dummy/db/migrate/20180318103603_add_expireable_columns.rb +2 -0
- data/test/dummy/db/migrate/20180318105329_add_confirmable_columns.rb +2 -0
- data/test/dummy/db/migrate/20180318105732_add_rememberable_columns.rb +2 -0
- data/test/dummy/db/migrate/20180318111336_add_recoverable_columns.rb +2 -0
- data/test/dummy/db/migrate/20180319114023_add_widget.rb +2 -0
- data/test/dummy/lib/shared_expirable_columns.rb +15 -0
- data/test/dummy/lib/shared_security_questions_fields.rb +17 -0
- data/test/dummy/lib/shared_user.rb +43 -0
- data/test/dummy/lib/shared_user_with_password_verification.rb +13 -0
- data/test/dummy/lib/shared_user_without_omniauth.rb +24 -0
- data/test/dummy/lib/shared_verification_fields.rb +16 -0
- data/test/dummy/log/test.log +45240 -0
- 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 +53 -0
- data/test/integration/test_session_limitable_workflow.rb +69 -0
- data/test/orm/active_record.rb +15 -0
- data/test/orm/mongoid.rb +13 -0
- data/test/support/integration_helpers.rb +35 -0
- data/test/support/mongoid.yml +6 -0
- data/test/test_compatibility.rb +15 -0
- data/test/test_complexity_validator.rb +282 -0
- data/test/test_database_authenticatable_patch.rb +146 -0
- data/test/test_helper.rb +41 -9
- data/test/test_install_generator.rb +20 -3
- data/test/test_paranoid_verification.rb +10 -9
- data/test/test_password_archivable.rb +37 -13
- data/test/test_password_expirable.rb +72 -9
- data/test/test_secure_validatable.rb +289 -55
- data/test/test_secure_validatable_overrides.rb +185 -0
- data/test/test_session_limitable.rb +57 -0
- data/test/tmp/config/initializers/devise_security.rb +52 -0
- 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 +42 -0
- data/test/tmp/config/locales/devise.security_extension.en.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.es.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.fa.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.fr.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.hi.yml +43 -0
- data/test/tmp/config/locales/devise.security_extension.it.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.ja.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.nl.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.pt.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.ru.yml +50 -0
- data/test/tmp/config/locales/devise.security_extension.tr.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.uk.yml +50 -0
- data/test/tmp/config/locales/devise.security_extension.zh_CN.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.zh_TW.yml +42 -0
- metadata +290 -124
- data/.circleci/config.yml +0 -41
- data/.document +0 -5
- data/.gitignore +0 -40
- data/.rubocop.yml +0 -63
- data/.ruby-version +0 -1
- data/.travis.yml +0 -25
- data/Appraisals +0 -19
- data/Gemfile +0 -3
- data/Rakefile +0 -28
- data/devise-security.gemspec +0 -44
- data/gemfiles/rails_4.1_stable.gemfile +0 -8
- data/gemfiles/rails_4.2_stable.gemfile +0 -8
- data/gemfiles/rails_5.0_stable.gemfile +0 -8
- data/gemfiles/rails_5.1_stable.gemfile +0 -8
- data/gemfiles/rails_5.2_rc1.gemfile +0 -8
- data/lib/devise-security/models/old_password.rb +0 -4
- data/lib/devise-security/orm/active_record.rb +0 -18
- data/lib/devise-security/patches/confirmations_controller_captcha.rb +0 -21
- data/lib/devise-security/patches/confirmations_controller_security_question.rb +0 -24
- data/lib/devise-security/patches/passwords_controller_captcha.rb +0 -20
- data/lib/devise-security/patches/passwords_controller_security_question.rb +0 -23
- data/lib/devise-security/patches/registrations_controller_captcha.rb +0 -33
- data/lib/devise-security/patches/sessions_controller_captcha.rb +0 -24
- data/lib/devise-security/patches/unlocks_controller_captcha.rb +0 -20
- data/lib/devise-security/patches/unlocks_controller_security_question.rb +0 -23
- data/lib/devise-security/schema.rb +0 -64
- data/lib/generators/templates/devise-security.rb +0 -38
- data/test/dummy/app/controllers/foos_controller.rb +0 -0
- data/test/dummy/app/models/.gitkeep +0 -0
- data/test/dummy/app/models/secure_user.rb +0 -3
- data/test/test_password_expired_controller.rb +0 -44
- data/test/test_security_question_controller.rb +0 -84
@@ -0,0 +1,50 @@
|
|
1
|
+
uk:
|
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} малі букви'
|
16
|
+
many: 'повинен включати хоча б %{count} малих букв'
|
17
|
+
other: 'повинен включати хоча б %{count} малі букви'
|
18
|
+
symbol:
|
19
|
+
one: 'повинен включати хоча б один розділовий знак або символ'
|
20
|
+
few: 'повинен включати хоча б %{count} розділові знаки або символи'
|
21
|
+
many: 'повинен включати хоча б %{count} розділових знаків або символів'
|
22
|
+
other: 'повинен включати хоча б %{count} розділові знаки або символи'
|
23
|
+
upper:
|
24
|
+
one: 'повинен включати хоча б одну прописну букву'
|
25
|
+
few: 'повинен включати хоча б %{count} прописні букви'
|
26
|
+
many: 'повинен включати хоча б %{count} прописних букв'
|
27
|
+
other: 'повинен включати хоча б %{count} прописні букви'
|
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: "Ваш аккаунт застарів через неактивність. Будь ласка, зв'яжіться з адміністратором."
|
@@ -0,0 +1,42 @@
|
|
1
|
+
zh_CN:
|
2
|
+
errors:
|
3
|
+
messages:
|
4
|
+
taken_in_past: '曾被使用过。'
|
5
|
+
equal_to_current_password: '必须与当前密码不同。'
|
6
|
+
equal_to_email: '必须与电子邮件地址不同。'
|
7
|
+
password_complexity:
|
8
|
+
digit:
|
9
|
+
one: 必须包含至少1个数字
|
10
|
+
other: 必须包含至少%{count}个数字
|
11
|
+
lower:
|
12
|
+
one: 必须包含至少1个小写字母
|
13
|
+
other: 必须包含至少%{count}个小写字母
|
14
|
+
symbol:
|
15
|
+
one: 必须包含至少1个特殊符号(!"#$%&'()*+,-./:;<=>?@[\]^_‘{|}~)
|
16
|
+
other: 必须包含至少%{count}个特殊符号(!"#$%&'()*+,-./:;<=>?@[\]^_‘{|}~)
|
17
|
+
upper:
|
18
|
+
one: 必须包含至少1个大写字母
|
19
|
+
other: 必须包含至少%{count}个大写字母
|
20
|
+
devise:
|
21
|
+
invalid_captcha: '验证码无效。'
|
22
|
+
invalid_security_question: '安全问题答案无效。'
|
23
|
+
paranoid_verify:
|
24
|
+
code_required: '请输入我们支持团队提供的代码'
|
25
|
+
paranoid_verification_code:
|
26
|
+
updated: 接受验证码
|
27
|
+
show:
|
28
|
+
submit_verification_code: 提交验证码
|
29
|
+
verification_code: 验证码
|
30
|
+
submit: 提交
|
31
|
+
password_expired:
|
32
|
+
updated: '你的新密码已保存。'
|
33
|
+
change_required: '你的密码已过期,请更新密码。'
|
34
|
+
show:
|
35
|
+
renew_your_password: 更新你的密码
|
36
|
+
current_password: 当前密码
|
37
|
+
new_password: 新密码
|
38
|
+
new_password_confirmation: 确认新密码
|
39
|
+
change_my_password: 更改密码
|
40
|
+
failure:
|
41
|
+
session_limited: '你的登录凭据已在另一个浏览器上被使用。请重新登录以在此浏览器继续。'
|
42
|
+
expired: '由于不活跃,你的账户已过期。请联系站点管理员。'
|
@@ -0,0 +1,42 @@
|
|
1
|
+
zh_TW:
|
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
|
+
other: 必須包含至少 %{count} 個數字
|
11
|
+
lower:
|
12
|
+
one: 必須包含至少一個小寫字母
|
13
|
+
other: 必須包含至少 %{count} 個小寫字母
|
14
|
+
symbol:
|
15
|
+
one: 必須包含至少一個特殊符號
|
16
|
+
other: 必須包含至少 %{count} 個特殊符號
|
17
|
+
upper:
|
18
|
+
one: 必須包含至少一個大寫字母
|
19
|
+
other: 必須包含至少 %{count} 個大寫字母
|
20
|
+
devise:
|
21
|
+
invalid_captcha: '輸入的驗證碼無效。'
|
22
|
+
invalid_security_question: '安全問題答案無效。'
|
23
|
+
paranoid_verify:
|
24
|
+
code_required: '請輸入由我們客服團隊提供的代碼'
|
25
|
+
paranoid_verification_code:
|
26
|
+
updated: 接受驗證碼
|
27
|
+
show:
|
28
|
+
submit_verification_code: 送出驗證碼
|
29
|
+
verification_code: 驗證碼
|
30
|
+
submit: 送出
|
31
|
+
password_expired:
|
32
|
+
updated: '你的新密碼已儲存'
|
33
|
+
change_required: '你的密碼已經過期,請更新密碼。'
|
34
|
+
show:
|
35
|
+
renew_your_password: 更新你的密碼
|
36
|
+
current_password: 目前密碼
|
37
|
+
new_password: 新密碼
|
38
|
+
new_password_confirmation: 確認新密碼
|
39
|
+
change_my_password: 更改我的密碼
|
40
|
+
failure:
|
41
|
+
session_limited: '你的登入憑證已在另一個瀏覽器上被使用,請重新登入以在此瀏覽器繼續使用。'
|
42
|
+
expired: '你的帳號因過久沒使用而已經過期,請洽網站管理員。'
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DeviseSecurity
|
2
4
|
module Controllers
|
3
5
|
module Helpers
|
@@ -27,8 +29,8 @@ module DeviseSecurity
|
|
27
29
|
end
|
28
30
|
|
29
31
|
def valid_captcha_if_defined?(captcha)
|
30
|
-
defined?(verify_recaptcha) && verify_recaptcha ||
|
31
|
-
defined?(valid_captcha?) && valid_captcha?(captcha)
|
32
|
+
(defined?(verify_recaptcha) && verify_recaptcha) ||
|
33
|
+
(defined?(valid_captcha?) && valid_captcha?(captcha))
|
32
34
|
end
|
33
35
|
|
34
36
|
def valid_security_question_answer?(resource, answer)
|
@@ -38,71 +40,92 @@ module DeviseSecurity
|
|
38
40
|
|
39
41
|
# controller instance methods
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
43
|
+
private
|
44
|
+
|
45
|
+
# Called as a `before_action` on all actions on any controller that uses
|
46
|
+
# this helper. If the user's session is marked as having an expired
|
47
|
+
# password we double check in case it has been changed by another process,
|
48
|
+
# then redirect to the password change url.
|
49
|
+
#
|
50
|
+
# @note `Warden::Manager.after_authentication` is run AFTER this method
|
51
|
+
#
|
52
|
+
# @note Once the warden session has `'password_expired'` set to `false`,
|
53
|
+
# it will **never** be checked again until the user re-logs in.
|
54
|
+
def handle_password_change
|
55
|
+
return if warden.nil?
|
56
|
+
|
57
|
+
if !devise_controller? &&
|
58
|
+
!ignore_password_expire? &&
|
59
|
+
!request.format.nil? &&
|
60
|
+
request.format.html?
|
61
|
+
Devise.mappings.keys.flatten.any? do |scope|
|
62
|
+
if signed_in?(scope) && warden.session(scope)['password_expired'] == true
|
63
|
+
if send(:"current_#{scope}").try(:need_change_password?)
|
64
|
+
store_location_for(scope, request.original_fullpath) if request.get?
|
65
|
+
redirect_for_password_change(scope)
|
66
|
+
else
|
67
|
+
warden.session(scope)['password_expired'] = false
|
58
68
|
end
|
59
69
|
end
|
60
70
|
end
|
61
71
|
end
|
72
|
+
end
|
62
73
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
74
|
+
# lookup if extra (paranoid) code verification is needed
|
75
|
+
def handle_paranoid_verification
|
76
|
+
return if warden.nil?
|
77
|
+
|
78
|
+
if !devise_controller? &&
|
79
|
+
!ignore_paranoid_verification_code? &&
|
80
|
+
!request.format.nil? &&
|
81
|
+
request.format.html?
|
82
|
+
Devise.mappings.keys.flatten.any? do |scope|
|
83
|
+
if signed_in?(scope) && warden.session(scope)['paranoid_verify'] == true
|
84
|
+
if send(:"current_#{scope}").try(:need_paranoid_verification?)
|
70
85
|
store_location_for(scope, request.original_fullpath) if request.get?
|
71
|
-
redirect_for_paranoid_verification
|
72
|
-
|
86
|
+
redirect_for_paranoid_verification(scope)
|
87
|
+
else
|
88
|
+
warden.session(scope)['paranoid_verify'] = false
|
73
89
|
end
|
74
90
|
end
|
75
91
|
end
|
76
92
|
end
|
93
|
+
end
|
77
94
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
95
|
+
# redirect for password update with alert message
|
96
|
+
def redirect_for_password_change(scope)
|
97
|
+
redirect_to change_password_required_path_for(scope), alert: I18n.t('change_required', scope: 'devise.password_expired')
|
98
|
+
end
|
82
99
|
|
83
|
-
|
84
|
-
|
85
|
-
|
100
|
+
def redirect_for_paranoid_verification(scope)
|
101
|
+
redirect_to paranoid_verification_code_path_for(scope), alert: I18n.t('code_required', scope: 'devise.paranoid_verify')
|
102
|
+
end
|
86
103
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
104
|
+
# path for change password
|
105
|
+
def change_password_required_path_for(resource_or_scope = nil)
|
106
|
+
scope = Devise::Mapping.find_scope!(resource_or_scope)
|
107
|
+
router_name = Devise.mappings[scope].router_name
|
108
|
+
context = router_name ? send(router_name) : _devise_route_context
|
109
|
+
context.send("#{scope}_password_expired_path")
|
110
|
+
end
|
93
111
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
112
|
+
def paranoid_verification_code_path_for(resource_or_scope = nil)
|
113
|
+
scope = Devise::Mapping.find_scope!(resource_or_scope)
|
114
|
+
router_name = Devise.mappings[scope].router_name
|
115
|
+
context = router_name ? send(router_name) : _devise_route_context
|
116
|
+
context.send("#{scope}_paranoid_verification_code_path")
|
117
|
+
end
|
99
118
|
|
100
|
-
|
119
|
+
protected
|
101
120
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
121
|
+
# allow to overwrite for some special handlings
|
122
|
+
def ignore_password_expire?
|
123
|
+
false
|
124
|
+
end
|
125
|
+
|
126
|
+
def ignore_paranoid_verification_code?
|
127
|
+
false
|
128
|
+
end
|
106
129
|
end
|
107
130
|
end
|
108
131
|
end
|
@@ -1,10 +1,12 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Updates the last_activity_at fields from the record. Only when the user is active
|
2
4
|
# for authentication and authenticated.
|
3
|
-
# An expiry of the account is only checked on sign in OR on manually setting the
|
5
|
+
# An expiry of the account is only checked on sign in OR on manually setting the
|
4
6
|
# expired_at to the past (see Devise::Models::Expirable for this)
|
5
7
|
Warden::Manager.after_set_user do |record, warden, options|
|
6
|
-
if record && record.respond_to?(:active_for_authentication?) && record.active_for_authentication? &&
|
8
|
+
if record && record.respond_to?(:active_for_authentication?) && record.active_for_authentication? &&
|
7
9
|
warden.authenticated?(options[:scope]) && record.respond_to?(:update_last_activity!)
|
8
10
|
record.update_last_activity!
|
9
11
|
end
|
10
|
-
end
|
12
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
Warden::Manager.after_set_user do |record, warden, options|
|
2
|
-
if record.respond_to?(:need_paranoid_verification?)
|
3
|
-
warden.session(options[:scope])['paranoid_verify'] = record.need_paranoid_verification?
|
4
|
-
end
|
4
|
+
warden.session(options[:scope])['paranoid_verify'] = record.need_paranoid_verification? if record.respond_to?(:need_paranoid_verification?)
|
5
5
|
end
|
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @note This happens after
|
4
|
+
# {DeviseSecurity::Controller::Helpers#handle_password_change}
|
1
5
|
Warden::Manager.after_authentication do |record, warden, options|
|
2
|
-
if record.respond_to?(:need_change_password?)
|
3
|
-
warden.session(options[:scope])['password_expired'] = record.need_change_password?
|
4
|
-
end
|
6
|
+
warden.session(options[:scope])['password_expired'] = record.need_change_password? if record.respond_to?(:need_change_password?)
|
5
7
|
end
|
@@ -1,24 +1,41 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
#
|
4
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# After each sign in, update unique_session_id. This is only triggered when the
|
4
|
+
# user is explicitly set (with set_user) and on authentication. Retrieving the
|
5
|
+
# user from session (:fetch) does not trigger it.
|
5
6
|
Warden::Manager.after_set_user except: :fetch do |record, warden, options|
|
6
|
-
if record.
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
if record.devise_modules.include?(:session_limitable) &&
|
8
|
+
warden.authenticated?(options[:scope]) &&
|
9
|
+
!record.skip_session_limitable?
|
10
|
+
|
11
|
+
if !options[:skip_session_limitable]
|
12
|
+
unique_session_id = Devise.friendly_token
|
13
|
+
warden.session(options[:scope])['unique_session_id'] = unique_session_id
|
14
|
+
record.update_unique_session_id!(unique_session_id)
|
15
|
+
else
|
16
|
+
warden.session(options[:scope])['devise.skip_session_limitable'] = true
|
17
|
+
end
|
10
18
|
end
|
11
19
|
end
|
12
20
|
|
13
|
-
# Each time a record is fetched from session we check if a new session from
|
14
|
-
# browser was opened for the record or not, based on a unique session
|
15
|
-
# If so, the old account is logged out and redirected to the sign in
|
21
|
+
# Each time a record is fetched from session we check if a new session from
|
22
|
+
# another browser was opened for the record or not, based on a unique session
|
23
|
+
# identifier. If so, the old account is logged out and redirected to the sign in
|
24
|
+
# page on the next request.
|
16
25
|
Warden::Manager.after_set_user only: :fetch do |record, warden, options|
|
17
26
|
scope = options[:scope]
|
18
|
-
env = warden.request.env
|
19
27
|
|
20
|
-
if record.
|
21
|
-
|
28
|
+
if record.devise_modules.include?(:session_limitable) &&
|
29
|
+
warden.authenticated?(scope) &&
|
30
|
+
options[:store] != false
|
31
|
+
if record.unique_session_id != warden.session(scope)['unique_session_id'] &&
|
32
|
+
!record.skip_session_limitable? &&
|
33
|
+
!warden.session(scope)['devise.skip_session_limitable']
|
34
|
+
Rails.logger.warn do
|
35
|
+
'[devise-security][session_limitable] session id mismatch: '\
|
36
|
+
"expected=#{record.unique_session_id.inspect} "\
|
37
|
+
"actual=#{warden.session(scope)['unique_session_id'].inspect}"
|
38
|
+
end
|
22
39
|
warden.raw_session.clear
|
23
40
|
warden.logout(scope)
|
24
41
|
throw :warden, scope: scope, message: :session_limited
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Devise
|
4
|
+
module Models
|
5
|
+
module Compatibility
|
6
|
+
class NotPersistedError < ActiveRecord::ActiveRecordError; end
|
7
|
+
|
8
|
+
module ActiveRecordPatch
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
unless defined?(ActiveRecord) && ActiveRecord.gem_version >= Gem::Version.new("5.1.x")
|
12
|
+
# When the record was saved, was the +encrypted_password+ changed?
|
13
|
+
# @return [Boolean]
|
14
|
+
def saved_change_to_encrypted_password?
|
15
|
+
encrypted_password_changed?
|
16
|
+
end
|
17
|
+
|
18
|
+
# The encrypted password that existed before the record was saved
|
19
|
+
# @return [String]
|
20
|
+
# @return [nil] if an +encrypted_password+ had not been set
|
21
|
+
def encrypted_password_before_last_save
|
22
|
+
previous_changes['encrypted_password'].try(:first)
|
23
|
+
end
|
24
|
+
|
25
|
+
# When the record is saved, will the +encrypted_password+ be changed?
|
26
|
+
# @return [Boolean]
|
27
|
+
def will_save_change_to_encrypted_password?
|
28
|
+
changed_attributes['encrypted_password'].present?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Updates the record with the value and does not trigger validations or callbacks
|
33
|
+
# @param name [Symbol] attribute to update
|
34
|
+
# @param value [String] value to set
|
35
|
+
def update_attribute_without_validatons_or_callbacks(name, value)
|
36
|
+
update_column(name, value)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Devise
|
4
|
+
module Models
|
5
|
+
module Compatibility
|
6
|
+
class NotPersistedError < Mongoid::Errors::MongoidError; end
|
7
|
+
|
8
|
+
module MongoidPatch
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
# Will saving this record change the +email+ attribute?
|
12
|
+
# @return [Boolean]
|
13
|
+
def will_save_change_to_email?
|
14
|
+
changed.include? 'email'
|
15
|
+
end
|
16
|
+
|
17
|
+
# Will saving this record change the +encrypted_password+ attribute?
|
18
|
+
# @return [Boolean]
|
19
|
+
def will_save_change_to_encrypted_password?
|
20
|
+
changed.include? 'encrypted_password'
|
21
|
+
end
|
22
|
+
|
23
|
+
# Updates the document with the value and does not trigger validations or callbacks
|
24
|
+
# @param name [Symbol] attribute to update
|
25
|
+
# @param value [String] value to set
|
26
|
+
def update_attribute_without_validatons_or_callbacks(name, value)
|
27
|
+
set(Hash[name, value])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,22 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "compatibility/#{DEVISE_ORM}_patch"
|
4
|
+
|
1
5
|
module Devise
|
2
6
|
module Models
|
7
|
+
# These compatibility modules define methods used by devise-security
|
8
|
+
# that may need to be defined or re-defined for compatibility between ORMs
|
9
|
+
# and/or older versions of ORMs.
|
3
10
|
module Compatibility
|
4
11
|
extend ActiveSupport::Concern
|
5
|
-
|
6
|
-
# for backwards compatibility with Rails < 5.1.x
|
7
|
-
unless Devise.activerecord51?
|
8
|
-
def saved_change_to_encrypted_password?
|
9
|
-
encrypted_password_changed?
|
10
|
-
end
|
11
|
-
|
12
|
-
def encrypted_password_before_last_save
|
13
|
-
previous_changes['encrypted_password'].try(:first)
|
14
|
-
end
|
15
|
-
|
16
|
-
def will_save_change_to_encrypted_password?
|
17
|
-
changed_attributes['encrypted_password'].present?
|
18
|
-
end
|
19
|
-
end
|
12
|
+
include "Devise::Models::Compatibility::#{DEVISE_ORM.to_s.classify}Patch".constantize
|
20
13
|
end
|
21
14
|
end
|
22
15
|
end
|
@@ -1,22 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Devise
|
2
4
|
module Models
|
3
5
|
module DatabaseAuthenticatablePatch
|
4
6
|
def update_with_password(params, *options)
|
5
7
|
current_password = params.delete(:current_password)
|
8
|
+
valid_password = valid_password?(current_password)
|
6
9
|
|
7
10
|
new_password = params[:password]
|
8
11
|
new_password_confirmation = params[:password_confirmation]
|
9
12
|
|
10
|
-
result = if valid_password
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
result = if valid_password && new_password.present? && new_password_confirmation.present?
|
14
|
+
update(params, *options)
|
15
|
+
else
|
16
|
+
assign_attributes(params, *options)
|
17
|
+
|
18
|
+
if current_password.blank?
|
19
|
+
errors.add(:current_password, :blank)
|
20
|
+
elsif !valid_password
|
21
|
+
errors.add(:current_password, :invalid)
|
22
|
+
end
|
23
|
+
|
24
|
+
errors.add(:password, :blank) if new_password.blank?
|
25
|
+
|
26
|
+
errors.add(:password_confirmation, :blank) if new_password_confirmation.blank?
|
27
|
+
|
28
|
+
false
|
29
|
+
end
|
20
30
|
|
21
31
|
clean_up_passwords
|
22
32
|
result
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'devise-security/hooks/expirable'
|
2
4
|
|
3
5
|
module Devise
|
@@ -20,7 +22,11 @@ module Devise
|
|
20
22
|
|
21
23
|
# Updates +last_activity_at+, called from a Warden::Manager.after_set_user hook.
|
22
24
|
def update_last_activity!
|
23
|
-
|
25
|
+
if respond_to?(:update_column)
|
26
|
+
self.update_column(:last_activity_at, Time.now.utc)
|
27
|
+
elsif defined? Mongoid
|
28
|
+
self.update_attribute(:last_activity_at, Time.now.utc)
|
29
|
+
end
|
24
30
|
end
|
25
31
|
|
26
32
|
# Tells if the account has expired
|
@@ -28,9 +34,11 @@ module Devise
|
|
28
34
|
# @return [bool]
|
29
35
|
def expired?
|
30
36
|
# expired_at set (manually, via cron, etc.)
|
31
|
-
return
|
37
|
+
return expired_at < Time.now.utc unless expired_at.nil?
|
38
|
+
|
32
39
|
# if it is not set, check the last activity against configured expire_after time range
|
33
|
-
return
|
40
|
+
return last_activity_at < self.class.expire_after.ago unless last_activity_at.nil?
|
41
|
+
|
34
42
|
# if last_activity_at is nil as well, the user has to be 'fresh' and is therefore not expired
|
35
43
|
false
|
36
44
|
end
|
@@ -52,13 +60,13 @@ module Devise
|
|
52
60
|
#
|
53
61
|
# @return [bool]
|
54
62
|
def active_for_authentication?
|
55
|
-
super && !
|
63
|
+
super && !expired?
|
56
64
|
end
|
57
65
|
|
58
66
|
# The message sym, if {#active_for_authentication?} returns +false+. E.g. needed
|
59
67
|
# for i18n.
|
60
68
|
def inactive_message
|
61
|
-
!
|
69
|
+
!expired? ? super : :expired
|
62
70
|
end
|
63
71
|
|
64
72
|
module ClassMethods
|
@@ -74,7 +82,6 @@ module Devise
|
|
74
82
|
all.each do |u|
|
75
83
|
u.expire! if u.expired? && u.expired_at.nil?
|
76
84
|
end
|
77
|
-
return
|
78
85
|
end
|
79
86
|
|
80
87
|
# Scope method to collect all expired users since +time+ ago
|
@@ -101,7 +108,7 @@ module Devise
|
|
101
108
|
# @example Overwritten version to blank out the object.
|
102
109
|
# def self.delete_all_expired_for(time = 90.days)
|
103
110
|
# expired_for(time).each do |u|
|
104
|
-
# u.
|
111
|
+
# u.update first_name: nil, last_name: nil
|
105
112
|
# end
|
106
113
|
# end
|
107
114
|
def delete_all_expired_for(time)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class OldPassword
|
4
|
+
include Mongoid::Document
|
5
|
+
|
6
|
+
## Database authenticatable
|
7
|
+
field :encrypted_password, type: String
|
8
|
+
validates_presence_of :encrypted_password
|
9
|
+
field :password_salt, type: String
|
10
|
+
|
11
|
+
field :password_archivable_type, type: String
|
12
|
+
validates_presence_of :password_archivable_type
|
13
|
+
|
14
|
+
field :password_archivable_id, type: String
|
15
|
+
validates_presence_of :password_archivable_id
|
16
|
+
index({ password_archivable_type: 1, password_archivable_id: 1 }, name: 'index_password_archivable')
|
17
|
+
|
18
|
+
include Mongoid::Timestamps
|
19
|
+
|
20
|
+
belongs_to :password_archivable, polymorphic: true
|
21
|
+
end
|