devise-two-factor 5.0.0 → 6.1.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +6 -0
  3. data/.github/workflows/ci.yml +6 -3
  4. data/.github/workflows/push.yml +28 -0
  5. data/.markdownlint.json +6 -0
  6. data/Appraisals +12 -32
  7. data/CHANGELOG.md +21 -0
  8. data/README.md +75 -30
  9. data/Rakefile +2 -0
  10. data/SECURITY.md +5 -0
  11. data/UPGRADING.md +38 -15
  12. data/devise-two-factor.gemspec +7 -13
  13. data/gemfiles/rails_7.0.gemfile +2 -2
  14. data/gemfiles/{rails_4.1.gemfile → rails_7.1.gemfile} +2 -2
  15. data/gemfiles/{rails_4.2.gemfile → rails_7.2.gemfile} +2 -2
  16. data/gemfiles/{rails_5.0.gemfile → rails_8.0.gemfile} +2 -2
  17. data/lib/devise-two-factor.rb +6 -4
  18. data/lib/devise_two_factor/models/two_factor_authenticatable.rb +4 -2
  19. data/lib/devise_two_factor/models/two_factor_backupable.rb +3 -2
  20. data/lib/devise_two_factor/spec_helpers/two_factor_authenticatable_shared_examples.rb +5 -5
  21. data/lib/devise_two_factor/spec_helpers/two_factor_backupable_shared_examples.rb +2 -2
  22. data/lib/devise_two_factor/strategies/two_factor_authenticatable.rb +1 -1
  23. data/lib/devise_two_factor/strategies/two_factor_backupable.rb +0 -3
  24. data/lib/devise_two_factor/version.rb +1 -1
  25. data/spec/devise/models/two_factor_authenticatable_spec.rb +5 -1
  26. data/spec/devise/models/two_factor_backupable_spec.rb +4 -0
  27. data/spec/spec_helper.rb +0 -1
  28. metadata +40 -107
  29. checksums.yaml.gz.sig +0 -0
  30. data/certs/tinfoil-cacert.pem +0 -41
  31. data/certs/tinfoilsecurity-gems-cert.pem +0 -35
  32. data/gemfiles/rails_5.1.gemfile +0 -8
  33. data/gemfiles/rails_5.2.gemfile +0 -8
  34. data/gemfiles/rails_6.0.gemfile +0 -8
  35. data/gemfiles/rails_6.1.gemfile +0 -8
  36. data.tar.gz.sig +0 -0
  37. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0f81d936ba021c504827ebf9a6faa199f7a0a8f714fee2d9ce6d48acbde423b
4
- data.tar.gz: 38bf04b9361f64618c84081c5ce5436f523f8476c625b91b92cfba8e56e2cd5c
3
+ metadata.gz: 84d4fba8bbdcee4f8d8b00c5d2662dc5e8d78cc60e7f0502c7e0e9d5fc93a4d4
4
+ data.tar.gz: 33a77876910f588992917ebb123148f26d82b26fbe713f944e59f3b720c4978c
5
5
  SHA512:
6
- metadata.gz: 54b62797c0194f8a3dc04f4594db384bdf6421eaf35707b7a35a39dc3993348790f1544e24d9d013885a7569a4c2381f938037626c26bf054ca00fe02bc46026
7
- data.tar.gz: 2c24d3d5e822151f323ba27efb915ad44a33be2a20b95b3decad88facf34c68a70bbf471b1d748bd2c2498a088b5ddc0fb333486d467eb9865dd3f6aa941694c
6
+ metadata.gz: d2f69c9f760278e3a1a40d5913b786468d9d4d12b54e93dd5d5437cfb5edc669f21a56953ea817d66722cbe305190762dcb86cef80009a0fff718e86a39726be
7
+ data.tar.gz: e3086e4034e6208e064f81e5845b1027b5e7c19d38ddd4788c615fe1a837772f1330aba234a30fb3f7f9cc549dbed3864da9f70249aef04b9efa066c8284733c
@@ -0,0 +1,6 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
@@ -12,14 +12,17 @@ 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.7', '3.0', '3.1', 'truffleruby-head']
16
- rails: ['7.0']
15
+ ruby: ['3.1', '3.2', '3.3', 'truffleruby-head']
16
+ rails: ['7.0', '7.1', '7.2', '8.0']
17
+ exclude:
18
+ - ruby: '3.1'
19
+ rails: '8.0'
17
20
 
18
21
  name: Ruby ${{ matrix.ruby }}, Rails ${{ matrix.rails }}
19
22
  env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
20
23
  BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/rails_${{ matrix.rails }}.gemfile
21
24
  steps:
22
- - uses: actions/checkout@v2
25
+ - uses: actions/checkout@v4
23
26
  - name: Set up Ruby
24
27
  uses: ruby/setup-ruby@v1
25
28
  with:
@@ -0,0 +1,28 @@
1
+ name: Push Gem
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - v*
7
+
8
+ jobs:
9
+ deployment:
10
+ name: Push gem to RubyGems.org
11
+ environment: RubyGems
12
+ runs-on: ubuntu-latest
13
+
14
+ permissions:
15
+ id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
16
+ contents: write # IMPORTANT: this permission is required for `rake release` to push the release tag
17
+
18
+ steps:
19
+ # Set up
20
+ - uses: actions/checkout@v4
21
+ - name: Set up Ruby
22
+ uses: ruby/setup-ruby@v1
23
+ with:
24
+ bundler-cache: true
25
+ ruby-version: ruby
26
+
27
+ # Release
28
+ - uses: rubygems/release-gem@v1
@@ -0,0 +1,6 @@
1
+ {
2
+ "MD026": false,
3
+ "MD029": false,
4
+ "MD031": false,
5
+ "MD034": false
6
+ }
data/Appraisals CHANGED
@@ -1,39 +1,19 @@
1
- appraise "rails-4.1" do
2
- gem 'railties', '~> 4.1'
3
- gem 'activesupport', '~> 4.1'
4
- end
5
-
6
- appraise "rails-4.2" do
7
- gem 'railties', '~> 4.2'
8
- gem 'activesupport', '~> 4.2'
9
- end
10
-
11
- appraise "rails-5.0" do
12
- gem 'railties', '~> 5.0'
13
- gem 'activesupport', '~> 5.0'
14
- end
15
-
16
- appraise "rails-5.1" do
17
- gem 'railties', '~> 5.1'
18
- gem 'activesupport', '~> 5.1'
19
- end
20
-
21
- appraise "rails-5.2" do
22
- gem 'railties', '~> 5.2'
23
- gem 'activesupport', '~> 5.2'
1
+ appraise "rails-7.0" do
2
+ gem 'railties', '~> 7.0.0'
3
+ gem 'activesupport', '~> 7.0.0'
24
4
  end
25
5
 
26
- appraise "rails-6.0" do
27
- gem 'railties', '~> 6.0'
28
- gem 'activesupport', '~> 6.0'
6
+ appraise "rails-7.1" do
7
+ gem 'railties', '~> 7.1.0'
8
+ gem 'activesupport', '~> 7.1.0'
29
9
  end
30
10
 
31
- appraise "rails-6.1" do
32
- gem 'railties', '~> 6.1'
33
- gem 'activesupport', '~> 6.1'
11
+ appraise "rails-7.2" do
12
+ gem 'railties', '~> 7.2.0'
13
+ gem 'activesupport', '~> 7.2.0'
34
14
  end
35
15
 
36
- appraise "rails-7.0" do
37
- gem 'railties', '~> 7.0'
38
- gem 'activesupport', '~> 7.0'
16
+ appraise "rails-8.0" do
17
+ gem 'railties', '~> 8.0.0'
18
+ gem 'activesupport', '~> 8.0.0'
39
19
  end
data/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 6.0.0
6
+
7
+ **Breaking Changes**
8
+ - `otp_secret_length` and `otp_backup_code_length` options have changed to be the number of random bytes that are generated. See [UPGRADING.md](UPGRADING.md).
9
+ - `consume_otp!` and `invalidate_otp_backup_code!` now call `save!` instead of `save`. See [UPGRADING.md](UPGRADING.md).
10
+
11
+ ## 5.1.0
12
+
13
+ - Remove faker dev dependency
14
+ - Insert two_factor_authenticatable at the top of the devise module list
15
+ - README and CI improvements
16
+
17
+ ## 5.0.0
18
+
19
+ **Breaking Changes**
20
+ - attr_encrypted has been deprecated in favor of native Rails attribute encryption. See [UPGRADING.md](UPGRADING.md) for details on how to migrate your records. You **must** use or build a migration strategy (see examples in [UPGRADING.md](UPGRADING.md)) to use existing data!
21
+ - Rails 7 is now required.
22
+
23
+ ## 4.1.0 / 4.1.1
24
+ - Add support for attr_encrypted v4
25
+
5
26
  ## 4.0.2
