devise-security 0.12.0 → 0.13.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +63 -0
  3. data/.gitignore +2 -0
  4. data/.mdlrc +1 -0
  5. data/.rubocop.yml +2 -1
  6. data/.ruby-version +1 -1
  7. data/.travis.yml +9 -11
  8. data/Appraisals +2 -2
  9. data/README.md +72 -53
  10. data/app/controllers/devise/paranoid_verification_code_controller.rb +2 -0
  11. data/app/controllers/devise/password_expired_controller.rb +2 -0
  12. data/config/locales/de.yml +13 -1
  13. data/config/locales/en.yml +13 -1
  14. data/config/locales/es.yml +13 -1
  15. data/config/locales/fr.yml +29 -0
  16. data/config/locales/tr.yml +17 -0
  17. data/devise-security.gemspec +10 -10
  18. data/gemfiles/{rails_4.1_stable.gemfile → rails_5.2.0.gemfile} +1 -1
  19. data/lib/devise-security.rb +8 -4
  20. data/lib/devise-security/controllers/helpers.rb +2 -0
  21. data/lib/devise-security/hooks/expirable.rb +3 -1
  22. data/lib/devise-security/hooks/paranoid_verification.rb +2 -0
  23. data/lib/devise-security/hooks/password_expirable.rb +2 -0
  24. data/lib/devise-security/hooks/session_limitable.rb +2 -0
  25. data/lib/devise-security/models/compatibility.rb +2 -0
  26. data/lib/devise-security/models/database_authenticatable_patch.rb +2 -0
  27. data/lib/devise-security/models/expirable.rb +2 -0
  28. data/lib/devise-security/models/old_password.rb +2 -0
  29. data/lib/devise-security/models/paranoid_verification.rb +2 -0
  30. data/lib/devise-security/models/password_archivable.rb +2 -0
  31. data/lib/devise-security/models/password_expirable.rb +96 -50
  32. data/lib/devise-security/models/secure_validatable.rb +10 -4
  33. data/lib/devise-security/models/security_questionable.rb +2 -0
  34. data/lib/devise-security/models/session_limitable.rb +2 -0
  35. data/lib/devise-security/orm/active_record.rb +2 -0
  36. data/lib/devise-security/patches.rb +2 -0
  37. data/lib/devise-security/patches/confirmations_controller_captcha.rb +2 -0
  38. data/lib/devise-security/patches/confirmations_controller_security_question.rb +2 -0
  39. data/lib/devise-security/patches/controller_captcha.rb +2 -0
  40. data/lib/devise-security/patches/controller_security_question.rb +2 -0
  41. data/lib/devise-security/patches/passwords_controller_captcha.rb +2 -0
  42. data/lib/devise-security/patches/passwords_controller_security_question.rb +2 -0
  43. data/lib/devise-security/patches/registrations_controller_captcha.rb +2 -0
  44. data/lib/devise-security/patches/sessions_controller_captcha.rb +2 -0
  45. data/lib/devise-security/patches/unlocks_controller_captcha.rb +2 -0
  46. data/lib/devise-security/patches/unlocks_controller_security_question.rb +2 -0
  47. data/lib/devise-security/rails.rb +2 -0
  48. data/lib/devise-security/routes.rb +2 -0
  49. data/lib/devise-security/schema.rb +2 -0
  50. data/lib/devise-security/validators/password_complexity_validator.rb +33 -0
  51. data/lib/devise-security/version.rb +3 -1
  52. data/lib/generators/devise_security/install_generator.rb +3 -1
  53. data/lib/generators/templates/devise-security.rb +9 -3
  54. data/test/dummy/Rakefile +3 -1
  55. data/test/dummy/app/controllers/application_controller.rb +2 -0
  56. data/test/dummy/app/controllers/captcha/sessions_controller.rb +2 -0
  57. data/test/dummy/app/controllers/security_question/unlocks_controller.rb +2 -0
  58. data/test/dummy/app/models/application_record.rb +2 -0
  59. data/test/dummy/app/models/captcha_user.rb +3 -1
  60. data/test/dummy/app/models/secure_user.rb +3 -1
  61. data/test/dummy/app/models/security_question_user.rb +3 -1
  62. data/test/dummy/app/models/user.rb +2 -0
  63. data/test/dummy/app/models/widget.rb +2 -0
  64. data/test/dummy/config.ru +3 -1
  65. data/test/dummy/config/application.rb +2 -0
  66. data/test/dummy/config/boot.rb +2 -0
  67. data/test/dummy/config/environment.rb +2 -0
  68. data/test/dummy/config/environments/test.rb +2 -0
  69. data/test/dummy/config/initializers/devise.rb +8 -0
  70. data/test/dummy/config/initializers/migration_class.rb +2 -0
  71. data/test/dummy/config/routes.rb +2 -0
  72. data/test/dummy/db/migrate/20120508165529_create_tables.rb +2 -0
  73. data/test/dummy/db/migrate/20150402165590_add_verification_columns.rb +2 -0
  74. data/test/dummy/db/migrate/20150407162345_add_verification_attempt_column.rb +2 -0
  75. data/test/dummy/db/migrate/20160320162345_add_security_questions_fields.rb +2 -0
  76. data/test/dummy/db/migrate/20180318103603_add_expireable_columns.rb +2 -0
  77. data/test/dummy/db/migrate/20180318105329_add_confirmable_columns.rb +2 -0
  78. data/test/dummy/db/migrate/20180318105732_add_rememberable_columns.rb +2 -0
  79. data/test/dummy/db/migrate/20180318111336_add_recoverable_columns.rb +2 -0
  80. data/test/dummy/db/migrate/20180319114023_add_widget.rb +2 -0
  81. data/test/test_captcha_controller.rb +2 -0
  82. data/test/test_complexity_validator.rb +60 -0
  83. data/test/test_helper.rb +19 -8
  84. data/test/test_install_generator.rb +7 -1
  85. data/test/test_paranoid_verification.rb +2 -0
  86. data/test/test_password_archivable.rb +2 -0
  87. data/test/test_password_expirable.rb +68 -7
  88. data/test/test_password_expired_controller.rb +2 -0
  89. data/test/test_secure_validatable.rb +10 -11
  90. data/test/test_security_question_controller.rb +2 -0
  91. metadata +32 -39
  92. data/.circleci/config.yml +0 -41
  93. data/gemfiles/rails_5.2_rc1.gemfile +0 -8
