devise-two-factor 4.1.1 → 6.4.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +6 -0
  3. data/.github/workflows/ci.yml +3 -18
  4. data/.github/workflows/push.yml +28 -0
  5. data/.markdownlint.json +6 -0
  6. data/Appraisals +9 -34
  7. data/CHANGELOG.md +59 -0
  8. data/README.md +110 -67
  9. data/Rakefile +2 -0
  10. data/SECURITY.md +5 -0
  11. data/UPGRADING.md +218 -2
  12. data/devise-two-factor.gemspec +8 -15
  13. data/gemfiles/{rails_4.2.gemfile → rails_7.2.gemfile} +2 -2
  14. data/gemfiles/{rails_5.0.gemfile → rails_8.0.gemfile} +2 -2
  15. data/gemfiles/{rails_4.1.gemfile → rails_8.1.gemfile} +2 -2
  16. data/lib/devise-two-factor.rb +12 -5
  17. data/lib/devise_two_factor/models/two_factor_authenticatable.rb +41 -29
  18. data/lib/devise_two_factor/models/two_factor_backupable.rb +6 -2
  19. data/lib/devise_two_factor/spec_helpers/two_factor_authenticatable_shared_examples.rb +6 -18
  20. data/lib/devise_two_factor/spec_helpers/two_factor_backupable_shared_examples.rb +53 -24
  21. data/lib/devise_two_factor/strategies/two_factor_authenticatable.rb +8 -2
  22. data/lib/devise_two_factor/strategies/two_factor_backupable.rb +6 -4
  23. data/lib/devise_two_factor/version.rb +1 -1
  24. data/lib/generators/devise_two_factor/devise_two_factor_generator.rb +2 -7
  25. data/spec/devise/models/two_factor_authenticatable_spec.rb +11 -69
  26. data/spec/devise/models/two_factor_backupable_spec.rb +11 -2
  27. data/spec/spec_helper.rb +0 -1
  28. metadata +42 -134
  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/gemfiles/rails_7.0.gemfile +0 -8
  37. data.tar.gz.sig +0 -0
  38. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 787116f2d8fd1e2ef5a6e663c0a3ed5a65ca6110a491f44d2de5eb1a985da516
4
- data.tar.gz: f379bef9da0239c3697fe14dad33ba6c662ecfd2b6bcc3ab54fb184f75a970d4
3
+ metadata.gz: 48a006cc0a0b85e48b88b8c8521231b65691c513d1c7bf0c55fac2b24fe73d07
4
+ data.tar.gz: e3d988b573e1720e5f1532a8fb161e3399cfd601273cf971957b33a6ad82a489
5
5
  SHA512:
6
- metadata.gz: 23b55432e9df42fa8723db98cd38abe641656ed05ce6f5d9e5d7ee1a1179624ba7212209c460a7ff4b16d4f14347b11c69750d795e7ff9f56513e93af651c619
7
- data.tar.gz: ce784140d65f12c6e46a51d86ed8e97edc6b98e706f276160e67f95bccf615c308a5cf273bd54ad71788b61e5dac92f9676c4e67ebfdd81c83584fa71e0bb509
6
+ metadata.gz: ee0fd9a8b7042adf26790545788508991ad06bab0191e2d75b3f7119f283f10fa922fa5a51353199539f8a220cb966706820538c22252e5f98a9f976077a8137
7
+ data.tar.gz: ad571291a241e5b7080f62c9ad410f91a0b1f140ff3d978370136212e659670cc8177328c1c306d6957ef925b7789dada8449e2aaa9e4f4096a78f7a97d7f777
@@ -0,0 +1,6 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
@@ -12,29 +12,14 @@ 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: ['3.2', '3.3', '3.4', '4.0', 'truffleruby-head']
16
+ rails: ['7.2', '8.0', '8.1']
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
35
20
  BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/rails_${{ matrix.rails }}.gemfile
36
21
  steps:
37
- - uses: actions/checkout@v2
22
+ - uses: actions/checkout@v6
38
23
  - name: Set up Ruby
39
24
  uses: ruby/setup-ruby@v1
40
25
  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@v6
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,14 @@
1
- appraise "rails-4.1" do
2
- gem 'railties', '~> 4.1'
3
- gem 'activesupport', '~> 4.1'
1
+ appraise "rails-7.2" do
2
+ gem 'railties', '~> 7.2.0'
3
+ gem 'activesupport', '~> 7.2.0'
4
4
  end
