devise-two-factor 4.1.1 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 787116f2d8fd1e2ef5a6e663c0a3ed5a65ca6110a491f44d2de5eb1a985da516
4
- data.tar.gz: f379bef9da0239c3697fe14dad33ba6c662ecfd2b6bcc3ab54fb184f75a970d4
3
+ metadata.gz: f0f81d936ba021c504827ebf9a6faa199f7a0a8f714fee2d9ce6d48acbde423b
4
+ data.tar.gz: 38bf04b9361f64618c84081c5ce5436f523f8476c625b91b92cfba8e56e2cd5c
5
5
  SHA512:
6
- metadata.gz: 23b55432e9df42fa8723db98cd38abe641656ed05ce6f5d9e5d7ee1a1179624ba7212209c460a7ff4b16d4f14347b11c69750d795e7ff9f56513e93af651c619
7
- data.tar.gz: ce784140d65f12c6e46a51d86ed8e97edc6b98e706f276160e67f95bccf615c308a5cf273bd54ad71788b61e5dac92f9676c4e67ebfdd81c83584fa71e0bb509
6
+ metadata.gz: 54b62797c0194f8a3dc04f4594db384bdf6421eaf35707b7a35a39dc3993348790f1544e24d9d013885a7569a4c2381f938037626c26bf054ca00fe02bc46026
7
+ data.tar.gz: 2c24d3d5e822151f323ba27efb915ad44a33be2a20b95b3decad88facf34c68a70bbf471b1d748bd2c2498a088b5ddc0fb333486d467eb9865dd3f6aa941694c
checksums.yaml.gz.sig CHANGED
Binary file
@@ -12,23 +12,8 @@ jobs:
12
12
  fail-fast: false
13
13
  matrix:
14
14
  # Due to https://github.com/actions/runner/issues/849, we should quote versions
15
- ruby: ['2.3', '2.4', '2.5', '2.6', '2.7', '3.0', 'truffleruby-head']
16
- rails: ['4.1', '4.2', '5.0', '5.1', '5.2', '6.0', '6.1', '7.0']
17
- exclude:
18
- - { ruby: '2.3', rails: '7.0' }
19
- - { ruby: '2.4', rails: '7.0' }
20
- - { ruby: '2.5', rails: '7.0' }
21
- - { ruby: '2.6', rails: '7.0' }
22
- - { ruby: '2.3', rails: '6.0' }
23
- - { ruby: '2.3', rails: '6.1' }
24
- - { ruby: '2.4', rails: '6.0' }
25
- - { ruby: '2.4', rails: '6.1' }
26
- - { ruby: '2.7', rails: '4.1' }
27
- - { ruby: '2.7', rails: '4.2' }
28
- - { ruby: '3.0', rails: '4.1' }
29
- - { ruby: '3.0', rails: '4.2' }
30
- - { ruby: 'truffleruby-head', rails: '4.1' }
31
- - { ruby: 'truffleruby-head', rails: '4.2' }
15
+ ruby: ['2.7', '3.0', '3.1', 'truffleruby-head']
16
+ rails: ['7.0']
32
17
 
33
18
  name: Ruby ${{ matrix.ruby }}, Rails ${{ matrix.rails }}
34
19
  env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
data/README.md CHANGED
@@ -19,79 +19,80 @@ An example Rails 4 application is provided in the `demo` directory. It showcases
19
19
  For the demo app to work, create an encryption key and store it as an environment variable. One way to do this is to create a file named `local_env.yml` in the application root. Set the value of `ENCRYPTION_KEY` in the YML file. That value will be loaded into the application environment by `application.rb`.
20
20
 
21
21
  ## Getting Started
22
- Devise-Two-Factor doesn't require much to get started, but there are a few prerequisites before you can start using it in your application.
23
22
 
24
- First, you'll need a Rails application setup with Devise. Visit the Devise [homepage](https://github.com/plataformatec/devise) for instructions.
23
+ Devise-Two-Factor doesn't require much to get started, but there are two prerequisites before you can start using it in your application:
25
24
 