@@ -3,7 +3,19 @@ es:
3
3
  messages:
4
4
  taken_in_past: 'la contraseña fue usada previamente, favor elegir otra.'
5
5
  equal_to_current_password: 'tiene que ser diferente a la contraseña actual.'
6
- password_format: 'tiene que contener mayúsculas, minúsculas y digitos '
6
+ password_complexity:
7
+ digit:
8
+ one: tiene que contener al menos un digito
9
+ other: tiene que contener al menos %{count} digitos
10
+ lower:
11
+ one: tiene que contener al menos un minúscula
12
+ other: tiene que contener al menos %{count} minúsculas
13
+ symbol:
14
+ one: tiene que contener al menos un signo de puntuación
15
+ other: tiene que contener al menos %{count} signos de puntuación
16
+ upper:
17
+ one: tiene que contener al menos un mayúscula
18
+ other: tiene que contener al menos %{count} mayúsculas
7
19
  devise:
8
20
  invalid_captcha: 'El captcha ingresado es inválido.'
9
21
  invalid_security_question: 'La respuesta a la pregunta de suguridad fue incorrecta.'
@@ -0,0 +1,29 @@
1
+ fr:
2
+ errors:
3
+ messages:
4
+ taken_in_past: a été utilisé trop récemment - s'il vous plaît, choisissez un autre
5
+ equal_to_current_password: doit être différent du l'actuel
6
+ password_complexity:
7
+ digit:
8
+ one: doit containir en moins d'un chiffre
9
+ other: doit containir en moins de %{count} chiffres
10
+ lower:
11
+ one: doit containir en moins d'un miniscule
12
+ other: doit containir en moins de %{count} miniscules
13
+ symbol:
14
+ one: doit containir en moins d'un signe de ponctuation
15
+ other: doit containir en moins de %{count} signes de ponctuation
16
+ upper:
17
+ one: doit containir en moins d'un majuscule
18
+ other: doit containir en moins de %{count} majuscules
19
+ devise:
20
+ invalid_captcha: Le captcha n'est pas valide
21
+ invalid_security_question: La réponse à la question de sécurité était invalide
22
+ paranoid_verify:
23
+ code_required: Veuillez entrer le code fourni par notre équipe de support
24
+ password_expired:
25
+ updated: Votre nouveau mot de passe est enregistré
26
+ change_required: Votre mot de passe a expiré - s'il vous plaît, choisissez un autre
27
+ failure:
28
+ session_limited: Vos identifiants de connexion ont été utilisés dans un autre navigateur. Veuillez vous reconnecter pour continuer dans ce navigateur
29
+ expired: Votre compte a expiré en raison de l'inactivité. Veuillez contacter l'administrateur du site
@@ -0,0 +1,17 @@
1
+ tr:
2
+ errors:
3
+ messages:
4
+ taken_in_past: "daha önce kullanıldı."
5
+ equal_to_current_password: "mevcut paroladan farklı olmalı."
6
+ password_format: "büyük, küçük harfler ve sayılar içermeli."
7
+ devise:
8
+ invalid_captcha: "Captcha hatalı."
9
+ invalid_security_question: "Güvenlik sorusunun cevabı yanlış."
10
+ paranoid_verify:
11
+ code_required: "Destek ekibimizden aldığınız kodu girin."
12
+ password_expired:
13
+ updated: "Yeni parolanız kaydedildi."
14
+ change_required: "Parolanızın geçerlilik süresi dolmuş. Lütfen parolanızı yenileyin."
15
+ failure:
16
+ session_limited: 'Hesabınıza başka bir tarayıcıdan giriş yapılmış. Lütfen devam etmek için yeniden giriş yapın.'
17
+ expired: 'Hesabınız aktif olarak kullanılmadığı için artık geçerli değil. Lütfen yönetici ile irtibata geçin.'
@@ -20,25 +20,25 @@ Gem::Specification.new do |s|
20
20
  s.files = `git ls-files`.split("\n")