5
5
 
6
- appraise "rails-4.2" do
7
- gem 'railties', '~> 4.2'
8
- gem 'activesupport', '~> 4.2'
6
+ appraise "rails-8.0" do
7
+ gem 'railties', '~> 8.0.0'
8
+ gem 'activesupport', '~> 8.0.0'
9
9
  end
10
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'
24
- end
25
-
26
- appraise "rails-6.0" do
27
- gem 'railties', '~> 6.0'
28
- gem 'activesupport', '~> 6.0'
29
- end
30
-
31
- appraise "rails-6.1" do
32
- gem 'railties', '~> 6.1'
33
- gem 'activesupport', '~> 6.1'
34
- end
35
-
36
- appraise "rails-7.0" do
37
- gem 'railties', '~> 7.0'
38
- gem 'activesupport', '~> 7.0'
11
+ appraise "rails-8.1" do
12
+ gem 'railties', '8.1.0'
13
+ gem 'activesupport', '8.1.0'
39
14
  end
data/CHANGELOG.md CHANGED
@@ -2,18 +2,64 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 6.4.0
6
+
7
+ - Remove upper limit on Devise version (allows v5) from gemspec
8
+
9
+ ## 6.3.1
10
+
11
+ - Fix DB-adapter-specific integration issue with backupable shared example
12
+ - Drop support for EOL Rails versions 7.0 and 7.1
13
+
14
+ ## 6.3.0
15
+
16
+ - Fixed timing to be consistent when Devise paranoid mode is active.
17
+
18
+ ## 6.2.0
19
+
20
+ - Rails 8.1 support
21
+
22
+ ## 6.1.0
23
+
24
+ - Rails 8 support
25
+
26
+ ## 6.0.0
27
+
28
+ **Breaking Changes**
29
+ - `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).
30
+ - `consume_otp!` and `invalidate_otp_backup_code!` now call `save!` instead of `save`. See [UPGRADING.md](UPGRADING.md).
31
+
32
+ ## 5.1.0
33
+
34
+ - Remove faker dev dependency
35
+ - Insert two_factor_authenticatable at the top of the devise module list
36
+ - README and CI improvements
37
+
38
+ ## 5.0.0
39
+
40
+ **Breaking Changes**
41
+ - 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!
42
+ - Rails 7 is now required.
43
+
44
+ ## 4.1.0 / 4.1.1
45
+
46
+ - Add support for attr_encrypted v4
47
+
5
48
  ## 4.0.2
49
+
6
50
  - Add Rails 7.0 support
7
51
  - Renew signing certificate
8
52
  - Use `after` option of TOTP#verify for additional timestamp verification
9
53
 
10
54
  ## 4.0.1
55
+
11
56
  - Convert CI from Travis CI to Github Actions ([#198](https://github.com/tinfoil/devise-two-factor/pull/198))
12
57
  - Fix ActiveSupport::Testing::TimeHelpers require in shared examples ([#191](https://github.com/tinfoil/devise-two-factor/pull/191))
13
58
  - Accept whitespace in provided codes ([#195](https://github.com/tinfoil/devise-two-factor/pull/195))
14
59
  - Add Truffleruby head to CI ([#200](https://github.com/tinfoil/devise-two-factor/pull/200))
15
60
 
16
61
  ## 4.0.0
62
+
17
63
  - [breaking] Drop support for Ruby <= 2.2
18
64
  - Update ROTP
19
65
  - Add Rails 6.1 support
@@ -22,20 +68,25 @@
22
68
  - Bugfixes & cleanup
23
69
 
24
70
  ## 3.1.0
71
+
25
72
  - Add Rails 6.0 support
26
73
  - New gem signing certificate
27
74
  - Fix paranoid-mode being ignored
28
75
 
29
76
  ## 3.0.3
77
+
30
78
  - Add Rails 5.2 support
31
79
 
32
80
  ## 3.0.2
81
+
33
82
  - Add Rails 5.1 support
34
83
 
35
84
  ## 3.0.1
85
+
36
86
  - Qualify call to rspec shared_examples
37
87
 
38
88
  ## 3.0.0
89
+
39
90
  See `UPGRADING.md` for specific help with breaking changes from 2.x to 3.0.0.
40
91
 
41
92
  - Adds support for Devise 4.
@@ -43,33 +94,41 @@ See `UPGRADING.md` for specific help with breaking changes from 2.x to 3.0.0.
43
94
  - Blocks the use of attr_encrypted 2.x. There was a significant vulnerability in the encryption implementation in attr_encrypted 2.x, and that version of the gem should not be used.
44
95
 
45
96
  ## 2.2.0
97
+
46
98
  - Use 192 bits, not 1024, as a secret key length. RFC 4226 recommends a minimum length of 128 bits and a recommended length of 160 bits. Google Authenticator doesn't accept 160 bit keys.
47
99
 
48
100
  ## 2.1.0
101
+
49
102
  - Return false if OTP value is nil, instead of an ROTP exception.
50
103
 
51
104
  ## 2.0.1
105
+
52
106
  No user-facing changes.
53
107
 
54
108
  ## 2.0.0
109
+
55
110
  See `UPGRADING.md` for specific help with breaking changes from 1.x to 2.0.0.
56
111
 
57
112
  - Replace `valid_otp?` method with `validate_and_consume_otp!`.
58
113
  - Disallow subsequent OTPs once validated via timesteps.
59
114
 
60
115
  ## 1.1.0
116
+
61
117
  - Removes runtimez activemodel dependency.
62
118
  - Uses `Devise::Encryptor` instead of `Devise.bcrypt`, which is deprecated.
63
119
  - Bump `rotp` dependency to 2.x.
64
120
 
65
121
  ## 1.0.2
122
+
66
123
  - Makes Railties the only requirement for Rails generators.
67
124
  - Explicitly check that the `otp_attempt` param is not nil in order to avoid 'ROTP only verifies strings' exceptions.
68
125
  - Adding warning about recoverable devise strategy and automatic `sign_in` after a password reset.
69
126
  - Loosen dependency version requirements for rotp, devise, and attr_encrypted.
70
127
 
71
128
  ## 1.0.1
129
+
72
130
  - Add version requirements for dependencies.
73
131
 
74
132
  ## 1.0.0
133
+
75
134
  - Initial release.
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,89 +10,109 @@ 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
- 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.
21
+
22
+ Devise-Two-Factor doesn't require much to get started, but there are two prerequisites before you can start using it in your application:
23
+
24
+ 1. A Rails application with [devise](https://github.com/heartcombo/devise) installed
25
+ 1. Secrets configured for ActiveRecord encrypted attributes
23
26
 
24
27
  First, you'll need a Rails application setup with Devise. Visit the Devise [homepage](https://github.com/plataformatec/devise) for instructions.
25
28
 
26
- You can add Devise-Two-Factor to your Gemfile with:
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.
27
30
 
28
- ```ruby
29
- gem 'devise-two-factor'
31
+ ```bash
32
+ # Generates a random key set and outputs it to stdout
33
+ ./bin/rails db:encryption:init
30
34
  ```