26
- You can add Devise-Two-Factor to your Gemfile with:
25
+ 1. A Rails application with [devise](https://github.com/heartcombo/devise) installed
26
+ 1. Secrets configured for ActiveRecord encrypted attributes
27
27
 
28
- ```ruby
29
- gem 'devise-two-factor'
30
- ```
28
+ First, you'll need a Rails application setup with Devise. Visit the Devise [homepage](https://github.com/plataformatec/devise) for instructions.
31
29
 
32
- Next, since Devise-Two-Factor encrypts its secrets before storing them in the database, you'll need to generate an encryption key, and store it in an environment variable of your choice. Set the encryption key in the model that uses Devise:
30
+ Devise-Two-Factor uses [ActiveRecord encrypted attributes](https://edgeguides.rubyonrails.org/active_record_encryption.html) which in turn uses Rails' encrypted credentials. [The Rails encrypted attributes guide](https://edgeguides.rubyonrails.org/active_record_encryption.html) has full details of how to set these up but briefly:
33
31
 
34
- ```ruby
35
- devise :two_factor_authenticatable,
36
- :otp_secret_encryption_key => ENV['YOUR_ENCRYPTION_KEY_HERE']
32
+ ```bash
33
+ # generate suitable encryption secrets to stdout
34
+ $ ./bin/rails db:encryption:init
37
35
 
36
+ # Add the output from the command above to your encrypted credentials file via
37
+ # Setting the EDITOR environment variable is optional, without it, your default editor will open
38
+ $ EDITOR=code ./bin/rails credentials:edit
38
39
  ```
39
40
 
40
- Finally, you can automate all of the required setup by simply running:
41
+ Add Devise-Two-Factor to your Gemfile with:
41
42
 
42
43
  ```ruby
43
- rails generate devise_two_factor MODEL ENVIRONMENT_VARIABLE
44
- ```
45
-
46
- Where `MODEL` is the name of the model you wish to add two-factor functionality to (for example `user`), and `ENVIRONMENT_VARIABLE` is the name of the variable you're storing your encryption key in.
47
-
48
- This generator will add a few columns to the specified model:
49
-
50
- * encrypted_otp_secret
51
- * encrypted_otp_secret_iv
52
- * encrypted_otp_secret_salt
53
- * consumed_timestep
54
- * otp_required_for_login
55
-
56
- Remember to apply the new migration.
44
+ # Gemfile
57
45
 
58
- ```ruby
59
- bundle exec rake db:migrate
46
+ gem 'devise-two-factor'
60
47
  ```
61
48
 
62
- It also adds the `:two_factor_authenticatable` directive to your model, and sets up your encryption key. If present, it will remove `:database_authenticatable` from the model, as the two strategies are incompatible. Lastly, the generator will add a Warden config block to your Devise initializer, which enables the strategies required for two-factor authentication.
49
+ There is a generator which automates most of the setup:
63
50
 
64
- If you're running Rails 3, or do not have strong parameters enabled, the generator will also setup the required mass-assignment security options in your model.
51
+ ```bash
52
+ # MODEL is the name of the model you wish to configure devise_two_factor e.g. User or Admin
53
+ ./bin/rails generate devise_two_factor MODEL
54
+ ```
65
55
 
66
- If you're running Rails 4, you'll also need to whitelist `:otp_attempt` as a permitted parameter in Devise `:sign_in` controller. You can do this by adding the following to your `application_controller.rb`:
56
+ Where `MODEL` is the name of the model you wish to add two-factor functionality to (for example `user`)
67
57
 
68
- ```ruby
69
- before_action :configure_permitted_parameters, if: :devise_controller?
58
+ This generator will:
70
59
 
71
- ...
60
+ 1. Create a new migration which adds a few columns to the specified model:
61
+ ```ruby
62
+ add_column :users, :otp_secret, :string
63
+ add_column :users, :consumed_timestep, :integer
64
+ add_column :users, :otp_required_for_login, :boolean
65
+ ```
66
+ 1. Edit `app/models/MODEL.rb` (where MODEL is your model name):
67
+ * add the `:two_factor_authenticatable` devise module
68
+ * remove the `:database_authenticatable` if present because it is incompatible with `:two_factor_authenticatable`
69
+ 1. Add a Warden config block to your Devise initializer, which enables the strategies required for two-factor authentication.
72
70
 
73
- protected
71
+ Remember to apply the new migration after you run the generator:
74
72
 
75
- def configure_permitted_parameters
76
- devise_parameter_sanitizer.for(:sign_in) << :otp_attempt
77
- end
73
+ ```bash
74
+ ./bin/rails db:migrate
78
75
  ```
79
76
 
80
- If you're running Devise 4.0.0 or above, you'll want to use `.permit` instead:
77
+ Next you need to whitelist `:otp_attempt` as a permitted parameter in Devise `:sign_in` controller. You can do this by adding the following to your `application_controller.rb`:
81
78
 
82
79
  ```ruby
83
- before_action :configure_permitted_parameters, if: :devise_controller?
80
+ # app/controllers/application_controller.rb
84
81
 
85
- ...
82
+ before_action :configure_permitted_parameters, if: :devise_controller?
86
83
 
87
- protected
84
+ # ...
88
85
 
89
- def configure_permitted_parameters
90
- devise_parameter_sanitizer.permit(:sign_in, keys: [:otp_attempt])
91
- end
86
+ protected
87
+
88
+ def configure_permitted_parameters
89
+ devise_parameter_sanitizer.permit(:sign_in, keys: [:otp_attempt])
90
+ end
92
91
  ```
93
92
 
94
- **After running the generator, verify that `:database_authenticatable` is not being loaded by your model. The generator will try to remove it, but if you have a non-standard Devise setup, this step may fail. Loading both `:database_authenticatable` and `:two_factor_authenticatable` in a model will allow users to bypass two-factor authenticatable due to the way Warden handles cascading strategies.**
93
+ Finally you should verify that `:database_authenticatable` is **not** being loaded by your model. The generator will try to remove it, but if you have a non-standard Devise setup, this step may fail.
94
+
95
+ **Loading both `:database_authenticatable` and `:two_factor_authenticatable` in a model is a security issue** It will allow users to bypass two-factor authenticatable due to the way Warden handles cascading strategies!
95
96
 
96
97
  ## Designing Your Workflow
97
98
  Devise-Two-Factor only worries about the backend, leaving the details of the integration up to you. This means that you're responsible for building the UI that drives the gem. While there is an example Rails application included in the gem, it is important to remember that this gem is intentionally very open-ended, and you should build a user experience which fits your individual application.
data/UPGRADING.md CHANGED
@@ -1,3 +1,196 @@
1
+ # Upgrading from 4.x to 5.x
2
+
3
+ ## Background
4
+
5
+ ### Database columns in version 4.x and older
6
+
7
+ Versions 4.x and older stored the OTP secret in an attribute called `encrypted_otp_secret` using the [attr_encrypted](https://github.com/attr-encrypted/attr_encrypted) gem. This gem is currently unmaintained which is part of the motivation for moving to Rails encrypted attributes. This attribute was backed by three database columns:
8
+
9
+ ```
10
+ encrypted_otp_secret
11
+ encrypted_otp_secret_iv
12
+ encrypted_otp_secret_salt
13
+ ```
14
+
15
+ Two other columns were also created:
16
+
17
+ ```
18
+ consumed_timestep
19
+ otp_required_for_login
20
+ ```
21
+
22
+ A fresh install of 4.x would create all five of the database columns above.
23
+
24
+ ### Database columns in version 5.x and later
25
+
26
+ Versions 5+ of this gem uses a single [Rails 7+ encrypted attribute](https://edgeguides.rubyonrails.org/active_record_encryption.html) named `otp_secret`to store the OTP secret in the database table (usually `users` but will be whatever model you picked).
27
+
28
+ A fresh install of 5+ will add the following columns to your `users` table:
29
+
30
+ ```bash
31
+ otp_secret # this replaces encrypted_otp_secret, encrypted_otp_secret_iv, encrypted_otp_secret_salt
32
+ consumed_timestep
33
+ otp_required_for_login
34
+ ```
35
+
36
+ ### Upgrading from 4.x to 5.x
37
+
38
+
39
+ We have attempted to make the upgrade as painless as possible but unfortunately because of the secret storage change, it cannot be as simple as `bundle update devise-two-factor` :heart:
40
+
41
+ #### Assumptions
42
+
43
+ This guide assumes you are upgrading an existing Rails 6 app (with `devise` and `devise-two-factor`) to Rails 7.
44
+
45
+ This gem must be upgraded **as part of a Rails 7 upgrade**. See [the official Rails upgrading guide](https://guides.rubyonrails.org/upgrading_ruby_on_rails.html) for an overview of upgrading Rails.
46
+
47
+ #### Phase 1: Upgrading devise-two-factor as part of Rails 7 upgrade
48
+
49
+ 1. Update the version constraint for Rails in your `Gemfile` to your desired version e.g. `gem "rails", "~> 7.0.3"`
50
+ 1. Run `bundle install` and resolve any issues with dependencies.
51
+ 1. Update the version constraint for `devise-two-factor` in your `Gemfile` to the the latest version (must be at least 5.x e.g. `~> 5.0`
52
+ 1. Run `./bin/rails app:update` as per the [Rails upgrade guide](https://guides.rubyonrails.org/upgrading_ruby_on_rails.html) and tweak the output as required for your app.
53
+ 1. Run `./bin/rails db:migrate` to update your DB based on the changes made by `app:update`
54
+ 1. Add a new `otp_secret` attribute to your user model
55
+ ```bash
56
+ # TODO: replace 'User' in the migration name with the name of your user model
57
+ ./bin/rails g migration AddOtpSecretToUser otp_secret:string
58
+ ./bin/rails db:migrate
59
+ ```
60
+ 1. Add a `legacy_otp_secret` method to your user model e.g. `User`.
61
+ * This method is used by the gem to find and decode the OTP secret from the legacy database columns.
62
+ * The implementation shown below works if you set up devise-two-factor with the settings suggested in the [README](./README.md).
63
+ * If you have customised the encryption scheme used to store the OTP secret then you will need to update this method to match.
64
+ * If you are unsure, you should try the method below as is, and if you can still sign in users with OTP enabled then all is well.
65
+ ```ruby
66
+ class User
67
+ # ...
68
+
69
+ private
70
+
71
+ ##
72
+ # Decrypt and return the `encrypted_otp_secret` attribute which was used in
73
+ # prior versions of devise-two-factor
74
+ # @return [String] The decrypted OTP secret
75
+ def legacy_otp_secret
76
+ return nil unless self[:encrypted_otp_secret]
77
+ return nil unless self.class.otp_secret_encryption_key
78
+
79
+ hmac_iterations = 2000 # a default set by the Encryptor gem
80
+ key = self.class.otp_secret_encryption_key
81
+ salt = Base64.decode64(encrypted_otp_secret_salt)
82
+ iv = Base64.decode64(encrypted_otp_secret_iv)
83
+
84
+ raw_cipher_text = Base64.decode64(encrypted_otp_secret)
85
+ # The last 16 bytes of the ciphertext are the authentication tag - we use
86
+ # Galois Counter Mode which is an authenticated encryption mode
87
+ cipher_text = raw_cipher_text[0..-17]
88
+ auth_tag = raw_cipher_text[-16..-1]
89
+
90
+ # this alrorithm lifted from
91
+ # https://github.com/attr-encrypted/encryptor/blob/master/lib/encryptor.rb#L54
92
+
93
+ # create an OpenSSL object which will decrypt the AES cipher with 256 bit
94
+ # keys in Galois Counter Mode (GCM). See
95
+ # https://ruby.github.io/openssl/OpenSSL/Cipher.html
96
+ cipher = OpenSSL::Cipher.new('aes-256-gcm')
97
+
98
+ # tell the cipher we want to decrypt. Symmetric algorithms use a very
99
+ # similar process for encryption and decryption, hence the same object can
100
+ # do both.
101
+ cipher.decrypt
102
+
103
+ # Use a Password-Based Key Derivation Function to generate the key actually
104
+ # used for encryptoin from the key we got as input.
105
+ cipher.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(key, salt, hmac_iterations, cipher.key_len)
106
+
107
+ # set the Initialization Vector (IV)
108
+ cipher.iv = iv
109
+
110
+ # The tag must be set after calling Cipher#decrypt, Cipher#key= and
111
+ # Cipher#iv=, but before calling Cipher#final. After all decryption is
112
+ # performed, the tag is verified automatically in the call to Cipher#final.
113
+ #
114
+ # If the auth_tag does not verify, then #final will raise OpenSSL::Cipher::CipherError
115
+ cipher.auth_tag = auth_tag
116
+
117
+ # auth_data must be set after auth_tag has been set when decrypting See
118
+ # http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL/Cipher.html#method-i-auth_data-3D
119
+ # we are not adding any authenticated data but OpenSSL docs say this should
120
+ # still be called.
121
+ cipher.auth_data = ''
122
+
123
+ # #update is (somewhat confusingly named) the method which actually
124
+ # performs the decryption on the given chunk of data. Our OTP secret is
125
+ # short so we only need to call it once.
126
+ #
127
+ # It is very important that we call #final because:
128
+ #
129
+ # 1. The authentication tag is checked during the call to #final
130
+ # 2. Block based cipher modes (e.g. CBC) work on fixed size chunks. We need
131
+ # to call #final to get it to process the last chunk properly. The output
132
+ # of #final should be appended to the decrypted value. This isn't
133
+ # required for streaming cipher modes but including it is a best practice
134
+ # so that your code will continue to function correctly even if you later
135
+ # change to a block cipher mode.
136
+ cipher.update(cipher_text) + cipher.final
137
+ end
138
+ end
139
+ ```
140
+ 2. Set up [Rails encrypted secrets](https://edgeguides.rubyonrails.org/active_record_encryption.html)
141
+ ```bash
142
+ ./bin/rails db:encryption:init
143
+ # capture the output and put in encrypted credentials via
144
+ ./bin/rails credentials:edit
145
+ ```
146
+ 3. Complete your Rails 7 upgrade (making whatever other changes are required)
147
+
148
+ You can now deploy your upgraded application and devise-two-factor should work as before.
149
+
150
+ This gem will fall back to **reading** the OTP secret from the legacy columns if it cannot find one in the new `otp_secret` column. When you **write** a new OTP secret it will always be written to the new `otp_secret` column.
151
+
152
+ #### Phase 2: Clean up
153
+
154
+ This "clean up" phase can happen at the same time as your initial deployment but teams managing existing apps will likely want to do clean-up as separate, later deployments.
155
+
156
+ 1. Create a rake task to copy the OTP secret for each user from the legacy column to the new `otp_secret` column. This prepares the way for us to remove the legacy columns in a later step.
157
+ ```ruby
158
+ # lib/tasks/devise_two_factor_migration.rake
159
+
160
+ # Use this as a starting point for your task to migrate your user's OTP secrets.
161
+ namespace :devise_two_factor do
162
+ desc "Copy devise_two_factor OTP secret from old format to new format"
163
+ task copy_otp_secret_to_rails7_encrypted_attr: [:environment] do
164
+ # TODO: change User to your user model
165
+ User.find_each do |user| # find_each finds in batches of 1,000 by default
166
+ otp_secret = user.otp_secret # read from otp_secret column, fall back to legacy columns if new column is empty
167
+ puts "Processing #{user.email}"
168
+ user.update!(otp_secret: otp_secret)
169
+ end
170
+ end
171
+ end
172
+ ```
173
+ 1. Remove the `#legacy_otp_secret` method from your user model (e.g. `User`) because it is no longer required.
174
+ 1. Remove the now unused legacy columns from the database. This assumes you have run a rake task as in the previous step to migrate all the legacy stored secrets to the new storage.
175
+ ```bash
176
+ # TODO: replace 'Users' in migration name with the name of your user model
177
+ ./bin/rails g migration RemoveLegacyDeviseTwoFactorSecretsFromUsers
178
+ ```
179
+ which generates
180
+ ```ruby
181
+ class RemoveLegacyDeviseTwoFactorSecretsFromUsers < ActiveRecord::Migration[7.0]
182
+ def change
183
+ # TODO: change :users to whatever your users table is
184
+
185
+ # WARNING: Only run this when you are confident you have copied the OTP
186
+ # secret for ALL users from `encrypted_otp_secret` to `otp_secret`!
187
+ remove_column :users, :encrypted_otp_secret
188
+ remove_column :users, :encrypted_otp_secret_iv
189
+ remove_column :users, :encrypted_otp_secret_salt
190
+ end
191
+ end
192
+ ```
193
+
1
194
  # Guide to upgrading from 2.x to 3.x
2
195
 
3
196
  Pull request #76 allows for compatibility with `attr_encrypted` 3.0, which should be used due to a security vulnerability discovered in 2.0.
@@ -23,7 +23,6 @@ Gem::Specification.new do |s|
23
23
 
24
24
  s.add_runtime_dependency 'railties', '~> 7.0'
25
25
  s.add_runtime_dependency 'activesupport', '~> 7.0'
26
- s.add_runtime_dependency 'attr_encrypted', '>= 1.3', '< 5', '!= 2'
27
26
  s.add_runtime_dependency 'devise', '~> 4.0'
28
27
  s.add_runtime_dependency 'rotp', '~> 6.0'
29
28
 
@@ -12,10 +12,14 @@ module Devise
12
12
  mattr_accessor :otp_allowed_drift
13
13
  @@otp_allowed_drift = 30
14
14
 
15
- # The key used to encrypt OTP secrets in the database
15
+ # The key used to encrypt OTP secrets in the database in legacy installs.
16
16
  mattr_accessor :otp_secret_encryption_key
17
17
  @@otp_secret_encryption_key = nil
18
18
 
19
+ # These options are passed to the Rails 7+ encrypted attribute
20
+ mattr_accessor :otp_encrypted_attribute_options
21
+ @@otp_encrypted_attribute_options = {}
22
+
19
23
  # The length of all generated OTP backup codes
20
24
  mattr_accessor :otp_backup_code_length
21
25
  @@otp_backup_code_length = 16
@@ -7,25 +7,28 @@ module Devise
7
7
  include Devise::Models::DatabaseAuthenticatable
8
8
 
9
9
  included do
10
- unless %i[otp_secret otp_secret=].all? { |attr| method_defined?(attr) }
11
- require 'attr_encrypted'
12
-
13
- unless singleton_class.ancestors.include?(AttrEncrypted)
14
- extend AttrEncrypted
15
- end
16
-
17
- unless attr_encrypted?(:otp_secret)
18
- attr_encrypted :otp_secret,
19
- :key => self.otp_secret_encryption_key,
20
- :mode => :per_attribute_iv_and_salt unless self.attr_encrypted?(:otp_secret)
21
- end
22
- end
23
-
10
+ encrypts :otp_secret, **splattable_encrypted_attr_options
24
11
  attr_accessor :otp_attempt
25
12
  end
26
13
 
14
+ def otp_secret
15
+ # return the OTP secret stored as a Rails encrypted attribute if it
16
+ # exists. Otherwise return OTP secret stored by the `attr_encrypted` gem
17
+ return self[:otp_secret] if self[:otp_secret]
18
+
19
+ legacy_otp_secret
20
+ end
21
+
22
+ ##
23
+ # Decrypt and return the `encrypted_otp_secret` attribute which was used in
24
+ # prior versions of devise-two-factor
25
+ # See: # https://github.com/tinfoil/devise-two-factor/blob/main/UPGRADING.md
26
+ def legacy_otp_secret
27
+ nil
28
+ end
29
+
27
30
  def self.required_fields(klass)
28
- [:encrypted_otp_secret, :encrypted_otp_secret_iv, :encrypted_otp_secret_salt, :consumed_timestep]
31
+ [:otp_secret, :consumed_timestep]
29
32
  end
30
33
 
31
34
  # This defaults to the model's otp_secret
@@ -87,11 +90,21 @@ module Devise
87
90
  module ClassMethods
88
91
  Devise::Models.config(self, :otp_secret_length,
89
92
  :otp_allowed_drift,
93
+ :otp_encrypted_attribute_options,
90
94
  :otp_secret_encryption_key)
91
95
 
92
96
  def generate_otp_secret(otp_secret_length = self.otp_secret_length)
93
97
  ROTP::Base32.random_base32(otp_secret_length)
94
98
  end
99
+
100
+ # Return value will be splatted with ** so return a version of the
101
+ # encrypted attribute options which is always a Hash.
102
+ # @return [Hash]
103
+ def splattable_encrypted_attr_options
104
+ return {} if otp_encrypted_attribute_options.nil?
105
+
106
+ otp_encrypted_attribute_options
107
+ end
95
108
  end
96
109
  end
97
110
  end
@@ -8,7 +8,7 @@ RSpec.shared_examples 'two_factor_authenticatable' do
8
8
 
9
9
  describe 'required_fields' do
10
10
  it 'should have the attr_encrypted fields for otp_secret' do
11
- expect(Devise::Models::TwoFactorAuthenticatable.required_fields(subject.class)).to contain_exactly(:encrypted_otp_secret, :encrypted_otp_secret_iv, :encrypted_otp_secret_salt, :consumed_timestep)
11
+ expect(Devise::Models::TwoFactorAuthenticatable.required_fields(subject.class)).to contain_exactly(:otp_secret, :consumed_timestep)
12
12
  end
13
13
  end
14
14
 
@@ -16,18 +16,6 @@ RSpec.shared_examples 'two_factor_authenticatable' do
16
16
  it 'should be of the configured length' do
17
17
  expect(subject.otp_secret.length).to eq(subject.class.otp_secret_length)
18
18
  end
19
-
20
- it 'stores the encrypted otp_secret' do
21
- expect(subject.encrypted_otp_secret).to_not be_nil
22
- end
23
-
24
- it 'stores an iv for otp_secret' do
25
- expect(subject.encrypted_otp_secret_iv).to_not be_nil
26
- end
27
-
28
- it 'stores a salt for otp_secret' do
29
- expect(subject.encrypted_otp_secret_salt).to_not be_nil
30
- end
31
19
  end
32
20
 
33
21
  describe '#validate_and_consume_otp!' do
@@ -1,3 +1,3 @@
1
1
  module DeviseTwoFactor
2
- VERSION = '4.1.1'.freeze
2
+ VERSION = '5.0.0'.freeze
3
3
  end
@@ -3,8 +3,6 @@ require 'rails/generators'
3
3
  module DeviseTwoFactor
4
4
  module Generators
5
5
  class DeviseTwoFactorGenerator < Rails::Generators::NamedBase
6
- argument :encryption_key_env, :type => :string, :required => true
7
-
8
6
  desc 'Creates a migration to add the required attributes to NAME, and ' \
9
7
  'adds the necessary Devise directives to the model'
10
8
 
@@ -19,9 +17,7 @@ module DeviseTwoFactor
19
17
  def create_devise_two_factor_migration
20
18
  migration_arguments = [
21
19
  "add_devise_two_factor_to_#{plural_name}",
22
- "encrypted_otp_secret:string",
23
- "encrypted_otp_secret_iv:string",
24
- "encrypted_otp_secret_salt:string",
20
+ "otp_secret:string",
25
21
  "consumed_timestep:integer",
26
22
  "otp_required_for_login:boolean"
27
23
  ]
@@ -51,8 +47,7 @@ module DeviseTwoFactor
51
47
  indent_depth = class_path.size
52
48
 
53
49
  content = [
54
- "devise :two_factor_authenticatable,",
55
- " :otp_secret_encryption_key => ENV['#{encryption_key_env}']\n"
50
+ "devise :two_factor_authenticatable"
56
51
  ]
57
52
 
58
53
  content << "attr_accessible :otp_attempt\n" if needs_attr_accessible?
@@ -6,34 +6,15 @@ class TwoFactorAuthenticatableDouble
6
6
  include ::ActiveModel::Validations::Callbacks
7
7
  extend ::Devise::Models
8
8
 
9
- define_model_callbacks :update
10
-
11
- devise :two_factor_authenticatable, :otp_secret_encryption_key => 'test-key'*4
12
-
13
- attr_accessor :consumed_timestep
14
-
15
- def save(validate)
16
- # noop for testing
17
- true
9
+ # stub out the ::ActiveRecord::Encryption::EncryptableRecord API
10
+ attr_accessor :otp_secret
11
+ def self.encrypts(*attrs)
12
+ nil
18
13
  end
19
- end
20
-
21
- class TwoFactorAuthenticatableWithCustomizeAttrEncryptedDouble
22
- extend ::ActiveModel::Callbacks
23
- include ::ActiveModel::Validations::Callbacks
24
-
25
- # like https://github.com/tinfoil/devise-two-factor/blob/cf73e52043fbe45b74d68d02bc859522ad22fe73/UPGRADING.md#guide-to-upgrading-from-2x-to-3x
26
- extend ::AttrEncrypted
27
- attr_encrypted :otp_secret,
28
- :key => 'test-key'*8,
29
- :mode => :per_attribute_iv_and_salt,
30
- :algorithm => 'aes-256-cbc'
31
-
32
- extend ::Devise::Models
33
14
 
34
15
  define_model_callbacks :update
35
16
 
36
- devise :two_factor_authenticatable, :otp_secret_encryption_key => 'test-key'*4
17
+ devise :two_factor_authenticatable
37
18
 
38
19
  attr_accessor :consumed_timestep
39
20
 
@@ -51,49 +32,6 @@ describe ::Devise::Models::TwoFactorAuthenticatable do
51
32
  end
52
33
  end
53
34
 
54
- describe ::Devise::Models::TwoFactorAuthenticatable do
55
- context 'When included in a class' do
56
- subject { TwoFactorAuthenticatableWithCustomizeAttrEncryptedDouble.new }
57
-
58
- it_behaves_like 'two_factor_authenticatable'
59
-
60
- before :each do
61
- subject.otp_secret = subject.class.generate_otp_secret
62
- subject.consumed_timestep = nil
63
- end
64
-
65
- describe 'otp_secret options' do
66
- it 'should be of the key' do
67
- if attr_encrypted_is_rails_seven_compatible?
68
- expect(subject.attr_encrypted_encrypted_attributes[:otp_secret][:key]).to eq('test-key'*8)
69
- else
70
- expect(subject.encrypted_attributes[:otp_secret][:key]).to eq('test-key'*8)
71
- end
72
- end
73
-
74
- it 'should be of the mode' do
75
- if attr_encrypted_is_rails_seven_compatible?
76
- expect(subject.attr_encrypted_encrypted_attributes[:otp_secret][:mode]).to eq(:per_attribute_iv_and_salt)
77
- else
78
- expect(subject.encrypted_attributes[:otp_secret][:mode]).to eq(:per_attribute_iv_and_salt)
79
- end
80
- end
81
-
82
- it 'should be of the mode' do
83
- if attr_encrypted_is_rails_seven_compatible?
84
- expect(subject.attr_encrypted_encrypted_attributes[:otp_secret][:algorithm]).to eq('aes-256-cbc')
85
- else
86
- expect(subject.encrypted_attributes[:otp_secret][:algorithm]).to eq('aes-256-cbc')
87
- end
88
- end
89
-
90
- def attr_encrypted_is_rails_seven_compatible?
91
- Gem::Version.new(AttrEncrypted::Version.string) >= Gem::Version.new('4.0.0')
92
- end
93
- end
94
- end
95
- end
96
-
97
35
  describe ::Devise::Models::TwoFactorAuthenticatable do
98
36
  context 'When clean_up_passwords is called ' do
99
37
  subject { TwoFactorAuthenticatableDouble.new }
@@ -101,11 +39,11 @@ describe ::Devise::Models::TwoFactorAuthenticatable do
101
39
  subject.otp_attempt = 'foo'
102
40
  subject.password_confirmation = 'foo'
103
41
  end
104
- it 'otp_attempt should be nill' do
42
+ it 'otp_attempt should be nill' do
105
43
  subject.clean_up_passwords
106
44
  expect(subject.otp_attempt).to be_nil
107
45
  end
108
- it 'password_confirmation should be nill' do
46
+ it 'password_confirmation should be nill' do
109
47
  subject.clean_up_passwords
110
48
  expect(subject.password_confirmation).to be_nil
111
49
  end
@@ -6,10 +6,15 @@ class TwoFactorBackupableDouble
6
6
  include ::ActiveModel::Validations::Callbacks
7
7
  extend ::Devise::Models
8
8
 
9
+ # stub out the ::ActiveRecord::Encryption::EncryptableRecord API
10
+ attr_accessor :otp_secret
11
+ def self.encrypts(*attrs)
12
+ nil
13
+ end
14
+
9
15
  define_model_callbacks :update
10
16
 
11
- devise :two_factor_authenticatable, :two_factor_backupable,
12
- :otp_secret_encryption_key => 'test-key'*4
17
+ devise :two_factor_authenticatable, :two_factor_backupable
13
18
 
14
19
  attr_accessor :otp_backup_codes
15
20
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise-two-factor
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.1
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shane Wilton
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain:
11
11
  - |
@@ -86,7 +86,7 @@ cert_chain:
86
86
  vqIDv6JBG9I16h/HhchntKfM58MI1bNZFBSdZqYOJiL8JIjP8HNIk76Y366ppG29
87
87
  EhBYYg==
88
88
  -----END CERTIFICATE-----
89
- date: 2023-10-12 00:00:00.000000000 Z
89
+ date: 2022-07-11 00:00:00.000000000 Z
90
90
  dependencies:
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: railties
@@ -116,32 +116,6 @@ dependencies:
116
116
  - - "~>"
117
117
  - !ruby/object:Gem::Version
118
118
  version: '7.0'
119
- - !ruby/object:Gem::Dependency
120
- name: attr_encrypted
121
- requirement: !ruby/object:Gem::Requirement
122
- requirements:
123
- - - ">="
124
- - !ruby/object:Gem::Version
125
- version: '1.3'
126
- - - "!="
127
- - !ruby/object:Gem::Version
128
- version: '2'
129
- - - "<"
130
- - !ruby/object:Gem::Version
131
- version: '5'
132
- type: :runtime
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - ">="
137
- - !ruby/object:Gem::Version
138
- version: '1.3'
139
- - - "!="
140
- - !ruby/object:Gem::Version
141
- version: '2'
142
- - - "<"
143
- - !ruby/object:Gem::Version
144
- version: '5'
145
119
  - !ruby/object:Gem::Dependency
146
120
  name: devise
147
121
  requirement: !ruby/object:Gem::Requirement
@@ -301,7 +275,7 @@ homepage: https://github.com/tinfoil/devise-two-factor
301
275
  licenses:
302
276
  - MIT
303
277
  metadata: {}
304
- post_install_message:
278
+ post_install_message:
305
279
  rdoc_options: []
306
280
  require_paths:
307
281
  - lib
@@ -316,8 +290,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
316
290
  - !ruby/object:Gem::Version
317
291
  version: '0'
318
292
  requirements: []
319
- rubygems_version: 3.0.3.1
320
- signing_key:
293
+ rubygems_version: 3.2.32
294
+ signing_key:
321
295
  specification_version: 4
322
296
  summary: Barebones two-factor authentication with Devise
323
297
  test_files:
metadata.gz.sig CHANGED
Binary file