21
21
  s.test_files = `git ls-files -- test/*`.split("\n")
22
22
  s.require_paths = ['lib']
23
- s.required_ruby_version = '>= 2.2.9'
23
+ s.required_ruby_version = '>= 2.3.7'
24
24
 
25
25
  if RUBY_VERSION >= '2.4'
26
- s.add_runtime_dependency 'rails', '>= 4.1.0', '< 6.0'
26
+ s.add_runtime_dependency 'rails', '>= 4.2.0', '< 6.0'
27
27
  else
28
- s.add_runtime_dependency 'railties', '>= 4.1.0', '< 6.0'
28
+ s.add_runtime_dependency 'railties', '>= 4.2.0', '< 6.0'
29
29
  end
30
30
  s.add_runtime_dependency 'devise', '>= 4.2.0', '< 5.0'
31
31
 
32
32
  s.add_development_dependency 'appraisal'
33
- s.add_development_dependency 'bundler', '>= 1.3.0', '< 2.0'
34
- s.add_development_dependency 'coveralls', '~> 0.8'
35
- s.add_development_dependency 'easy_captcha', '~> 0'
33
+ s.add_development_dependency 'bundler'
34
+ s.add_development_dependency 'coveralls'
35
+ s.add_development_dependency 'easy_captcha'
36
36
  s.add_development_dependency 'm'