31
35
 
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:
33
-
34
- ```ruby
35
- devise :two_factor_authenticatable,
36
- :otp_secret_encryption_key => ENV['YOUR_ENCRYPTION_KEY_HERE']
36
+ You can load the key set using Rails' credentials.
37
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
38
42
  ```
39
43
 
40
- Finally, you can automate all of the required setup by simply running:
44
+ To learn more about credentials run `./bin/rails credentials:help`.
45
+
46
+ Alternatively, you can configure your application with environment variables rather than Rails' credentials.
41
47
 
42
48
  ```ruby
43
- rails generate devise_two_factor MODEL ENVIRONMENT_VARIABLE
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']
44
54
  ```
45
55
 
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.
56
+ Add Devise-Two-Factor to your Gemfile with:
47
57
 
48
- This generator will add a few columns to the specified model:
58
+ ```ruby
59
+ # Gemfile
49
60
 
50
- * encrypted_otp_secret
51
- * encrypted_otp_secret_iv
52
- * encrypted_otp_secret_salt
53
- * consumed_timestep
54
- * otp_required_for_login
61
+ gem 'devise-two-factor'
62
+ ```
55
63
 
56
- Remember to apply the new migration.
64
+ There is a generator which automates most of the setup:
57
65
 
58
- ```ruby
59
- bundle exec rake db:migrate
66
+ ```bash
67
+ # MODEL is the name of the model you wish to configure devise_two_factor e.g. User or Admin
68
+ ./bin/rails generate devise_two_factor MODEL
60
69
  ```
61
70
 
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.
71
+ Where `MODEL` is the name of the model you wish to add two-factor functionality to (for example `user`)
63
72
 
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.
73
+ This generator will:
65
74
 
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`:
75
+ 1. Create a new migration which adds a few columns to the specified model:
67
76
 
