devise-two-factor 5.1.0 → 6.3.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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +10 -3
- data/.github/workflows/push.yml +28 -0
- data/.markdownlint.json +6 -0
- data/Appraisals +15 -30
- data/CHANGELOG.md +18 -0
- data/README.md +6 -11
- data/Rakefile +2 -0
- data/SECURITY.md +1 -1
- data/UPGRADING.md +26 -12
- data/devise-two-factor.gemspec +4 -8
- data/gemfiles/rails_7.2.gemfile +8 -0
- data/gemfiles/rails_8.0.gemfile +8 -0
- data/gemfiles/rails_8.1.gemfile +8 -0
- data/lib/devise-two-factor.rb +6 -3
- data/lib/devise_two_factor/models/two_factor_authenticatable.rb +13 -14
- data/lib/devise_two_factor/models/two_factor_backupable.rb +5 -1
- data/lib/devise_two_factor/spec_helpers/two_factor_authenticatable_shared_examples.rb +4 -4
- data/lib/devise_two_factor/spec_helpers/two_factor_backupable_shared_examples.rb +51 -23
- data/lib/devise_two_factor/strategies/two_factor_authenticatable.rb +7 -1
- data/lib/devise_two_factor/strategies/two_factor_backupable.rb +6 -4
- data/lib/devise_two_factor/version.rb +1 -1
- data/spec/devise/models/two_factor_authenticatable_spec.rb +1 -1
- data/spec/devise/models/two_factor_backupable_spec.rb +4 -0
- metadata +47 -94
- checksums.yaml.gz.sig +0 -0
- data/certs/tinfoil-cacert.pem +0 -41
- data/certs/tinfoilsecurity-gems-cert.pem +0 -35
- data.tar.gz.sig +0 -0
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4513e96354e689136149f21dbd447785cc74113a66725bed6153e44758a1709b
|
|
4
|
+
data.tar.gz: 9e83133ebdc4166577129276188688780aeec065a5fead7861073496210215e9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a119f4f3f825409f6cea77f934fe7e35fa741dfb1b242c33a59dd523eb43ef2270b3dda80d1403f4baf7ade03271bbd4b145d21c6c390eeb223e00f0aadb6d29
|
|
7
|
+
data.tar.gz: 0f31866cb9c1523ff94095d2b43cf819d2a06a658a2dad94383d93e4c6c47d9e791092090cf8063f351c7727df6f2146e4bf22bf5a204c0b1f80aaf8a4fec3fb
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -12,14 +12,21 @@ 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: ['3.1', '3.2', '3.3', 'truffleruby-head']
|
|
16
|
-
rails: ['7.0', '7.1']
|
|
15
|
+
ruby: ['3.1', '3.2', '3.3', '3.4', 'truffleruby-head']
|
|
16
|
+
rails: ['7.0', '7.1', '7.2', '8.0', '8.1']
|
|
17
|
+
exclude:
|
|
18
|
+
- ruby: '3.1'
|
|
19
|
+
rails: '8.0'
|
|
20
|
+
- ruby: '3.1'
|
|
21
|
+
rails: '8.1'
|
|
22
|
+
- ruby: '3.4'
|
|
23
|
+
rails: '7.0'
|
|
17
24
|
|
|
18
25
|
name: Ruby ${{ matrix.ruby }}, Rails ${{ matrix.rails }}
|
|
19
26
|
env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
|
|
20
27
|
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/rails_${{ matrix.rails }}.gemfile
|
|
21
28
|
steps:
|
|
22
|
-
- uses: actions/checkout@
|
|
29
|
+
- uses: actions/checkout@v6
|
|
23
30
|
- name: Set up Ruby
|
|
24
31
|
uses: ruby/setup-ruby@v1
|
|
25
32
|
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
|
data/.markdownlint.json
ADDED
data/Appraisals
CHANGED
|
@@ -1,39 +1,24 @@
|
|
|
1
|
-
appraise "rails-
|
|
2
|
-
gem 'railties', '~>
|
|
3
|
-
gem 'activesupport', '~>
|
|
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'
|
|
1
|
+
appraise "rails-7.0" do
|
|
2
|
+
gem 'railties', '~> 7.0.0'
|
|
3
|
+
gem 'activesupport', '~> 7.0.0'
|
|
19
4
|
end
|
|
20
5
|
|
|
21
|
-
appraise "rails-
|
|
22
|
-
gem 'railties', '~>
|
|
23
|
-
gem 'activesupport', '~>
|
|
6
|
+
appraise "rails-7.1" do
|
|
7
|
+
gem 'railties', '~> 7.1.0'
|
|
8
|
+
gem 'activesupport', '~> 7.1.0'
|
|
24
9
|
end
|
|
25
10
|
|
|
26
|
-
appraise "rails-
|
|
27
|
-
gem 'railties', '~>
|
|
28
|
-
gem 'activesupport', '~>
|
|
11
|
+
appraise "rails-7.2" do
|
|
12
|
+
gem 'railties', '~> 7.2.0'
|
|
13
|
+
gem 'activesupport', '~> 7.2.0'
|
|
29
14
|
end
|
|
30
15
|
|
|
31
|
-
appraise "rails-
|
|
32
|
-
gem 'railties', '~>
|
|
33
|
-
gem 'activesupport', '~>
|
|
16
|
+
appraise "rails-8.0" do
|
|
17
|
+
gem 'railties', '~> 8.0.0'
|
|
18
|
+
gem 'activesupport', '~> 8.0.0'
|
|
34
19
|
end
|
|
35
20
|
|
|
36
|
-
appraise "rails-
|
|
37
|
-
gem 'railties', '
|
|
38
|
-
gem 'activesupport', '
|
|
21
|
+
appraise "rails-8.1" do
|
|
22
|
+
gem 'railties', '8.1.0'
|
|
23
|
+
gem 'activesupport', '8.1.0'
|
|
39
24
|
end
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 6.3.0
|
|
6
|
+
|
|
7
|
+
- Fixed timing to be consistent when Devise paranoid mode is active.
|
|
8
|
+
|
|
9
|
+
## 6.2.0
|
|
10
|
+
|
|
11
|
+
- Rails 8.1 support
|
|
12
|
+
|
|
13
|
+
## 6.1.0
|
|
14
|
+
|
|
15
|
+
- Rails 8 support
|
|
16
|
+
|
|
17
|
+
## 6.0.0
|
|
18
|
+
|
|
19
|
+
**Breaking Changes**
|
|
20
|
+
- `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).
|
|
21
|
+
- `consume_otp!` and `invalidate_otp_backup_code!` now call `save!` instead of `save`. See [UPGRADING.md](UPGRADING.md).
|
|
22
|
+
|
|
5
23
|
## 5.1.0
|
|
6
24
|
|
|
7
25
|
- Remove faker dev dependency
|
data/README.md
CHANGED
|
@@ -15,9 +15,7 @@ We welcome pull requests, bug reports, and other contributions. We're especially
|
|
|
15
15
|
|
|
16
16
|
## Example App
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
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).
|
|
21
19
|
|
|
22
20
|
## Getting Started
|
|
23
21
|
|
|
@@ -84,7 +82,7 @@ This generator will:
|
|
|
84
82
|
|
|
85
83
|
1. Edit `app/models/MODEL.rb` (where MODEL is your model name):
|
|
86
84
|
* add the `:two_factor_authenticatable` devise module
|
|
87
|
-
* remove the `:database_authenticatable` if present
|
|
85
|
+
* remove the `:database_authenticatable` devise module, if present; having both modules enabled will lead to issues described below.
|
|
88
86
|
1. Add a Warden config block to your Devise initializer, which enables the strategies required for two-factor authentication.
|
|
89
87
|
|
|
90
88
|
Remember to apply the new migration after you run the generator:
|
|
@@ -109,9 +107,9 @@ Next you need to whitelist `:otp_attempt` as a permitted parameter in Devise `:s
|
|
|
109
107
|
end
|
|
110
108
|
```
|
|
111
109
|
|
|
112
|
-
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.
|
|
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`.
|
|
113
111
|
|
|
114
|
-
**Loading both `:database_authenticatable` and `:two_factor_authenticatable` in a model is a security issue
|
|
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!
|
|
115
113
|
|
|
116
114
|
## Designing Your Workflow
|
|
117
115
|
|
|
@@ -157,10 +155,7 @@ At Tinfoil Security, we opted to use the excellent [rqrcode-rails3](https://gith
|
|
|
157
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.
|
|
158
156
|
|
|
159
157
|
```ruby
|
|
160
|
-
issuer
|
|
161
|
-
label = "#{issuer}:#{current_user.email}"
|
|
162
|
-
|
|
163
|
-
current_user.otp_provisioning_uri(label, issuer: issuer)
|
|
158
|
+
current_user.otp_provisioning_uri(current_user.email, issuer: 'Your App')
|
|
164
159
|
|
|
165
160
|
# > "otpauth://totp/Your%20App:user@example.com?secret=[otp_secret]&issuer=Your+App"
|
|
166
161
|
```
|
|
@@ -238,7 +233,7 @@ class AddDeviseTwoFactorBackupableToUsers < ActiveRecord::Migration
|
|
|
238
233
|
end
|
|
239
234
|
```
|
|
240
235
|
|
|
241
|
-
#### MySQL
|
|
236
|
+
#### MySQL, SQL Server, other databases without an array string type
|
|
242
237
|
|
|
243
238
|
```ruby
|
|
244
239
|
# migration
|
data/Rakefile
CHANGED
data/SECURITY.md
CHANGED
data/UPGRADING.md
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
|
-
# Upgrading
|
|
1
|
+
# Upgrading
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Upgrading from 5.x to 6.x
|
|
4
4
|
|
|
5
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
@@ -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
|
-
|
|
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
|
|
|
@@ -200,7 +214,7 @@ This "clean up" phase can happen at the same time as your initial deployment but
|
|
|
200
214
|
devise :two_factor_authenticatable
|
|
201
215
|
```
|
|
202
216
|
|
|
203
|
-
|
|
217
|
+
## Upgrading from 2.x to 3.x
|
|
204
218
|
|
|
205
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.
|
|
206
220
|
|
|
@@ -220,7 +234,7 @@ class User < ActiveRecord::Base
|
|
|
220
234
|
:otp_secret_encryption_key => ENV['DEVISE_TWO_FACTOR_ENCRYPTION_KEY']
|
|
221
235
|
```
|
|
222
236
|
|
|
223
|
-
|
|
237
|
+
## Upgrading from 1.x to 2.x
|
|
224
238
|
|
|
225
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.
|
|
226
240
|
|
data/devise-two-factor.gemspec
CHANGED
|
@@ -11,18 +11,13 @@ Gem::Specification.new do |s|
|
|
|
11
11
|
s.description = 'Devise-Two-Factor is a minimalist extension to Devise which offers support for two-factor authentication through the TOTP scheme.'
|
|
12
12
|
s.authors = ['Quinn Wilton']
|
|
13
13
|
|
|
14
|
-
s.cert_chain = [
|
|
15
|
-
'certs/tinfoil-cacert.pem',
|
|
16
|
-
'certs/tinfoilsecurity-gems-cert.pem'
|
|
17
|
-
]
|
|
18
|
-
s.signing_key = File.expand_path("~/.ssh/tinfoilsecurity-gems-key.pem") if $0 =~ /gem\z/
|
|
19
14
|
s.files = `git ls-files`.split("\n").delete_if { |x| x.match('demo/*') }
|
|
20
15
|
s.test_files = `git ls-files -- spec/*`.split("\n")
|
|
21
16
|
s.require_paths = ['lib']
|
|
22
17
|
|
|
23
|
-
s.add_runtime_dependency 'railties', '
|
|
24
|
-
s.add_runtime_dependency 'activesupport', '
|
|
25
|
-
s.add_runtime_dependency 'devise', '
|
|
18
|
+
s.add_runtime_dependency 'railties', '>= 7.0', '< 8.2'
|
|
19
|
+
s.add_runtime_dependency 'activesupport', '>= 7.0', '< 8.2'
|
|
20
|
+
s.add_runtime_dependency 'devise', '>= 4.0', '< 5.0'
|
|
26
21
|
s.add_runtime_dependency 'rotp', '~> 6.0'
|
|
27
22
|
|
|
28
23
|
s.add_development_dependency 'activemodel'
|
|
@@ -30,4 +25,5 @@ Gem::Specification.new do |s|
|
|
|
30
25
|
s.add_development_dependency 'bundler', '> 1.0'
|
|
31
26
|
s.add_development_dependency 'rspec', '> 3'
|
|
32
27
|
s.add_development_dependency 'simplecov'
|
|
28
|
+
s.add_development_dependency 'rake', '~> 13'
|
|
33
29
|
end
|
data/lib/devise-two-factor.rb
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
require 'logger'
|
|
1
2
|
require 'devise'
|
|
2
3
|
require 'devise_two_factor/models'
|
|
3
4
|
require 'devise_two_factor/strategies'
|
|
4
5
|
|
|
5
6
|
module Devise
|
|
6
|
-
# The length of generated OTP
|
|
7
|
+
# The length of randomly generated OTP shared secret (in bytes).
|
|
8
|
+
# The secrets will be base32-encoded and have a length 1.6 times the configured value.
|
|
7
9
|
mattr_accessor :otp_secret_length
|
|
8
|
-
@@otp_secret_length =
|
|
10
|
+
@@otp_secret_length = 20
|
|
9
11
|
|
|
10
12
|
# The number of seconds before and after the current
|
|
11
13
|
# time for which codes will be accepted
|
|
@@ -20,7 +22,8 @@ module Devise
|
|
|
20
22
|
mattr_accessor :otp_encrypted_attribute_options
|
|
21
23
|
@@otp_encrypted_attribute_options = {}
|
|
22
24
|
|
|
23
|
-
# The length of
|
|
25
|
+
# The length of randomly generated OTP backup codes (in bytes).
|
|
26
|
+
# The codes will be hex-encoded and have a length twice the configured value.
|
|
24
27
|
mattr_accessor :otp_backup_code_length
|
|
25
28
|
@@otp_backup_code_length = 16
|
|
26
29
|
|
|
@@ -41,12 +41,11 @@ module Devise
|
|
|
41
41
|
|
|
42
42
|
if self.consumed_timestep
|
|
43
43
|
# reconstruct the timestamp of the last consumed timestep
|
|
44
|
-
after_timestamp = self.consumed_timestep *
|
|
44
|
+
after_timestamp = self.consumed_timestep * totp.interval
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
end
|
|
47
|
+
timestamp = totp.verify(code.gsub(/\s+/, ""), drift_behind: self.class.otp_allowed_drift, drift_ahead: self.class.otp_allowed_drift, after: after_timestamp)
|
|
48
|
+
return consume_otp!(totp, timestamp) if timestamp
|
|
50
49
|
|
|
51
50
|
false
|
|
52
51
|
end
|
|
@@ -59,11 +58,6 @@ module Devise
|
|
|
59
58
|
otp.at(Time.now)
|
|
60
59
|
end
|
|
61
60
|
|
|
62
|
-
# ROTP's TOTP#timecode is private, so we duplicate it here
|
|
63
|
-
def current_otp_timestep
|
|
64
|
-
Time.now.utc.to_i / otp.interval
|
|
65
|
-
end
|
|
66
|
-
|
|
67
61
|
def otp_provisioning_uri(account, options = {})
|
|
68
62
|
otp_secret = options[:otp_secret] || self.otp_secret
|
|
69
63
|
ROTP::TOTP.new(otp_secret, options).provisioning_uri(account)
|
|
@@ -78,10 +72,14 @@ module Devise
|
|
|
78
72
|
|
|
79
73
|
# An OTP cannot be used more than once in a given timestep
|
|
80
74
|
# Storing timestep of last valid OTP is sufficient to satisfy this requirement
|
|
81
|
-
def consume_otp!
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
def consume_otp!(otp, timestamp)
|
|
76
|
+
timestep = timestamp / otp.interval
|
|
77
|
+
|
|
78
|
+
if self.consumed_timestep != timestep
|
|
79
|
+
self.consumed_timestep = timestep
|
|
80
|
+
save!(validate: false)
|
|
81
|
+
|
|
82
|
+
return true
|
|
85
83
|
end
|
|
86
84
|
|
|
87
85
|
false
|
|
@@ -93,8 +91,9 @@ module Devise
|
|
|
93
91
|
:otp_encrypted_attribute_options,
|
|
94
92
|
:otp_secret_encryption_key)
|
|
95
93
|
|
|
94
|
+
# Geneartes an OTP secret of the specified length, returning it after Base32 encoding.
|
|
96
95
|
def generate_otp_secret(otp_secret_length = self.otp_secret_length)
|
|
97
|
-
ROTP::Base32.
|
|
96
|
+
ROTP::Base32.random(otp_secret_length)
|
|
98
97
|
end
|
|
99
98
|
|
|
100
99
|
# 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
|
|
23
|
+
codes << SecureRandom.hex(code_length)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
hashed_codes = codes.map { |code| Devise::Encryptor.digest(self.class, code) }
|
|
@@ -34,11 +34,15 @@ module Devise
|
|
|
34
34
|
def invalidate_otp_backup_code!(code)
|
|
35
35
|
codes = self.otp_backup_codes || []
|
|
36
36
|
|
|
37
|
+
# Should we still have some other kind of non iterable result, terminate.
|
|
38
|
+
raise TypeError.new("`otp_backup_codes` is expected to be an Array, got #{codes.class.name}. Hint: If your database does not support arrays, does your model correctly `serialize :otp_backup_codes, Array`?") unless codes.is_a?(Array)
|
|
39
|
+
|
|
37
40
|
codes.each do |backup_code|
|
|
38
41
|
next unless Devise::Encryptor.compare(self.class, backup_code, code)
|
|
39
42
|
|
|
40
43
|
codes.delete(backup_code)
|
|
41
44
|
self.otp_backup_codes = codes
|
|
45
|
+
save!(validate: false)
|
|
42
46
|
return true
|
|
43
47
|
end
|
|
44
48
|
|
|
@@ -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
|
|
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
|
|
|
@@ -129,11 +129,11 @@ RSpec.shared_examples 'two_factor_authenticatable' do
|
|
|
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
|
|
|
@@ -49,38 +49,66 @@ RSpec.shared_examples 'two_factor_backupable' do
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
describe '#invalidate_otp_backup_code!' do
|
|
52
|
-
before do
|
|
53
|
-
@plaintext_codes = subject.generate_otp_backup_codes!
|
|
54
|
-
end
|
|
55
52
|
|
|
56
|
-
context 'given an invalid recovery code' do
|
|
57
|
-
it 'returns false' do
|
|
58
|
-
expect(subject.invalidate_otp_backup_code!('password')).to be false
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
53
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
expect(subject.invalidate_otp_backup_code!(
|
|
54
|
+
describe "#invalidate_otp_backup_code!" do
|
|
55
|
+
context "with no backup codes" do
|
|
56
|
+
it "does nothing" do
|
|
57
|
+
expect(subject.invalidate_otp_backup_code!("foo")).to be false
|
|
66
58
|
end
|
|
67
59
|
end
|
|
68
60
|
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
context "with an array of backup codes, newly generated" do
|
|
62
|
+
before do
|
|
63
|
+
@plaintext_codes = subject.generate_otp_backup_codes!
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
context 'given an invalid recovery code' do
|
|
67
|
+
it 'returns false' do
|
|
68
|
+
expect(subject.invalidate_otp_backup_code!('password')).to be false
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context 'given a valid recovery code' do
|
|
73
|
+
it 'returns true' do
|
|
74
|
+
@plaintext_codes.each do |code|
|
|
75
|
+
expect(subject.invalidate_otp_backup_code!(code)).to be true
|
|
76
|
+
end
|
|
77
|
+
end
|
|
71
78
|
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
it 'invalidates that recovery code' do
|
|
80
|
+
code = @plaintext_codes.sample
|
|
81
|
+
|
|
82
|
+
subject.invalidate_otp_backup_code!(code)
|
|
83
|
+
expect(subject.invalidate_otp_backup_code!(code)).to be false
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'does not invalidate the other recovery codes' do
|
|
87
|
+
code = @plaintext_codes.sample
|
|
88
|
+
subject.invalidate_otp_backup_code!(code)
|
|
89
|
+
|
|
90
|
+
@plaintext_codes.delete(code)
|
|
91
|
+
|
|
92
|
+
@plaintext_codes.each do |code|
|
|
93
|
+
expect(subject.invalidate_otp_backup_code!(code)).to be true
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
74
97
|
end
|
|
75
98
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
99
|
+
context "with backup codes as a string" do
|
|
100
|
+
before do
|
|
101
|
+
@plaintext_codes = subject.generate_otp_backup_codes!
|
|
79
102
|
|
|
80
|
-
|
|
103
|
+
# Simulates database adapters that don't understand `t.string :otp_backup_codes, type: array` properly
|
|
104
|
+
# such as SQL Server; and have just returned the serialized string still.
|
|
105
|
+
# and the user not having done:
|
|
106
|
+
# `serialize :otp_backup_codes, Array` in their model
|
|
107
|
+
subject.otp_backup_codes = subject.otp_backup_codes.to_json
|
|
108
|
+
end
|
|
81
109
|
|
|
82
|
-
|
|
83
|
-
expect
|
|
110
|
+
it "raises a meaningful error" do
|
|
111
|
+
expect { subject.invalidate_otp_backup_code!("flork") }.to raise_error(TypeError)
|
|
84
112
|
end
|
|
85
113
|
end
|
|
86
114
|
end
|
|
@@ -4,12 +4,18 @@ module Devise
|
|
|
4
4
|
|
|
5
5
|
def authenticate!
|
|
6
6
|
resource = mapping.to.find_for_database_authentication(authentication_hash)
|
|
7
|
+
|
|
8
|
+
hashed = false
|
|
7
9
|
# We authenticate in two cases:
|
|
8
10
|
# 1. The password and the OTP are correct
|
|
9
11
|
# 2. The password is correct, and OTP is not required for login
|
|
10
12
|
# We check the OTP, then defer to DatabaseAuthenticatable
|
|
11
|
-
if validate(resource) { validate_otp(resource) }
|
|
13
|
+
if validate(resource) { hashed = true; validate_otp(resource) }
|
|
12
14
|
super
|
|
15
|
+
else
|
|
16
|
+
# Paranoid mode: do the expensive hash even when resource is nil,
|
|
17
|
+
# to avoid timing-based user enumeration.
|
|
18
|
+
mapping.to.new.password = password if !hashed && Devise.paranoid
|
|
13
19
|
end
|
|
14
20
|
|
|
15
21
|
fail(Devise.paranoid ? :invalid : :not_found_in_database) unless resource
|
|
@@ -5,10 +5,7 @@ module Devise
|
|
|
5
5
|
def authenticate!
|
|
6
6
|
resource = mapping.to.find_for_database_authentication(authentication_hash)
|
|
7
7
|
|
|
8
|
-
if validate(resource) { resource
|
|
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!
|
|
8
|
+
if validate(resource) { validate_backup_code(resource) }
|
|
12
9
|
super
|
|
13
10
|
end
|
|
14
11
|
|
|
@@ -18,6 +15,11 @@ module Devise
|
|
|
18
15
|
# but database authenticatable automatically halts on a bad password
|
|
19
16
|
@halted = false if @result == :failure
|
|
20
17
|
end
|
|
18
|
+
|
|
19
|
+
def validate_backup_code(resource)
|
|
20
|
+
return if params[scope].nil? || params[scope]['otp_attempt'].nil?
|
|
21
|
+
resource.invalidate_otp_backup_code!(params[scope]['otp_attempt'])
|
|
22
|
+
end
|
|
21
23
|
end
|
|
22
24
|
end
|
|
23
25
|
end
|
metadata
CHANGED
|
@@ -1,135 +1,74 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: devise-two-factor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 6.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Quinn Wilton
|
|
8
|
-
autorequire:
|
|
9
8
|
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: 2024-06-18 00:00:00.000000000 Z
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
90
11
|
dependencies:
|
|
91
12
|
- !ruby/object:Gem::Dependency
|
|
92
13
|
name: railties
|
|
93
14
|
requirement: !ruby/object:Gem::Requirement
|
|
94
15
|
requirements:
|
|
95
|
-
- - "
|
|
16
|
+
- - ">="
|
|
96
17
|
- !ruby/object:Gem::Version
|
|
97
18
|
version: '7.0'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '8.2'
|
|
98
22
|
type: :runtime
|
|
99
23
|
prerelease: false
|
|
100
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
101
25
|
requirements:
|
|
102
|
-
- - "
|
|
26
|
+
- - ">="
|
|
103
27
|
- !ruby/object:Gem::Version
|
|
104
28
|
version: '7.0'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '8.2'
|
|
105
32
|
- !ruby/object:Gem::Dependency
|
|
106
33
|
name: activesupport
|
|
107
34
|
requirement: !ruby/object:Gem::Requirement
|
|
108
35
|
requirements:
|
|
109
|
-
- - "
|
|
36
|
+
- - ">="
|
|
110
37
|
- !ruby/object:Gem::Version
|
|
111
38
|
version: '7.0'
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '8.2'
|
|
112
42
|
type: :runtime
|
|
113
43
|
prerelease: false
|
|
114
44
|
version_requirements: !ruby/object:Gem::Requirement
|
|
115
45
|
requirements:
|
|
116
|
-
- - "
|
|
46
|
+
- - ">="
|
|
117
47
|
- !ruby/object:Gem::Version
|
|
118
48
|
version: '7.0'
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '8.2'
|
|
119
52
|
- !ruby/object:Gem::Dependency
|
|
120
53
|
name: devise
|
|
121
54
|
requirement: !ruby/object:Gem::Requirement
|
|
122
55
|
requirements:
|
|
123
|
-
- - "
|
|
56
|
+
- - ">="
|
|
124
57
|
- !ruby/object:Gem::Version
|
|
125
58
|
version: '4.0'
|
|
59
|
+
- - "<"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '5.0'
|
|
126
62
|
type: :runtime
|
|
127
63
|
prerelease: false
|
|
128
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
129
65
|
requirements:
|
|
130
|
-
- - "
|
|
66
|
+
- - ">="
|
|
131
67
|
- !ruby/object:Gem::Version
|
|
132
68
|
version: '4.0'
|
|
69
|
+
- - "<"
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: '5.0'
|
|
133
72
|
- !ruby/object:Gem::Dependency
|
|
134
73
|
name: rotp
|
|
135
74
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -214,16 +153,31 @@ dependencies:
|
|
|
214
153
|
- - ">="
|
|
215
154
|
- !ruby/object:Gem::Version
|
|
216
155
|
version: '0'
|
|
156
|
+
- !ruby/object:Gem::Dependency
|
|
157
|
+
name: rake
|
|
158
|
+
requirement: !ruby/object:Gem::Requirement
|
|
159
|
+
requirements:
|
|
160
|
+
- - "~>"
|
|
161
|
+
- !ruby/object:Gem::Version
|
|
162
|
+
version: '13'
|
|
163
|
+
type: :development
|
|
164
|
+
prerelease: false
|
|
165
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
166
|
+
requirements:
|
|
167
|
+
- - "~>"
|
|
168
|
+
- !ruby/object:Gem::Version
|
|
169
|
+
version: '13'
|
|
217
170
|
description: Devise-Two-Factor is a minimalist extension to Devise which offers support
|
|
218
171
|
for two-factor authentication through the TOTP scheme.
|
|
219
|
-
email:
|
|
220
172
|
executables: []
|
|
221
173
|
extensions: []
|
|
222
174
|
extra_rdoc_files: []
|
|
223
175
|
files:
|
|
224
176
|
- ".github/dependabot.yml"
|
|
225
177
|
- ".github/workflows/ci.yml"
|
|
178
|
+
- ".github/workflows/push.yml"
|
|
226
179
|
- ".gitignore"
|
|
180
|
+
- ".markdownlint.json"
|
|
227
181
|
- ".rspec"
|
|
228
182
|
- Appraisals
|
|
229
183
|
- CHANGELOG.md
|
|
@@ -234,11 +188,12 @@ files:
|
|
|
234
188
|
- Rakefile
|
|
235
189
|
- SECURITY.md
|
|
236
190
|
- UPGRADING.md
|
|
237
|
-
- certs/tinfoil-cacert.pem
|
|
238
|
-
- certs/tinfoilsecurity-gems-cert.pem
|
|
239
191
|
- devise-two-factor.gemspec
|
|
240
192
|
- gemfiles/rails_7.0.gemfile
|
|
241
193
|
- gemfiles/rails_7.1.gemfile
|
|
194
|
+
- gemfiles/rails_7.2.gemfile
|
|
195
|
+
- gemfiles/rails_8.0.gemfile
|
|
196
|
+
- gemfiles/rails_8.1.gemfile
|
|
242
197
|
- lib/devise-two-factor.rb
|
|
243
198
|
- lib/devise_two_factor/models.rb
|
|
244
199
|
- lib/devise_two_factor/models/two_factor_authenticatable.rb
|
|
@@ -258,7 +213,6 @@ homepage: https://github.com/devise-two-factor/devise-two-factor
|
|
|
258
213
|
licenses:
|
|
259
214
|
- MIT
|
|
260
215
|
metadata: {}
|
|
261
|
-
post_install_message:
|
|
262
216
|
rdoc_options: []
|
|
263
217
|
require_paths:
|
|
264
218
|
- lib
|
|
@@ -273,8 +227,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
273
227
|
- !ruby/object:Gem::Version
|
|
274
228
|
version: '0'
|
|
275
229
|
requirements: []
|
|
276
|
-
rubygems_version:
|
|
277
|
-
signing_key:
|
|
230
|
+
rubygems_version: 4.0.3
|
|
278
231
|
specification_version: 4
|
|
279
232
|
summary: Barebones two-factor authentication with Devise
|
|
280
233
|
test_files:
|
checksums.yaml.gz.sig
DELETED
|
Binary file
|
data/certs/tinfoil-cacert.pem
DELETED
|
@@ -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-----
|
data.tar.gz.sig
DELETED
|
Binary file
|
metadata.gz.sig
DELETED
|
Binary file
|