37
- s.add_development_dependency 'minitest', '5.10.3' # see https://github.com/seattlerb/minitest/issues/730
37
+ s.add_development_dependency 'minitest'
38
38
  s.add_development_dependency 'pry-byebug'
39
39
  s.add_development_dependency 'pry-rescue'
40
40
  s.add_development_dependency 'pry'
41
- s.add_development_dependency 'rails_email_validator', '~> 0'
42
- s.add_development_dependency 'rubocop', '~> 0'
43
- s.add_development_dependency 'sqlite3', '~> 1.3', '>= 1.3.10'
41
+ s.add_development_dependency 'rails_email_validator'
42
+ s.add_development_dependency 'rubocop', '~> 0.58.0'
43
+ s.add_development_dependency 'sqlite3'
44
44
  end
@@ -3,6 +3,6 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "omniauth"
6
- gem "rails", "~> 4.1.0"
6
+ gem "rails", "~> 5.2.0"
7
7
 
8
8
  gemspec path: "../"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record'
2
4
  require 'active_support/core_ext/integer'
3
5
  require 'active_support/ordered_hash'
@@ -6,13 +8,15 @@ require 'devise'
6
8
 
7
9
  module Devise
8
10
 
9
- # Should the password expire (e.g 3.months)
11
+ # Number of seconds that passwords are valid (e.g 3.months)
12
+ # Disable pasword expiration with +false+
13
+ # Expire only on demand with +true+
10
14
  mattr_accessor :expire_password_after
11
15
  @@expire_password_after = 3.months
12
16
 
13
17
  # Validate password for strongness
14
- mattr_accessor :password_regex
15
- @@password_regex = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/
18
+ mattr_accessor :password_complexity
19
+ @@password_complexity = { digit: 1, lower: 1, symbol: 1, upper: 1 }
16
20
 
17
21
  # Number of old passwords in archive
18
22
  mattr_accessor :password_archiving_count
@@ -23,7 +27,7 @@ module Devise
23
27
  @@deny_old_passwords = true
24
28
 
25
29
  # enable email validation for :secure_validatable. (true, false, validation_options)
26
- # dependency: need an email validator like rails_email_validator
30
+ # dependency: need an email validator, see https://github.com/devise-security/devise-security/blob/master/README.md#e-mail-validation
27
31
  mattr_accessor :email_validation
28
32
  @@email_validation = true
29
33
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity
2
4
  module Controllers
3
5
  module Helpers
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Updates the last_activity_at fields from the record. Only when the user is active
2
4
  # for authentication and authenticated.
3
5
  # An expiry of the account is only checked on sign in OR on manually setting the
@@ -7,4 +9,4 @@ Warden::Manager.after_set_user do |record, warden, options|
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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Warden::Manager.after_set_user do |record, warden, options|
2
4
  if record.respond_to?(:need_paranoid_verification?)
3
5
  warden.session(options[:scope])['paranoid_verify'] = record.need_paranoid_verification?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Warden::Manager.after_authentication do |record, warden, options|
2
4
  if record.respond_to?(:need_change_password?)
3
5
  warden.session(options[:scope])['password_expired'] = record.need_change_password?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # After each sign in, update unique_session_id.
2
4
  # This is only triggered when the user is explicitly set (with set_user)
3
5
  # and on authentication. Retrieving the user from session (:fetch) does
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Models
3
5
  module Compatibility
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Models
3
5
  module DatabaseAuthenticatablePatch
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'devise-security/hooks/expirable'
2
4
 
3
5
  module Devise
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record'
2
4
  class OldPassword < ActiveRecord::Base
3
5
  belongs_to :password_archivable, polymorphic: true
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'devise-security/hooks/paranoid_verification'
2
4
 
3
5
  module Devise
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'compatibility'
2
4
 
3
5
  module Devise
@@ -1,67 +1,113 @@
1
- require 'devise-security/hooks/password_expirable'
1
+ # frozen_string_literal: true
2
2
 