68
- ```ruby
69
- before_action :configure_permitted_parameters, if: :devise_controller?
77
+ ```ruby
78
+ add_column :users, :otp_secret, :string
79
+ add_column :users, :consumed_timestep, :integer
80
+ add_column :users, :otp_required_for_login, :boolean
81
+ ```
70
82
 
71
- ...
83
+ 1. Edit `app/models/MODEL.rb` (where MODEL is your model name):
84
+ * add the `:two_factor_authenticatable` devise module
85
+ * remove the `:database_authenticatable` devise module, if present; having both modules enabled will lead to issues described below.
86
+ 1. Add a Warden config block to your Devise initializer, which enables the strategies required for two-factor authentication.
72
87
 
73
- protected
88
+ Remember to apply the new migration after you run the generator:
74
89
 
75
- def configure_permitted_parameters
76
- devise_parameter_sanitizer.for(:sign_in) << :otp_attempt
77
- end
90
+ ```bash
91
+ ./bin/rails db:migrate
78
92
  ```
79
93
 
80
- If you're running Devise 4.0.0 or above, you'll want to use `.permit` instead:
94
+ 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
95
 
82
96
  ```ruby
83
- before_action :configure_permitted_parameters, if: :devise_controller?
97
+ # app/controllers/application_controller.rb
84
98
 
85
- ...
99
+ before_action :configure_permitted_parameters, if: :devise_controller?
86
100
 
87
- protected
101
+ # ...
88
102
 
89
- def configure_permitted_parameters
90
- devise_parameter_sanitizer.permit(:sign_in, keys: [:otp_attempt])
91
- end
103
+ protected
104
+
105
+ def configure_permitted_parameters
106
+ devise_parameter_sanitizer.permit(:sign_in, keys: [:otp_attempt])
107
+ end
92
108
  ```
93
109
 
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.**
110
+ 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. `:two_factor_authenticatable` includes all of `:database_authenticatable`'s functionality; it will still allow login without two-factor authentication until you enable it on your model's records with `otp_required_for_login`.
111
+
112
+ **Loading both `:database_authenticatable` and `:two_factor_authenticatable` in a model is a security issue.** It will allow users to bypass two-factor authentication regardless of how `otp_required_for_login` is set due to the way Warden handles cascading strategies!
95
113
 
96
114
  ## Designing Your Workflow
115
+
97
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.
98
117
 
99
118
  There are two key workflows you'll have to think about:
@@ -103,8 +122,8 @@ There are two key workflows you'll have to think about:
103
122
 
104
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).
105
124
 
106
-
107
125
  ### Logging In
126
+
108
127
  Logging in with two-factor authentication works extremely similarly to regular database authentication in Devise. The `TwoFactorAuthenticatable` strategy accepts three parameters:
109
128
 
110
129
  1. email
@@ -114,11 +133,13 @@ Logging in with two-factor authentication works extremely similarly to regular d
114
133
  These parameters can be submitted to the standard Devise login route, and the strategy will handle the authentication of the user for you.
115
134
 
116
135
  ### Disabling Automatic Login After Password Resets
136
+
117
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.
118
138
 
119
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`).
120
140
 
121
141
  ### Enabling Two-Factor Authentication
142
+
122
143
  Enabling two-factor authentication for a user is easy. For example, if my user model were named User, I could do the following:
123
144
 
124
145
  ```ruby
@@ -134,10 +155,7 @@ At Tinfoil Security, we opted to use the excellent [rqrcode-rails3](https://gith
134
155
  If you decide to do this you'll need to generate a URI to act as the source for the QR code. This can be done using the `User#otp_provisioning_uri` method.
135
156
 
136
157
  ```ruby
137
- issuer = 'Your App'
138
- label = "#{issuer}:#{current_user.email}"
139
-
140
- current_user.otp_provisioning_uri(label, issuer: issuer)
158
+ current_user.otp_provisioning_uri(current_user.email, issuer: 'Your App')
141
159
 
142
160
  # > "otpauth://totp/Your%20App:user@example.com?secret=[otp_secret]&issuer=Your+App"
143
161
  ```