6
27
  - Add Rails 7.0 support
7
28
  - Renew signing certificate
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # Devise-Two-Factor Authentication
2
- By [Tinfoil Security](https://www.tinfoilsecurity.com/) (acq. [Synopsys](https://www.synopsys.com/) 2020). Interested in [working with us](https://www.synopsys.com/careers.html)? We're hiring!
3
2
 
4
- ![Build Status](https://github.com/tinfoil/devise-two-factor/actions/workflows/ci.yml/badge.svg)
3
+ ![Build Status](https://github.com/devise-two-factor/devise-two-factor/actions/workflows/ci.yml/badge.svg)
5
4
 
6
5
  Devise-Two-Factor is a minimalist extension to Devise which offers support for two-factor authentication, through the [TOTP](https://en.wikipedia.org/wiki/Time-based_One-Time_Password) scheme. It:
7
6
 
@@ -11,12 +10,12 @@ Devise-Two-Factor is a minimalist extension to Devise which offers support for t
11
10
  * Is extensible, and includes two-factor backup codes as an example of how plugins can be structured
12
11
 
13
12
  ## Contributing
13
+
14
14
  We welcome pull requests, bug reports, and other contributions. We're especially looking for help getting this gem fully compatible with Rails 5+ and squashing any deprecation messages.
15
15
 
16
16
  ## Example App
17
- An example Rails 4 application is provided in the `demo` directory. It showcases a minimal example of Devise-Two-Factor in action, and can act as a reference for integrating the gem into your own application.
18
17
 
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`.
18
+ See [examples](demo/README.md).
20
19
 
21
20
  ## Getting Started
22
21
 
@@ -27,15 +26,31 @@ Devise-Two-Factor doesn't require much to get started, but there are two prerequ
27
26
 
28
27
  First, you'll need a Rails application setup with Devise. Visit the Devise [homepage](https://github.com/plataformatec/devise) for instructions.
29
28
 
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:
29
+ Devise-Two-Factor uses [ActiveRecord encrypted attributes](https://edgeguides.rubyonrails.org/active_record_encryption.html). If you haven't already set up ActiveRecord encryption you must generate a key set and configure your application to use them either with Rails' encrypted credentials or from another source such as environment variables.
31
30
 
32
31
  ```bash
33
- # generate suitable encryption secrets to stdout
34
- $ ./bin/rails db:encryption:init
32
+ # Generates a random key set and outputs it to stdout
33
+ ./bin/rails db:encryption:init
34
+ ```
35
+
36
+ You can load the key set using Rails' credentials.
37
+
38
+ ```bash
39
+ # Copy the generated key set into your encrypted credentials file
40
+ # Setting the EDITOR environment variable is optional, but without it your default editor will open
41
+ EDITOR="code --wait" ./bin/rails credentials:edit
42
+ ```
43
+
44
+ To learn more about credentials run `./bin/rails credentials:help`.
35
45
 
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
46
+ Alternatively, you can configure your application with environment variables rather than Rails' credentials.
47
+
48
+ ```ruby
49
+ # Copy the generate key set and set them as environment variables
50
+
51
+ config.active_record.encryption.primary_key = ENV['ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY']
52
+ config.active_record.encryption.deterministic_key = ENV['ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY']
53
+ config.active_record.encryption.key_derivation_salt = ENV['ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT']
39
54
  ```
40
55
 
41
56
  Add Devise-Two-Factor to your Gemfile with:
@@ -58,11 +73,13 @@ Where `MODEL` is the name of the model you wish to add two-factor functionality
58
73
  This generator will:
59
74
 
60
75
  1. Create a new migration which adds a few columns to the specified model:
76
+
61
77
  ```ruby
62
78
  add_column :users, :otp_secret, :string
63
79
  add_column :users, :consumed_timestep, :integer
64
80
  add_column :users, :otp_required_for_login, :boolean
65
81
  ```
82
+
66
83
  1. Edit `app/models/MODEL.rb` (where MODEL is your model name):
67
84
  * add the `:two_factor_authenticatable` devise module
68
85
  * remove the `:database_authenticatable` if present because it is incompatible with `:two_factor_authenticatable`
@@ -95,6 +112,7 @@ Finally you should verify that `:database_authenticatable` is **not** being load
95
112
  **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!
96
113
 
97
114
  ## Designing Your Workflow
115
+
98
116
  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.
99
117
 
100
118
  There are two key workflows you'll have to think about:
@@ -104,8 +122,8 @@ There are two key workflows you'll have to think about:
104
122
 
105
123
  We chose to keep things as simple as possible, and our implementation can be found by registering at [Tinfoil Security](https://www.tinfoilsecurity.com/), and enabling two-factor authentication from the [security settings page](https://www.tinfoilsecurity.com/account/security).
106
124
 
107
-
108
125
  ### Logging In
126
+
109
127
  Logging in with two-factor authentication works extremely similarly to regular database authentication in Devise. The `TwoFactorAuthenticatable` strategy accepts three parameters:
110
128
 
111
129
  1. email
@@ -115,11 +133,13 @@ Logging in with two-factor authentication works extremely similarly to regular d
115
133
  These parameters can be submitted to the standard Devise login route, and the strategy will handle the authentication of the user for you.
116
134
 
117
135
  ### Disabling Automatic Login After Password Resets
136
+
118
137
  If you use the Devise `recoverable` strategy, the default behavior after a password reset is to automatically authenticate the user and log them in. This is obviously a problem if a user has two-factor authentication enabled, as resetting the password would get around the two-factor requirement.
119
138
 
120
139
  Because of this, you need to set `sign_in_after_reset_password` to `false` (either globally in your Devise initializer or via `devise_for`).
121
140
 
122
141
  ### Enabling Two-Factor Authentication
142
+
123
143
  Enabling two-factor authentication for a user is easy. For example, if my user model were named User, I could do the following:
124
144
 
125
145
  ```ruby
@@ -150,6 +170,7 @@ current_user.current_otp
150
170
  ```
151
171
 
152
172
  The generated code will be valid for the duration specified by `otp_allowed_drift`. This value can be modified by adding a config in `config/initializers/devise.rb`.
173
+
153
174
  ```ruby
154
175
  Devise.otp_allowed_drift = 240 # value in seconds
155
176
  Devise.setup do |config|
@@ -166,13 +187,27 @@ However you decide to handle enrollment, there are a few important consideration
166
187
  It sounds like a lot of work, but most of these problems have been very elegantly solved by other people. We recommend taking a look at the excellent workflows used by Heroku and Google for inspiration.
167
188
 
168
189
  ### Filtering sensitive parameters from the logs
190
+
169
191
  To prevent two-factor authentication codes from leaking if your application logs get breached, you'll want to filter sensitive parameters from the Rails logs. Add the following to `config/initializers/filter_parameter_logging.rb`:
170
192
 
171
193
  ```ruby
172
194
  Rails.application.config.filter_parameters += [:otp_attempt]
173
195
  ```
174
196
 
197
+ ### Preventing Brute-Force Attacks
198
+
199
+ With any authentication solution it is also important to protect your users from brute-force attacks. For Devise-Two-Factor specifically if a user's username and password have already been compromised an attacker would be able to try possible TOTP codes and see if they can hit a lucky collision to log in. While Devise-Two-Factor is open-ended by design and cannot solve this for all applications natively there are some possible mitigations to consider. A non-exhaustive list follows:
200
+
201
+ 1. Use the `lockable` strategy from Devise to lock a user after a certain number of failed login attempts. See https://www.rubydoc.info/github/heartcombo/devise/main/Devise/Models/Lockable for more information.
202
+ 2. Configure a rate limit for your application, especially on the endpoints used to log in. One such library to accomplish this is [rack-attack](https://rubygems.org/gems/rack-attack).
203
+ 3. When displaying authentication errors hide whether validating a username/password combination failed or a two-factor code failed behind a more generic error message.
204
+
205
+ #### Acknowledgements
206
+
207
+ Thank you to Christian Reitter (Radically Open Security) and Chris MacNaughton (Centauri Solutions) for reporting the issue.
208
+
175
209
  ## Backup Codes
210
+
176
211
  Devise-Two-Factor is designed with extensibility in mind. One such extension, `TwoFactorBackupable`, is included and serves as a good example of how to extend this gem. This plugin allows you to add the ability to generate single-use backup codes for a user, which they may use to bypass two-factor authentication, in the event that they lose access to their device.
177
212
 
178
213
  To install it, you need to add the `:two_factor_backupable` directive to your model.
@@ -187,17 +222,39 @@ You'll also be required to enable the `:two_factor_backupable` strategy, by addi
187
222
  manager.default_strategies(:scope => :user).unshift :two_factor_backupable
188
223
  ```
189
224
 
190
- The final installation step is dependent on your version of Rails. If you're not running Rails 4, skip to the next section. Otherwise, create the following migration:
225
+ ### Migration
226
+
227
+ The final installation step may be dependent on your version of Rails.
228
+
229
+ #### PostgreSQL
191
230
 
192
231
  ```ruby
193
232
  class AddDeviseTwoFactorBackupableToUsers < ActiveRecord::Migration
194
233
  def change
195
- # Change type from :string to :text if using MySQL database
196
234
  add_column :users, :otp_backup_codes, :string, array: true
197
235
  end
198
236
  end
199
237
  ```
200
238
 
239
+ #### MySQL
240
+
241
+ ```ruby
242
+ # migration
243
+ class AddDeviseTwoFactorBackupableToUsers < ActiveRecord::Migration
244
+ def change
245
+ add_column :users, :otp_backup_codes, :text
246
+ end
247
+ end
248
+
249
+ # model
250
+ class User < ApplicationRecord
251
+ devise :two_factor_backupable
252
+ serialize :otp_backup_codes, Array
253
+ end
254
+ ```
255
+
256
+ ### Generation
257
+
201
258
  You can then generate backup codes for a user:
202
259
 
203
260
  ```ruby
@@ -215,23 +272,8 @@ devise :two_factor_backupable, otp_backup_code_length: 32,
215
272
  otp_number_of_backup_codes: 10
216
273
  ```
217
274
 
218
- ### Help! I'm not using Rails 4.0!
219
- Don't worry! `TwoFactorBackupable` stores the backup codes as an array of strings in the database. In Rails 4.0 this is supported natively, but in earlier versions you can use a gem to emulate this behavior: we recommend [activerecord-postgres-array](https://github.com/tlconnor/activerecord-postgres-array).
220
-
221
- You'll then simply have to create a migration to add an array named `otp_backup_codes` to your model. If you use the above gem, this migration might look like:
222
-
223
- ```ruby
224
- class AddTwoFactorBackupCodesToUsers < ActiveRecord::Migration
225
- def change
226
- # Change type from :string_array to :text_array if using MySQL database
227
- add_column :users, :otp_backup_codes, :string_array
228
- end
229
- end
230
- ```
231
-
232
- Now just continue with the setup in the previous section, skipping the generator step.
233
-
234
275
  ## Testing
276
+
235
277
  Devise-Two-Factor includes shared-examples for both `TwoFactorAuthenticatable` and `TwoFactorBackupable`. Adding the following two lines to the specs for your two-factor enabled models will allow you to test your models for two-factor functionality:
236
278
 
237
279
  ```ruby
@@ -242,6 +284,7 @@ it_behaves_like "two_factor_backupable"
242
284
  ```
243
285
 
244
286
  ## Troubleshooting
287
+
245
288
  If you are using Rails 4.x and Ruby >= 2.7, you may get an error like
246
289
 
247
290
  ```
@@ -251,9 +294,11 @@ Failure/Error: require 'devise'
251
294
  NoMethodError:
252
295
  undefined method `new' for BigDecimal:Class
253
296
  ```
297
+
254
298
  see https://github.com/ruby/bigdecimal#which-version-should-you-select and https://github.com/ruby/bigdecimal/issues/127
255
299
  for more details, but you should be able to solve this
256
300
  by explicitly requiring an older version of bigdecimal in your gemfile like
257
- ```
301
+
302
+ ```ruby
258
303
  gem "bigdecimal", "~> 1.4"
259
304
  ```
data/Rakefile CHANGED
@@ -11,6 +11,8 @@ rescue Bundler::BundlerError => e
11
11
  end
12
12
  require 'rake'
13
13
 
14
+ require 'bundler/gem_tasks'
15
+
14
16
  require 'rspec/core'
15
17
  require 'rspec/core/rake_task'
16
18
  RSpec::Core::RakeTask.new(:spec) do |spec|
data/SECURITY.md ADDED
@@ -0,0 +1,5 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ Please report any vulnerabilities to the [Black Duck PSIRT](psirt@blackduck.com).
data/UPGRADING.md CHANGED
@@ -1,8 +1,25 @@
1
- # Upgrading from 4.x to 5.x
1
+ # Upgrading
2
2
 
3
- ## Background
3
+ ## Upgrading from 5.x to 6.x
4
4
 
5
- ### Database columns in version 4.x and older
5
+ ### save!
6
+
7
+ `consume_otp!` and `invalidate_otp_backup_code!` now call `save!` instead of `save` (or nothing at all in the case of `invalidate_otp_backup_code!`). If you manually called `save`/`save!` after calling `invalidate_otp_backup_code!` you may be able to remove it.
8
+
9
+ ### Secret Lengths
10
+
11
+ The `otp_secret_length` and `otp_backup_code_length` options have changed to be the number of random bytes that are generated.
12
+ If you had configured these values you may want to change them if you wish to keep the same output length.
13
+
14
+ `otp_secret_length` now has a default value of 20, generating a 160 bit secret key with an output length length of 32 bytes.
15
+
16
+ `otp_backup_code_length` now has a default value of 16, generating a 32 byte backup code.
17
+
18
+ ## Upgrading from 4.x to 5.x
19
+
20
+ ### Background
21
+
22
+ #### Database columns in version 4.x and older
6
23
 
7
24
  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
25
 
@@ -21,7 +38,7 @@ otp_required_for_login
21
38
 
22
39
  A fresh install of 4.x would create all five of the database columns above.
23
40
 
24
- ### Database columns in version 5.x and later
41
+ #### Database columns in version 5.x and later
25
42
 
26
43
  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
44
 
@@ -33,18 +50,15 @@ consumed_timestep
33
50
  otp_required_for_login
34
51
  ```
35
52
 
36
- ### Upgrading from 4.x to 5.x
37
-
38
-
39
53
  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
54
 
41
- #### Assumptions
55
+ ### Assumptions
42
56
 
43
57
  This guide assumes you are upgrading an existing Rails 6 app (with `devise` and `devise-two-factor`) to Rails 7.
44
58
 
45
59
  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
60
 
47
- #### Phase 1: Upgrading devise-two-factor as part of Rails 7 upgrade
61
+ ### Phase 1: Upgrading devise-two-factor as part of Rails 7 upgrade
48
62
 
49
63
  1. Update the version constraint for Rails in your `Gemfile` to your desired version e.g. `gem "rails", "~> 7.0.3"`
50
64
  1. Run `bundle install` and resolve any issues with dependencies.
@@ -59,7 +73,7 @@ This gem must be upgraded **as part of a Rails 7 upgrade**. See [the official Ra
59
73
  ```
60
74
  1. Add a `legacy_otp_secret` method to your user model e.g. `User`.
61
75
  * 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).
76
+ * The implementation shown below works if you set up devise-two-factor with the settings suggested in the [OLD README](https://github.com/devise-two-factor/devise-two-factor/blob/8d74f5ee45594bf00e60d5d49eb6fcde82c2d2ba/README.md).
63
77
  * If you have customised the encryption scheme used to store the OTP secret then you will need to update this method to match.
64
78
  * 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
79
  ```ruby
@@ -87,7 +101,7 @@ This gem must be upgraded **as part of a Rails 7 upgrade**. See [the official Ra
87
101
  cipher_text = raw_cipher_text[0..-17]
88
102
  auth_tag = raw_cipher_text[-16..-1]
89
103
 
90
- # this alrorithm lifted from
104
+ # this algorithm lifted from
91
105
  # https://github.com/attr-encrypted/encryptor/blob/master/lib/encryptor.rb#L54
92
106
 
93
107
  # create an OpenSSL object which will decrypt the AES cipher with 256 bit
@@ -101,7 +115,7 @@ This gem must be upgraded **as part of a Rails 7 upgrade**. See [the official Ra
101
115
  cipher.decrypt
102
116
 
103
117
  # Use a Password-Based Key Derivation Function to generate the key actually
104
- # used for encryptoin from the key we got as input.
118
+ # used for encryption from the key we got as input.
105
119
  cipher.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(key, salt, hmac_iterations, cipher.key_len)
106
120
 
107
121
  # set the Initialization Vector (IV)
@@ -149,7 +163,7 @@ You can now deploy your upgraded application and devise-two-factor should work a
149
163
 
150
164
  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
165
 
152
- #### Phase 2: Clean up
166
+ ### Phase 2: Clean up
153
167
 
154
168
  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
169
 
@@ -190,8 +204,17 @@ This "clean up" phase can happen at the same time as your initial deployment but
190
204
  end
191
205
  end
192
206
  ```
207
+ 1. Remove `otp_secret_encryption_key` from the model setup. This also assumes you successfully ran the rake task in step 1.
208
+ ```ruby
209
+ # from this:
210
+ devise :two_factor_authenticatable,
211
+ otp_secret_encryption_key: ENV['YOUR_ENCRYPTION_KEY_HERE']
212
+
213
+ # to this:
214
+ devise :two_factor_authenticatable
215
+ ```
193
216
 
194
- # Guide to upgrading from 2.x to 3.x
217
+ ## Upgrading from 2.x to 3.x
195
218
 
196
219
  Pull request #76 allows for compatibility with `attr_encrypted` 3.0, which should be used due to a security vulnerability discovered in 2.0.
197
220
 
@@ -211,7 +234,7 @@ class User < ActiveRecord::Base
211
234
  :otp_secret_encryption_key => ENV['DEVISE_TWO_FACTOR_ENCRYPTION_KEY']
212
235
  ```
213
236
 
214
- # Guide to upgrading from 1.x to 2.x
237
+ ## Upgrading from 1.x to 2.x
215
238
 
216
239
  Pull request #43 added a new field to protect against "shoulder-surfing" attacks. If upgrading, you'll need to add the `:consumed_timestep` column to your `Users` model.
217
240
 
@@ -5,24 +5,18 @@ Gem::Specification.new do |s|
5
5
  s.name = 'devise-two-factor'
6
6
  s.version = DeviseTwoFactor::VERSION.dup
7
7
  s.platform = Gem::Platform::RUBY
8
- s.licenses = ['MIT']
8
+ s.license = 'MIT'
9
9
  s.summary = 'Barebones two-factor authentication with Devise'
10
- s.email = 'engineers@tinfoilsecurity.com'
11
- s.homepage = 'https://github.com/tinfoil/devise-two-factor'
12
- s.description = 'Barebones two-factor authentication with Devise'
13
- s.authors = ['Shane Wilton']
10
+ s.homepage = 'https://github.com/devise-two-factor/devise-two-factor'
11
+ s.description = 'Devise-Two-Factor is a minimalist extension to Devise which offers support for two-factor authentication through the TOTP scheme.'
12
+ s.authors = ['Quinn Wilton']
14
13
 
15
- s.cert_chain = [
16
- 'certs/tinfoil-cacert.pem',
17
- 'certs/tinfoilsecurity-gems-cert.pem'
18
- ]
19
- s.signing_key = File.expand_path("~/.ssh/tinfoilsecurity-gems-key.pem") if $0 =~ /gem\z/
20
14
  s.files = `git ls-files`.split("\n").delete_if { |x| x.match('demo/*') }
21
15
  s.test_files = `git ls-files -- spec/*`.split("\n")
22
16
  s.require_paths = ['lib']
23
17
 
24
- s.add_runtime_dependency 'railties', '~> 7.0'
25
- s.add_runtime_dependency 'activesupport', '~> 7.0'
18
+ s.add_runtime_dependency 'railties', '>= 7.0', '< 8.1'
19
+ s.add_runtime_dependency 'activesupport', '>= 7.0', '< 8.1'
26
20
  s.add_runtime_dependency 'devise', '~> 4.0'
27
21
  s.add_runtime_dependency 'rotp', '~> 6.0'
28
22
 
@@ -31,5 +25,5 @@ Gem::Specification.new do |s|
31
25
  s.add_development_dependency 'bundler', '> 1.0'
32
26
  s.add_development_dependency 'rspec', '> 3'
33
27
  s.add_development_dependency 'simplecov'
34
- s.add_development_dependency 'faker'
28
+ s.add_development_dependency 'rake', '~> 13'
35
29
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "railties", "~> 7.0"
6
- gem "activesupport", "~> 7.0"
5
+ gem "railties", "~> 7.0.0"
6
+ gem "activesupport", "~> 7.0.0"
7
7
 
8
8
  gemspec path: "../"
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "railties", "~> 4.1"
6
- gem "activesupport", "~> 4.1"
5
+ gem "railties", "~> 7.1.0"
6
+ gem "activesupport", "~> 7.1.0"
7
7
 
8
8
  gemspec path: "../"
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "railties", "~> 4.2"
6
- gem "activesupport", "~> 4.2"
5
+ gem "railties", "~> 7.2.0"
6
+ gem "activesupport", "~> 7.2.0"
7
7
 
8
8
  gemspec path: "../"
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "railties", "~> 5.0"
6
- gem "activesupport", "~> 5.0"
5
+ gem "railties", "~> 8.0.0"
6
+ gem "activesupport", "~> 8.0.0"
7
7
 
8
8
  gemspec path: "../"
@@ -3,9 +3,10 @@ require 'devise_two_factor/models'
3
3
  require 'devise_two_factor/strategies'
4
4
 
5
5
  module Devise
6
- # The length of generated OTP secrets
6
+ # The length of randomly generated OTP shared secret (in bytes).
7
+ # The secrets will be base32-encoded and have a length 1.6 times the configured value.
7
8
  mattr_accessor :otp_secret_length
8
- @@otp_secret_length = 24
9
+ @@otp_secret_length = 20
9
10
 
10
11
  # The number of seconds before and after the current
11
12
  # time for which codes will be accepted
@@ -20,7 +21,8 @@ module Devise
20
21
  mattr_accessor :otp_encrypted_attribute_options
21
22
  @@otp_encrypted_attribute_options = {}
22
23
 
23
- # The length of all generated OTP backup codes
24
+ # The length of randomly generated OTP backup codes (in bytes).
25
+ # The codes will be hex-encoded and have a length twice the configured value.
24
26
  mattr_accessor :otp_backup_code_length
25
27
  @@otp_backup_code_length = 16
26
28
 
@@ -31,7 +33,7 @@ module Devise
31
33
  end
32
34
 
33
35
  Devise.add_module(:two_factor_authenticatable, :route => :session, :strategy => true,
34
- :controller => :sessions, :model => true)
36
+ :controller => :sessions, :model => true, :insert_at => 0)
35
37
 
36
38
  Devise.add_module(:two_factor_backupable, :route => :session, :strategy => true,
37
39
  :controller => :sessions, :model => true)
@@ -81,7 +81,8 @@ module Devise
81
81
  def consume_otp!
82
82
  if self.consumed_timestep != current_otp_timestep
83
83
  self.consumed_timestep = current_otp_timestep
84
- return save(validate: false)
84
+ save!(validate: false)
85
+ return true
85
86
  end
86
87
 
87
88
  false
@@ -93,8 +94,9 @@ module Devise
93
94
  :otp_encrypted_attribute_options,
94
95
  :otp_secret_encryption_key)
95
96
 
97
+ # Geneartes an OTP secret of the specified length, returning it after Base32 encoding.
96
98
  def generate_otp_secret(otp_secret_length = self.otp_secret_length)
97
- ROTP::Base32.random_base32(otp_secret_length)
99
+ ROTP::Base32.random(otp_secret_length)
98
100
  end
99
101
 
100
102
  # Return value will be splatted with ** so return a version of the
@@ -20,7 +20,7 @@ module Devise
20
20
  code_length = self.class.otp_backup_code_length
21
21
 
22
22
  number_of_codes.times do
23
- codes << SecureRandom.hex(code_length / 2) # Hexstring has length 2*n
23
+ codes << SecureRandom.hex(code_length)
24
24
  end
25
25
 
26
26
  hashed_codes = codes.map { |code| Devise::Encryptor.digest(self.class, code) }
@@ -30,7 +30,7 @@ module Devise
30
30
  end
31
31
 
32
32
  # Returns true and invalidates the given code
33
- # iff that code is a valid backup code.
33
+ # if that code is a valid backup code.
34
34
  def invalidate_otp_backup_code!(code)
35
35
  codes = self.otp_backup_codes || []
36
36
 
@@ -39,6 +39,7 @@ module Devise
39
39
 
40
40
  codes.delete(backup_code)
41
41
  self.otp_backup_codes = codes
42
+ save!(validate: false)
42
43
  return true
43
44
  end
44
45
 
@@ -13,8 +13,8 @@ RSpec.shared_examples 'two_factor_authenticatable' do
13
13
  end
14
14
 
15
15
  describe '#otp_secret' do
16
- it 'should be of the configured length' do
17
- expect(subject.otp_secret.length).to eq(subject.class.otp_secret_length)
16
+ it 'should be of the expected length' do
17
+ expect(subject.otp_secret.length).to eq(subject.class.otp_secret_length*8/5)
18
18
  end
19
19
  end
20
20
 
@@ -125,15 +125,15 @@ RSpec.shared_examples 'two_factor_authenticatable' do
125
125
 
126
126
  describe '#otp_provisioning_uri' do
127
127
  let(:otp_secret_length) { subject.class.otp_secret_length }
128
- let(:account) { Faker::Internet.email }
128
+ let(:account) { 'user@host.example' }
129
129
  let(:issuer) { 'Tinfoil' }
130
130
 
131
131
  it 'should return uri with specified account' do
132
- expect(subject.otp_provisioning_uri(account)).to match(%r{otpauth://totp/#{CGI.escape(account)}\?secret=\w{#{otp_secret_length}}})
132
+ expect(subject.otp_provisioning_uri(account)).to match(%r{otpauth://totp/#{CGI.escape(account)}\?secret=\w{#{otp_secret_length*8/5}}})
133
133
  end
134
134
 
135
135
  it 'should return uri with issuer option' do
136
- expect(subject.otp_provisioning_uri(account, issuer: issuer)).to match(%r{otpauth://totp/#{issuer}:#{CGI.escape(account)}\?.*secret=\w{#{otp_secret_length}}(&|$)})
136
+ expect(subject.otp_provisioning_uri(account, issuer: issuer)).to match(%r{otpauth://totp/#{issuer}:#{CGI.escape(account)}\?.*secret=\w{#{otp_secret_length*8/5}}(&|$)})
137
137
  expect(subject.otp_provisioning_uri(account, issuer: issuer)).to match(%r{otpauth://totp/#{issuer}:#{CGI.escape(account)}\?.*issuer=#{issuer}(&|$)})
138
138
  end
139
139
  end
@@ -17,7 +17,7 @@ RSpec.shared_examples 'two_factor_backupable' do
17
17
 
18
18
  it 'generates recovery codes of the correct length' do
19
19
  @plaintext_codes.each do |code|
20
- expect(code.length).to eq(subject.class.otp_backup_code_length)
20
+ expect(code.length).to eq(subject.class.otp_backup_code_length*2)
21
21
  end
22
22
  end
23
23
 
@@ -34,7 +34,7 @@ RSpec.shared_examples 'two_factor_backupable' do
34
34
  end
35
35
 
36
36
  context 'with existing recovery codes' do
37
- let(:old_codes) { Faker::Lorem.words }
37
+ let(:old_codes) { ['adam', 'betty', 'charles'] }
38
38
  let(:old_codes_hashed) { old_codes.map { |x| Devise::Encryptor.digest(subject.class, x) } }
39
39
 
40
40
  before do
@@ -21,7 +21,7 @@ module Devise
21
21
 
22
22
  def validate_otp(resource)
23
23
  return true unless resource.otp_required_for_login
24
- return if params[scope]['otp_attempt'].nil?
24
+ return if params[scope].nil? || params[scope]['otp_attempt'].nil?
25
25
  resource.validate_and_consume_otp!(params[scope]['otp_attempt'])
26
26
  end
27
27
  end
@@ -6,9 +6,6 @@ module Devise
6
6
  resource = mapping.to.find_for_database_authentication(authentication_hash)
7
7
 
8
8
  if validate(resource) { resource.invalidate_otp_backup_code!(params[scope]['otp_attempt']) }
9
- # Devise fails to authenticate invalidated resources, but if we've
10
- # gotten here, the object changed (Since we deleted a recovery code)
11
- resource.save!
12
9
  super
13
10
  end
14
11
 
@@ -1,3 +1,3 @@
1
1
  module DeviseTwoFactor
2
- VERSION = '5.0.0'.freeze
2
+ VERSION = '6.1.0'.freeze
3
3
  end
@@ -18,13 +18,17 @@ class TwoFactorAuthenticatableDouble
18
18
 
19
19
  attr_accessor :consumed_timestep
20
20
 
21
- def save(validate)
21
+ def save!(_)
22
22
  # noop for testing
23
23
  true
24
24
  end
25
25
  end
26
26
 
27
27
  describe ::Devise::Models::TwoFactorAuthenticatable do
28
+ it 'should be inserted prior to other devise modules' do
29
+ expect(Devise::ALL.first).to eq(:two_factor_authenticatable)
30
+ end
31
+
28
32
  context 'When included in a class' do
29
33
  subject { TwoFactorAuthenticatableDouble.new }
30
34
 
@@ -17,6 +17,10 @@ class TwoFactorBackupableDouble
17
17
  devise :two_factor_authenticatable, :two_factor_backupable
18
18
 
19
19
  attr_accessor :otp_backup_codes
20
+
21
+ def save!(_)
22
+ true
23
+ end
20
24
  end
21
25
 
22
26
  describe ::Devise::Models::TwoFactorBackupable do
data/spec/spec_helper.rb CHANGED
@@ -18,7 +18,6 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
18
18
  $LOAD_PATH.unshift(File.dirname(__FILE__))
19
19
 
20
20
  require 'rspec'
21
- require 'faker'
22
21
  require 'devise-two-factor'
23
22
  require 'devise_two_factor/spec_helpers'
24
23
 
metadata CHANGED
@@ -1,121 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise-two-factor
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0
4
+ version: 6.1.0
5
5
  platform: ruby
6
6
  authors:
7
- - Shane Wilton
8
- autorequire:
7
+ - Quinn Wilton
8
+ autorequire:
9
9
  bindir: bin
10
- cert_chain:
11
- - |
12
- -----BEGIN CERTIFICATE-----
13
- MIIHSjCCBTKgAwIBAgIJAK2u0LojMCNgMA0GCSqGSIb3DQEBBQUAMIGcMQswCQYD
14
- VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEfMB0GA1UE
15
- ChMWVGluZm9pbCBTZWN1cml0eSwgSW5jLjEfMB0GA1UEAxMWVGluZm9pbCBTZWN1
16
- cml0eSwgSW5jLjEqMCgGCSqGSIb3DQEJARYbc3VwcG9ydEB0aW5mb2lsc2VjdXJp
17
- dHkuY29tMB4XDTIxMDkwOTE4MjIwMFoXDTI1MDkwOTE4MjIwMFowgZwxCzAJBgNV
18
- BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJUGFsbyBBbHRvMR8wHQYDVQQK
19
- ExZUaW5mb2lsIFNlY3VyaXR5LCBJbmMuMR8wHQYDVQQDExZUaW5mb2lsIFNlY3Vy
20
- aXR5LCBJbmMuMSowKAYJKoZIhvcNAQkBFhtzdXBwb3J0QHRpbmZvaWxzZWN1cml0
21
- eS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqbHvsSj0H0FB1
22
- 0gLYoDK1BKugkSB2DZeZZHP6B1UdWRahJXJP9oT1lhfQxx8iX4cgEi7JU3NqA6NR
23
- cIRFQ50eH/qlmgs7909gaf8pDaeC0vR3wd0GeRg6qr1eDEnkzIyr/D1AMiX6H1eP
24
- Y7J3SfrdaL3gft2iPRKGkgqsXR7oBNLA3n/ShiNgPXqRDl1CCj6aMY0cn5ROFScz
25
- vT2FUB4DEwPD2l18m1p99OnXqsOLL2J65qA2+cI8FtgFmlwIi5oSf+URvIdNx+cH
26
- lInlAtVHCvAKYLY0dlQ7czMQBcRpYjp2rwPt9f2ksq9b/voMTBABYHFV+IVn8svv
27
- GZ5e1+icjtr/R7dCGmCdEdFLXVxafmZhukymG9USv9DKuv1qh7r4q8KaPIE8n7nQ
28
- m97jENFfsgnwv+nUmIJ3tzuW5ZxO7A0tIIYdwzt0UjrO3ya4R5bTFXr4bnzZ/g/s
29
- CLknWqg1BCRlPd6LnpVGPT0gNDV1pEO25wE3A3Yy0Ujxudcgay/CgUhnlU11qOAc
30
- xmar2fhNZsviUhndd/220Ad5QMV2XzcAiopJIeu0juIVGRQM7x2h19Hsp0m6sOEF
31
- jfhvbdUa4nvmIFeYFY+hr/YkTmG9ZjyBa8YaZXhwjhSmKCQ374J7mn5e0Cryuvi5
32
- tYhwJn8rdwYZF/h2qqfEu8vaLoD09QIDAQABo4IBizCCAYcwHQYDVR0OBBYEFMmT
33
- /x412UH+5OHqgleeTjLOv6iHMIHRBgNVHSMEgckwgcaAFMmT/x412UH+5OHqglee
34
- TjLOv6iHoYGipIGfMIGcMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNV
35
- BAcTCVBhbG8gQWx0bzEfMB0GA1UEChMWVGluZm9pbCBTZWN1cml0eSwgSW5jLjEf
36
- MB0GA1UEAxMWVGluZm9pbCBTZWN1cml0eSwgSW5jLjEqMCgGCSqGSIb3DQEJARYb
37
- c3VwcG9ydEB0aW5mb2lsc2VjdXJpdHkuY29tggkAra7QuiMwI2AwDwYDVR0TAQH/
38
- BAUwAwEB/zARBglghkgBhvhCAQEEBAMCAQYwCQYDVR0SBAIwADArBglghkgBhvhC
39
- AQ0EHhYcVGlueUNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAmBgNVHREEHzAdgRtz
40
- dXBwb3J0QHRpbmZvaWxzZWN1cml0eS5jb20wDgYDVR0PAQH/BAQDAgEGMA0GCSqG
41
- SIb3DQEBBQUAA4ICAQBZy4JJSmwLuO0nZbdr4tJeVS2P8bcGi6PzAcdzVfwzjp6n
42
- 5qf8m4O8my4lnJieom0GrWSHQoPY1Yur4hEoZbugKO9DWZL3dTiGcrgw0TbQ6Gtq
43
- TTPatW3LA21qFJwvohSvLqPdmZuM+H9g49sdl2kNTDVI6iUyMYuNpL14aPKPGBFo
44
- o7UjciT1h7JtJl9b/fXrbPeRHBwpZXWeipiPGv/OZW5KnOsNlUkTquS7Zj4ETkIC
45
- 6mVtmsLvq+YwFthFyMU37pXwYxcmqRmH6lX+XC6AVW5oO4GBmG+Zr/Z+h5Cih5ca
46
- /mX88RkO+dGTjw1IdxKmxOqKL62OBATKrTDJ/scsmRptynA4TunYW+7ikOpDbPfL
47
- l18aleLISlcgWJg/Czf2nmBqAClPLnhV8qxWsvt58MQQ/Jpoggvpl8EG1PylWiBS
48
- Kc/4Ad/FKQFpTzXUgDg2kV07npVjYbBzA5p4ZSWSlflFu93jb9gg2+qtnRSImVCZ
49
- nQjZdsv8hebElPAIbtJjSnoH1Kz2ucYLakdF1UMKnpp1PVREtuKPz/foU9KUHs0z
50
- dWRALx8cWG4uKK9AIEUlVdGKfX0Wj0qFK0KGxl3f3jObud5Agwue2EPKWwUzEGUh
51
- Iqp60gNw3vBdKHw4dh1bfcbXWnRDL+OQPuOFZeMWgu1QmeHeuggYtYtRg7V5Kg==
52
- -----END CERTIFICATE-----
53
- - |
54
- -----BEGIN CERTIFICATE-----
55
- MIIGADCCA+igAwIBAgIIHIF9ta6cW3YwDQYJKoZIhvcNAQENBQAwgZwxCzAJBgNV
56
- BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJUGFsbyBBbHRvMR8wHQYDVQQK
57
- ExZUaW5mb2lsIFNlY3VyaXR5LCBJbmMuMR8wHQYDVQQDExZUaW5mb2lsIFNlY3Vy
58
- aXR5LCBJbmMuMSowKAYJKoZIhvcNAQkBFhtzdXBwb3J0QHRpbmZvaWxzZWN1cml0
59
- eS5jb20wHhcNMjIwMzIyMjI1MzAwWhcNMjUwOTA5MTgyMjAwWjCBiDELMAkGA1UE
60
- BhMCVVMxCzAJBgNVBAgTAkNBMR8wHQYDVQQKExZUaW5mb2lsIFNlY3VyaXR5LCBJ
61
- bmMuMR0wGwYDVQQDExR0aW5mb2lsc2VjdXJpdHktZ2VtczEsMCoGCSqGSIb3DQEJ
62
- ARYdZW5naW5lZXJzQHRpbmZvaWxzZWN1cml0eS5jb20wggIiMA0GCSqGSIb3DQEB
63
- AQUAA4ICDwAwggIKAoICAQDNJYNH8D+8lACLt3KzjEIPs3XVBCPaMm2eD/Xk9OOT
64
- uDV/NqgMK0icD9MRxMUtS3SCrC9QcPocXT76f2LQ3yVJuK+rBUasymEES47PIx2c
65
- zC4n4Hga0xPPuBpioO26oaRFsobyzh9RPOIbnYfpjyqtdrbm+YyM3sPR4XzFirv9
66
- xomT4E9T4RCLgOQHTcLKL9K9m+EN7PeVdVUXV0Pa7cVs2vJUKedsd7vnr6Lzbn8T
67
- oPk/7J/4W931PbaeI5yg9ZuaRa9K2IaY1TkPI67NW4qKitBVepRlXw6Sb7TYcUnc
68
- WEQ/eC5CpnOmqUrG5tfGD8cc5aGZOkitW/VXZgVj81xgCv1hk4HjErrqq4FBNAaC
69
- SNyBfwR0TUYqg1lN1nbNjOKwfb6YRn06R2ovcFJG0tmGhsQULCr6fW8u2TfSM+U9
70
- WFSIJx2griureY7EZPwg/MgsUiWUWMFemz3GVYXWJR3dN2pW9Uqr3rkjKZbA0bst
71
- GWahJO9HuFdDakQxoaTPYPtTQDC+kskkO6lKG1KLIoZ1iLZzB1Ks1vEeyE7lp1im
72
- WgpUq+q23PFkt1gIBi/4tGvzsLZye25QU2Y+XLzldCNm+DyRFXZ+Q+bK33IveUeU
73
- WEOv4T1qTXHAOypyzmgodVRG/PrlsSMOBfE515kG1mDMGjRcCpEtlskgxUbf7qM7
74
- hQIDAQABo1gwVjAJBgNVHRMEAjAAMEkGA1UdHwRCMEAwPqA8oDqGOGh0dHBzOi8v
75
- d3d3LnRpbmZvaWxzZWN1cml0eS5jb20vc2VjdXJpdHkvcmV2b2NhdGlvbl9saXN0
76
- MA0GCSqGSIb3DQEBDQUAA4ICAQAiYF/m2ny/mxFvBVxHfdYuzybhCvsEUd+TSnoe
77
- mqOWntY3sxCOaY0aGOMB4vyg9G+oP/kT4m63sD4uQxeuU7WOjaG2smCSS5q+PSWS
78
- v63gILqPamjSyP/Om864EA6YlvVQ7nPXhVDEaiBt3iliefJGmb0wWSbbDCmq3aMb
79
- WTLuax/IeY6MjJi20LutIcuz+VX8OxlA1hSpgAToMz3xrhA8fPt5UkKhkDkPFYBF
80
- 5htKVipyijChWsXyt33YM2qGaavTEXzxza1I99PGNRKxUMvbSMas4YaLqkBpQSc+
81
- mcrLWYPiXWsePGu+j08AypE2Ubp4AOSZJN9rBBGotC3gofipo+K/sBiOM9xXI76Q
82
- 0HYOxXPa7D7UQQG1R9i0rcxmf9qepIVYCldmqVkKKDizcDo5UI9lRiLFjDyQhn6l
83
- YFY9bPQ4lKTK5Jr3M6+dV7fHxLhqXyMGs1905IUb7qvB7Bq/f0qJfC0JZuY/qdn2
84
- lL0SeFKOVsjErtobh3u8p8j2USkc8uJgIANHpXEMEExdp899CV/eVjh3TpAR7E6T
85
- mg7Q9Hi6Hh8z+Le9iR4I49vPEWDQEvj35IT6VfwU79UfIOlX+DkW8fFkPbaut3Se
86
- vqIDv6JBG9I16h/HhchntKfM58MI1bNZFBSdZqYOJiL8JIjP8HNIk76Y366ppG29
87
- EhBYYg==
88
- -----END CERTIFICATE-----
89
- date: 2022-07-11 00:00:00.000000000 Z
10
+ cert_chain: []
11
+ date: 2024-11-11 00:00:00.000000000 Z
90
12
  dependencies:
91
13
  - !ruby/object:Gem::Dependency
92
14
  name: railties
93
15
  requirement: !ruby/object:Gem::Requirement
94
16
  requirements:
95
- - - "~>"
17
+ - - ">="
96
18
  - !ruby/object:Gem::Version
97
19
  version: '7.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '8.1'
98
23
  type: :runtime
99
24
  prerelease: false
100
25
  version_requirements: !ruby/object:Gem::Requirement
101
26
  requirements:
102
- - - "~>"
27
+ - - ">="
103
28
  - !ruby/object:Gem::Version
104
29
  version: '7.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '8.1'
105
33
  - !ruby/object:Gem::Dependency
106
34
  name: activesupport
107
35
  requirement: !ruby/object:Gem::Requirement
108
36
  requirements:
109
- - - "~>"
37
+ - - ">="
110
38
  - !ruby/object:Gem::Version
111
39
  version: '7.0'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '8.1'
112
43
  type: :runtime
113
44
  prerelease: false
114
45
  version_requirements: !ruby/object:Gem::Requirement
115
46
  requirements:
116
- - - "~>"
47
+ - - ">="
117
48
  - !ruby/object:Gem::Version
118
49
  version: '7.0'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '8.1'
119
53
  - !ruby/object:Gem::Dependency
120
54
  name: devise
121
55
  requirement: !ruby/object:Gem::Requirement
@@ -215,27 +149,31 @@ dependencies:
215
149
  - !ruby/object:Gem::Version
216
150
  version: '0'
217
151
  - !ruby/object:Gem::Dependency
218
- name: faker
152
+ name: rake
219
153
  requirement: !ruby/object:Gem::Requirement
220
154
  requirements:
221
- - - ">="
155
+ - - "~>"
222
156
  - !ruby/object:Gem::Version
223
- version: '0'
157
+ version: '13'
224
158
  type: :development
225
159
  prerelease: false
226
160
  version_requirements: !ruby/object:Gem::Requirement
227
161
  requirements:
228
- - - ">="
162
+ - - "~>"
229
163
  - !ruby/object:Gem::Version
230
- version: '0'
231
- description: Barebones two-factor authentication with Devise
232
- email: engineers@tinfoilsecurity.com
164
+ version: '13'
165
+ description: Devise-Two-Factor is a minimalist extension to Devise which offers support
166
+ for two-factor authentication through the TOTP scheme.
167
+ email:
233
168
  executables: []
234
169
  extensions: []
235
170
  extra_rdoc_files: []
236
171
  files:
172
+ - ".github/dependabot.yml"
237
173
  - ".github/workflows/ci.yml"
174
+ - ".github/workflows/push.yml"
238
175
  - ".gitignore"
176
+ - ".markdownlint.json"
239
177
  - ".rspec"
240
178
  - Appraisals
241
179
  - CHANGELOG.md
@@ -244,18 +182,13 @@ files:
244
182
  - LICENSE
245
183
  - README.md
246
184
  - Rakefile
185
+ - SECURITY.md
247
186
  - UPGRADING.md
248
- - certs/tinfoil-cacert.pem
249
- - certs/tinfoilsecurity-gems-cert.pem
250
187
  - devise-two-factor.gemspec
251
- - gemfiles/rails_4.1.gemfile
252
- - gemfiles/rails_4.2.gemfile
253
- - gemfiles/rails_5.0.gemfile
254
- - gemfiles/rails_5.1.gemfile
255
- - gemfiles/rails_5.2.gemfile
256
- - gemfiles/rails_6.0.gemfile
257
- - gemfiles/rails_6.1.gemfile
258
188
  - gemfiles/rails_7.0.gemfile
189
+ - gemfiles/rails_7.1.gemfile
190
+ - gemfiles/rails_7.2.gemfile
191
+ - gemfiles/rails_8.0.gemfile
259
192
  - lib/devise-two-factor.rb
260
193
  - lib/devise_two_factor/models.rb
261
194
  - lib/devise_two_factor/models/two_factor_authenticatable.rb
@@ -271,11 +204,11 @@ files:
271
204
  - spec/devise/models/two_factor_authenticatable_spec.rb
272
205
  - spec/devise/models/two_factor_backupable_spec.rb
273
206
  - spec/spec_helper.rb
274
- homepage: https://github.com/tinfoil/devise-two-factor
207
+ homepage: https://github.com/devise-two-factor/devise-two-factor
275
208
  licenses:
276
209
  - MIT
277
210
  metadata: {}
278
- post_install_message:
211
+ post_install_message:
279
212
  rdoc_options: []
280
213
  require_paths:
281
214
  - lib
@@ -290,8 +223,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
290
223
  - !ruby/object:Gem::Version
291
224
  version: '0'
292
225
  requirements: []
293
- rubygems_version: 3.2.32
294
- signing_key:
226
+ rubygems_version: 3.5.22
227
+ signing_key:
295
228
  specification_version: 4
296
229
  summary: Barebones two-factor authentication with Devise
297
230
  test_files:
checksums.yaml.gz.sig DELETED
Binary file
@@ -1,41 +0,0 @@
1
- -----BEGIN CERTIFICATE-----
2
- MIIHSjCCBTKgAwIBAgIJAK2u0LojMCNgMA0GCSqGSIb3DQEBBQUAMIGcMQswCQYD
3
- VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEfMB0GA1UE
4
- ChMWVGluZm9pbCBTZWN1cml0eSwgSW5jLjEfMB0GA1UEAxMWVGluZm9pbCBTZWN1
5
- cml0eSwgSW5jLjEqMCgGCSqGSIb3DQEJARYbc3VwcG9ydEB0aW5mb2lsc2VjdXJp
6
- dHkuY29tMB4XDTIxMDkwOTE4MjIwMFoXDTI1MDkwOTE4MjIwMFowgZwxCzAJBgNV
7
- BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJUGFsbyBBbHRvMR8wHQYDVQQK
8
- ExZUaW5mb2lsIFNlY3VyaXR5LCBJbmMuMR8wHQYDVQQDExZUaW5mb2lsIFNlY3Vy
9
- aXR5LCBJbmMuMSowKAYJKoZIhvcNAQkBFhtzdXBwb3J0QHRpbmZvaWxzZWN1cml0
10
- eS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqbHvsSj0H0FB1
11
- 0gLYoDK1BKugkSB2DZeZZHP6B1UdWRahJXJP9oT1lhfQxx8iX4cgEi7JU3NqA6NR
12
- cIRFQ50eH/qlmgs7909gaf8pDaeC0vR3wd0GeRg6qr1eDEnkzIyr/D1AMiX6H1eP
13
- Y7J3SfrdaL3gft2iPRKGkgqsXR7oBNLA3n/ShiNgPXqRDl1CCj6aMY0cn5ROFScz
14
- vT2FUB4DEwPD2l18m1p99OnXqsOLL2J65qA2+cI8FtgFmlwIi5oSf+URvIdNx+cH
15
- lInlAtVHCvAKYLY0dlQ7czMQBcRpYjp2rwPt9f2ksq9b/voMTBABYHFV+IVn8svv
16
- GZ5e1+icjtr/R7dCGmCdEdFLXVxafmZhukymG9USv9DKuv1qh7r4q8KaPIE8n7nQ
17
- m97jENFfsgnwv+nUmIJ3tzuW5ZxO7A0tIIYdwzt0UjrO3ya4R5bTFXr4bnzZ/g/s
18
- CLknWqg1BCRlPd6LnpVGPT0gNDV1pEO25wE3A3Yy0Ujxudcgay/CgUhnlU11qOAc
19
- xmar2fhNZsviUhndd/220Ad5QMV2XzcAiopJIeu0juIVGRQM7x2h19Hsp0m6sOEF
20
- jfhvbdUa4nvmIFeYFY+hr/YkTmG9ZjyBa8YaZXhwjhSmKCQ374J7mn5e0Cryuvi5
21
- tYhwJn8rdwYZF/h2qqfEu8vaLoD09QIDAQABo4IBizCCAYcwHQYDVR0OBBYEFMmT
22
- /x412UH+5OHqgleeTjLOv6iHMIHRBgNVHSMEgckwgcaAFMmT/x412UH+5OHqglee
23
- TjLOv6iHoYGipIGfMIGcMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNV
24
- BAcTCVBhbG8gQWx0bzEfMB0GA1UEChMWVGluZm9pbCBTZWN1cml0eSwgSW5jLjEf
25
- MB0GA1UEAxMWVGluZm9pbCBTZWN1cml0eSwgSW5jLjEqMCgGCSqGSIb3DQEJARYb
26
- c3VwcG9ydEB0aW5mb2lsc2VjdXJpdHkuY29tggkAra7QuiMwI2AwDwYDVR0TAQH/
27
- BAUwAwEB/zARBglghkgBhvhCAQEEBAMCAQYwCQYDVR0SBAIwADArBglghkgBhvhC
28
- AQ0EHhYcVGlueUNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAmBgNVHREEHzAdgRtz
29
- dXBwb3J0QHRpbmZvaWxzZWN1cml0eS5jb20wDgYDVR0PAQH/BAQDAgEGMA0GCSqG
30
- SIb3DQEBBQUAA4ICAQBZy4JJSmwLuO0nZbdr4tJeVS2P8bcGi6PzAcdzVfwzjp6n
31
- 5qf8m4O8my4lnJieom0GrWSHQoPY1Yur4hEoZbugKO9DWZL3dTiGcrgw0TbQ6Gtq
32
- TTPatW3LA21qFJwvohSvLqPdmZuM+H9g49sdl2kNTDVI6iUyMYuNpL14aPKPGBFo
33
- o7UjciT1h7JtJl9b/fXrbPeRHBwpZXWeipiPGv/OZW5KnOsNlUkTquS7Zj4ETkIC
34
- 6mVtmsLvq+YwFthFyMU37pXwYxcmqRmH6lX+XC6AVW5oO4GBmG+Zr/Z+h5Cih5ca
35
- /mX88RkO+dGTjw1IdxKmxOqKL62OBATKrTDJ/scsmRptynA4TunYW+7ikOpDbPfL
36
- l18aleLISlcgWJg/Czf2nmBqAClPLnhV8qxWsvt58MQQ/Jpoggvpl8EG1PylWiBS
37
- Kc/4Ad/FKQFpTzXUgDg2kV07npVjYbBzA5p4ZSWSlflFu93jb9gg2+qtnRSImVCZ
38
- nQjZdsv8hebElPAIbtJjSnoH1Kz2ucYLakdF1UMKnpp1PVREtuKPz/foU9KUHs0z
39
- dWRALx8cWG4uKK9AIEUlVdGKfX0Wj0qFK0KGxl3f3jObud5Agwue2EPKWwUzEGUh
40
- Iqp60gNw3vBdKHw4dh1bfcbXWnRDL+OQPuOFZeMWgu1QmeHeuggYtYtRg7V5Kg==
41
- -----END CERTIFICATE-----
@@ -1,35 +0,0 @@
1
- -----BEGIN CERTIFICATE-----
2
- MIIGADCCA+igAwIBAgIIHIF9ta6cW3YwDQYJKoZIhvcNAQENBQAwgZwxCzAJBgNV
3
- BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJUGFsbyBBbHRvMR8wHQYDVQQK
4
- ExZUaW5mb2lsIFNlY3VyaXR5LCBJbmMuMR8wHQYDVQQDExZUaW5mb2lsIFNlY3Vy
5
- aXR5LCBJbmMuMSowKAYJKoZIhvcNAQkBFhtzdXBwb3J0QHRpbmZvaWxzZWN1cml0
6
- eS5jb20wHhcNMjIwMzIyMjI1MzAwWhcNMjUwOTA5MTgyMjAwWjCBiDELMAkGA1UE
7
- BhMCVVMxCzAJBgNVBAgTAkNBMR8wHQYDVQQKExZUaW5mb2lsIFNlY3VyaXR5LCBJ
8
- bmMuMR0wGwYDVQQDExR0aW5mb2lsc2VjdXJpdHktZ2VtczEsMCoGCSqGSIb3DQEJ
9
- ARYdZW5naW5lZXJzQHRpbmZvaWxzZWN1cml0eS5jb20wggIiMA0GCSqGSIb3DQEB
10
- AQUAA4ICDwAwggIKAoICAQDNJYNH8D+8lACLt3KzjEIPs3XVBCPaMm2eD/Xk9OOT
11
- uDV/NqgMK0icD9MRxMUtS3SCrC9QcPocXT76f2LQ3yVJuK+rBUasymEES47PIx2c
12
- zC4n4Hga0xPPuBpioO26oaRFsobyzh9RPOIbnYfpjyqtdrbm+YyM3sPR4XzFirv9
13
- xomT4E9T4RCLgOQHTcLKL9K9m+EN7PeVdVUXV0Pa7cVs2vJUKedsd7vnr6Lzbn8T
14
- oPk/7J/4W931PbaeI5yg9ZuaRa9K2IaY1TkPI67NW4qKitBVepRlXw6Sb7TYcUnc
15
- WEQ/eC5CpnOmqUrG5tfGD8cc5aGZOkitW/VXZgVj81xgCv1hk4HjErrqq4FBNAaC
16
- SNyBfwR0TUYqg1lN1nbNjOKwfb6YRn06R2ovcFJG0tmGhsQULCr6fW8u2TfSM+U9
17
- WFSIJx2griureY7EZPwg/MgsUiWUWMFemz3GVYXWJR3dN2pW9Uqr3rkjKZbA0bst
18
- GWahJO9HuFdDakQxoaTPYPtTQDC+kskkO6lKG1KLIoZ1iLZzB1Ks1vEeyE7lp1im
19
- WgpUq+q23PFkt1gIBi/4tGvzsLZye25QU2Y+XLzldCNm+DyRFXZ+Q+bK33IveUeU
20
- WEOv4T1qTXHAOypyzmgodVRG/PrlsSMOBfE515kG1mDMGjRcCpEtlskgxUbf7qM7
21
- hQIDAQABo1gwVjAJBgNVHRMEAjAAMEkGA1UdHwRCMEAwPqA8oDqGOGh0dHBzOi8v
22
- d3d3LnRpbmZvaWxzZWN1cml0eS5jb20vc2VjdXJpdHkvcmV2b2NhdGlvbl9saXN0
23
- MA0GCSqGSIb3DQEBDQUAA4ICAQAiYF/m2ny/mxFvBVxHfdYuzybhCvsEUd+TSnoe
24
- mqOWntY3sxCOaY0aGOMB4vyg9G+oP/kT4m63sD4uQxeuU7WOjaG2smCSS5q+PSWS
25
- v63gILqPamjSyP/Om864EA6YlvVQ7nPXhVDEaiBt3iliefJGmb0wWSbbDCmq3aMb
26
- WTLuax/IeY6MjJi20LutIcuz+VX8OxlA1hSpgAToMz3xrhA8fPt5UkKhkDkPFYBF
27
- 5htKVipyijChWsXyt33YM2qGaavTEXzxza1I99PGNRKxUMvbSMas4YaLqkBpQSc+
28
- mcrLWYPiXWsePGu+j08AypE2Ubp4AOSZJN9rBBGotC3gofipo+K/sBiOM9xXI76Q
29
- 0HYOxXPa7D7UQQG1R9i0rcxmf9qepIVYCldmqVkKKDizcDo5UI9lRiLFjDyQhn6l
30
- YFY9bPQ4lKTK5Jr3M6+dV7fHxLhqXyMGs1905IUb7qvB7Bq/f0qJfC0JZuY/qdn2
31
- lL0SeFKOVsjErtobh3u8p8j2USkc8uJgIANHpXEMEExdp899CV/eVjh3TpAR7E6T
32
- mg7Q9Hi6Hh8z+Le9iR4I49vPEWDQEvj35IT6VfwU79UfIOlX+DkW8fFkPbaut3Se
33
- vqIDv6JBG9I16h/HhchntKfM58MI1bNZFBSdZqYOJiL8JIjP8HNIk76Y366ppG29
34
- EhBYYg==
35
- -----END CERTIFICATE-----
@@ -1,8 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "railties", "~> 5.1"
6
- gem "activesupport", "~> 5.1"
7
-
8
- gemspec path: "../"
@@ -1,8 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "railties", "~> 5.2"
6
- gem "activesupport", "~> 5.2"
7
-
8
- gemspec path: "../"
@@ -1,8 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "railties", "~> 6.0"
6
- gem "activesupport", "~> 6.0"
7
-
8
- gemspec path: "../"
@@ -1,8 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "railties", "~> 6.1"
6
- gem "activesupport", "~> 6.1"
7
-
8
- gemspec path: "../"
data.tar.gz.sig DELETED
Binary file
metadata.gz.sig DELETED
Binary file