3
- module Devise
4
- module Models
3
+ require 'devise-security/hooks/password_expirable'
5
4
 
6
- # PasswordExpirable takes care of change password after
7
- module PasswordExpirable
8
- extend ActiveSupport::Concern
5
+ module Devise::Models
6
+ # PasswordExpirable makes passwords expire after a configurable amount of
7
+ # time, or on demand.
8
+ #
9
+ # == Configuration
10
+ # Set +expire_password_after+ to the number of seconds a password is valid for
11
+ # (example: +3.months+). Setting it to +true+ will allow passwords to be expired
12
+ # on-demand only, and +false+ disables this feature.
13
+ #
14
+ # == Expire On-Demand
15
+ # This is useful to force users to change passwords for complex business reasons.
16
+ # Call +need_change_password+ to indicate a record needs a new password.
17
+ module PasswordExpirable
18
+ extend ActiveSupport::Concern
9
19
 
10
- included do
11
- before_save :update_password_changed
12
- end
20
+ included do
21
+ scope :with_password_change_requested, -> { where(password_changed_at: nil) }
22
+ scope :without_password_change_requested, -> { where.not(password_changed_at: nil) }
23
+ scope :with_expired_password, -> { where('password_changed_at is NULL OR password_changed_at < ?', expire_password_after.seconds.ago) }
24
+ scope :without_expired_password, -> { without_password_change_requested.where('password_changed_at >= ?', expire_password_after.seconds.ago) }
25
+ before_save :update_password_changed
26
+ end
13
27
 
14
- # is an password change required?
15
- def need_change_password?
16
- if expired_password_after_numeric?
17
- self.password_changed_at.nil? || self.password_changed_at < self.expire_password_after.seconds.ago
18
- else
19
- false
20
- end
21
- end
28
+ # Is a password change required?
29
+ # @return [Boolean]
30
+ # @return [true] if +password_changed_at+ has not been set or if it is old
31
+ # enough based on +expire_password_after+ configuration.
32
+ def need_change_password?
33
+ password_change_requested? || password_too_old?
34
+ end
22
35
 
23
- # set a fake datetime so a password change is needed and save the record
24
- def need_change_password!
25
- if expired_password_after_numeric?
26
- need_change_password
27
- self.save(validate: false)
28
- end
29
- end
36
+ # Clear the +password_changed_at+ field so that the user will be required to
37
+ # update their password.
38
+ # @note Saves the record (without validations)
39
+ # @return [Boolean]
40
+ def need_change_password!
41
+ return unless password_expiration_enabled?
42
+ need_change_password
43
+ save(validate: false)
44
+ end
45
+ alias expire_password! need_change_password!
46
+ alias request_password_change! need_change_password!
30
47
 
31
- # set a fake datetime so a password change is needed
32
- def need_change_password
33
- if expired_password_after_numeric?
34
- self.password_changed_at = self.expire_password_after.seconds.ago
35
- end
48
+ # Clear the +password_changed_at+ field so that the user will be required to
49
+ # update their password.
50
+ # @note Does not save the record
51
+ # @return [void]
52
+ def need_change_password
53
+ return unless password_expiration_enabled?
54
+ self.password_changed_at = nil
55
+ end
56
+ alias expire_password need_change_password
57
+ alias request_password_change need_change_password
36
58
 
37
- # is date not set it will set default to need set new password next login
38
- need_change_password if self.password_changed_at.nil?
59
+ # @return [Integer] number of seconds passwords are valid for
60
+ # @return [true] passwords are expired 'on demand' only.
61
+ # @return [false] passwords never expire (this feature is disabled)
62
+ def expire_password_after
63
+ self.class.expire_password_after
64
+ end
39
65
 