@@ -149,6 +167,7 @@ current_user.current_otp
149
167
  ```
150
168
 
151
169
  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`.
170
+
152
171
  ```ruby
153
172
  Devise.otp_allowed_drift = 240 # value in seconds
154
173
  Devise.setup do |config|
@@ -165,13 +184,27 @@ However you decide to handle enrollment, there are a few important consideration
165
184
  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.
166
185
 
167
186
  ### Filtering sensitive parameters from the logs
187
+
168
188
  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`:
169
189
 
170
190
  ```ruby
171
191
  Rails.application.config.filter_parameters += [:otp_attempt]
172
192
  ```
173
193
 
194
+ ### Preventing Brute-Force Attacks
195
+
196
+ 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:
197
+
198
+ 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.
199
+ 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).
200
+ 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.
201
+
202
+ #### Acknowledgements
203
+
204
+ Thank you to Christian Reitter (Radically Open Security) and Chris MacNaughton (Centauri Solutions) for reporting the issue.
205
+
174
206
  ## Backup Codes
207
+
175
208
  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.
176
209
 
177
210
  To install it, you need to add the `:two_factor_backupable` directive to your model.
@@ -186,17 +219,39 @@ You'll also be required to enable the `:two_factor_backupable` strategy, by addi
186
219
  manager.default_strategies(:scope => :user).unshift :two_factor_backupable
187
220
  ```
188
221
 
189
- 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:
222
+ ### Migration
223
+
224
+ The final installation step may be dependent on your version of Rails.
225
+
226
+ #### PostgreSQL
190
227
 
191
228
  ```ruby
192
229
  class AddDeviseTwoFactorBackupableToUsers < ActiveRecord::Migration
193
230
  def change
194
- # Change type from :string to :text if using MySQL database
195
231
  add_column :users, :otp_backup_codes, :string, array: true
196
232
  end
197
233
  end
198
234
  ```
199
235
 
236
+ #### MySQL, SQL Server, other databases without an array string type
237
+
238
+ ```ruby
239
+ # migration
240
+ class AddDeviseTwoFactorBackupableToUsers < ActiveRecord::Migration
241
+ def change
242
+ add_column :users, :otp_backup_codes, :text
243
+ end
244
+ end
245
+
246
+ # model
247
+ class User < ApplicationRecord
248
+ devise :two_factor_backupable
249
+ serialize :otp_backup_codes, Array
250
+ end
251
+ ```
252
+
253
+ ### Generation
254
+
200
255
  You can then generate backup codes for a user:
201
256
 
202
257
  ```ruby
@@ -214,23 +269,8 @@ devise :two_factor_backupable, otp_backup_code_length: 32,
214
269
  otp_number_of_backup_codes: 10
215
270
  ```
216
271
 
217
- ### Help! I'm not using Rails 4.0!
218
- 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).
219
-
220
- 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:
221
-
222
- ```ruby
223
- class AddTwoFactorBackupCodesToUsers < ActiveRecord::Migration
224
- def change
225
- # Change type from :string_array to :text_array if using MySQL database
226
- add_column :users, :otp_backup_codes, :string_array
227
- end
228
- end
229
- ```
230
-
231
- Now just continue with the setup in the previous section, skipping the generator step.
232
-
233
272
  ## Testing
273
+
234
274
  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:
235
275
 
236
276
  ```ruby
@@ -241,6 +281,7 @@ it_behaves_like "two_factor_backupable"
241
281
  ```
242
282
 
243
283
  ## Troubleshooting
284
+
244
285
  If you are using Rails 4.x and Ruby >= 2.7, you may get an error like
245
286
 
246
287
  ```
@@ -250,9 +291,11 @@ Failure/Error: require 'devise'
250
291
  NoMethodError:
251
292
  undefined method `new' for BigDecimal:Class
252
293
  ```
294
+
253
295
  see https://github.com/ruby/bigdecimal#which-version-should-you-select and https://github.com/ruby/bigdecimal/issues/127
254
296
  for more details, but you should be able to solve this
255
297
  by explicitly requiring an older version of bigdecimal in your gemfile like
256
- ```
298
+
299
+ ```ruby
257
300
  gem "bigdecimal", "~> 1.4"
258
301
  ```
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).