40
- self.password_changed_at
41
- end
66
+ # When +password_changed_at+ is set to +NULL+ in the database
67
+ # the user is required to change their password. This only happens
68
+ # on demand or when the column is first added to the table.
69
+ # @return [Boolean]
70
+ def password_change_requested?
71
+ return false unless password_expiration_enabled?
72
+ return false if new_record?
73
+ password_changed_at.nil?
74
+ end
42
75
 
43
- def expire_password_after
44
- self.class.expire_password_after
45
- end
76
+ # Is this password older than the configured expiration timeout?
77
+ # @return [Boolean]
78
+ def password_too_old?
79
+ return false if new_record?
80
+ return false unless password_expiration_enabled?
81
+ return false if expire_password_on_demand?
82
+ password_changed_at < expire_password_after.seconds.ago
83
+ end
84
+ alias password_expired? password_too_old?
46
85
 
47
- private
86
+ private
48
87
 
49
- # is password changed then update password_cahanged_at
50
- def update_password_changed
51
- self.password_changed_at = Time.now if (self.new_record? || self.encrypted_password_changed?) && !self.password_changed_at_changed?
52
- end
88
+ # Update +password_changed_at+ for new records and changed passwords.
89
+ # @note called as a +before_save+ hook
90
+ def update_password_changed
91
+ return unless (new_record? || encrypted_password_changed?) && !password_changed_at_changed?
92
+ self.password_changed_at = Time.zone.now
93
+ end
53
94
 
54
- def expired_password_after_numeric?
55
- return @_numeric if defined?(@_numeric)
56
- @_numeric ||= self.expire_password_after.is_a?(1.class) ||
57
- self.expire_password_after.is_a?(Float)
58
- end
95
+ # Enabled if configuration +expire_password_after+ is set to an {Integer},
96
+ # {Float}, or {true}
97
+ def password_expiration_enabled?
98
+ expire_password_after.is_a?(1.class) ||
99
+ expire_password_after.is_a?(Float) ||
100
+ expire_password_on_demand?
101
+ end
59
102
 
60
- module ClassMethods
61
- ::Devise::Models.config(self, :expire_password_after)
62
- end
103
+ # When +expire_password_after+ is set to +true+ then only expire passwords
104
+ # on demand.
105
+ def expire_password_on_demand?
106
+ expire_password_after.present? && expire_password_after == true
63
107
  end
64
108
 
109
+ module ClassMethods
110
+ ::Devise::Models.config(self, :expire_password_after)
111
+ end
65
112
  end
66
-
67
113
  end
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'compatibility'
4
+ require_relative '../validators/password_complexity_validator'
2
5
 
3
6
  module Devise
4
7
  module Models
@@ -45,8 +48,10 @@ module Devise
45
48
  end
46
49
 
47
50
  # extra validations
48
- validates :email, email: email_validation if email_validation # use rails_email_validator or similar
49
- validates :password, format: { with: password_regex, message: :password_format }, if: :password_required?
51
+ validates :email, email: email_validation if email_validation # see https://github.com/devise-security/devise-security/blob/master/README.md#e-mail-validation
52
+ validates :password,
53
+ 'devise_security/password_complexity': password_complexity,
54
+ if: :password_required?
50
55
 
51
56
  # don't allow use same password
52
57
  validate :current_equal_password_validation
@@ -79,9 +84,10 @@ module Devise
79
84
  end
80
85
 
81
86
  module ClassMethods
82
- Devise::Models.config(self, :password_regex, :password_length, :email_validation)
87
+ Devise::Models.config(self, :password_complexity, :password_length, :email_validation)
88
+
89
+ private
83
90
 
84
- private
85
91
  def has_uniqueness_validation_of_login?
86
92
  validators.any? do |validator|
87
93
  validator.kind_of?(ActiveRecord::Validations::UniquenessValidator) &&
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Models
3
5
  # SecurityQuestionable is an accessible add-on for visually handicapped people,
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'devise-security/hooks/session_limitable'
2
4
 
3
5
  module Devise
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity
2
4
  module Orm
3
5
  # This module contains some helpers and handle schema (migrations):