devise-two-factor 5.1.0 → 6.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c55843526e6b06f1804f07a6c38bd6a247006d90ba43a5f6659cb8c0f98d53ff
4
- data.tar.gz: e0e67eab0624fa0fa88f9719afbbc1da169bcf280f60d249efd96bc98ad10629
3
+ metadata.gz: b6893a24ebfb4eae935305d8b0657383c1630eb7619e23dfcd9ad0190bf1cd64
4
+ data.tar.gz: 24bab113bff7ae25afd03f9230caf372f8bdb0c319eeb18ab469353ca5136de1
5
5
  SHA512:
6
- metadata.gz: cc10ef88ba898b09fd310dc3015853027b8120915d35c2c491dad9976d5809981486efc4d49e4d44abdbf01573ca643de31029a279bffc43609b92e8101cf0eb
7
- data.tar.gz: 2dd29e26cc88edcea8044f6acf22d55275550d9d5c02cbc07337f1509b7b61de766b9e2ab6f1cc436ee569c016fea51130b4bff42853088807c576e2c363f9d0
6
+ metadata.gz: 0b78eedcbafc38b967fb8a6717b9f70f87751b234ce905f5b4d99a176224e4927bc7039a69bb454daba16c8b07b368dc1d9a89e19016910733fd5f744f857a7c
7
+ data.tar.gz: '0199a55f34cc5b67375620c1acabbca8b7256ead8b5401a6917dc2589b64158080851c9d5a061bc7fba1b95d36034f4e24aa13f1efb802a9cf44d1e202f407a0'
@@ -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@v4
29
+ - uses: actions/checkout@v5
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@v5
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,24 @@
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'
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-5.2" do
22
- gem 'railties', '~> 5.2'
23
- gem 'activesupport', '~> 5.2'
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-6.0" do
27
- gem 'railties', '~> 6.0'
28
- gem 'activesupport', '~> 6.0'
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-6.1" do
32
- gem 'railties', '~> 6.1'
33
- gem 'activesupport', '~> 6.1'
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-7.0" do
37
- gem 'railties', '~> 7.0'
38
- gem 'activesupport', '~> 7.0'
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,20 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 6.2.0
6
+
7
+ - Rails 8.1 support
8
+
9
+ ## 6.1.0
10
+
11
+ - Rails 8 support
12
+
13
+ ## 6.0.0
14
+
15
+ **Breaking Changes**
16
+ - `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).
17
+ - `consume_otp!` and `invalidate_otp_backup_code!` now call `save!` instead of `save`. See [UPGRADING.md](UPGRADING.md).
18
+
5
19
  ## 5.1.0
6
20
 
7
21
  - 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
- 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.
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 because it is incompatible with `:two_factor_authenticatable`
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** It will allow users to bypass two-factor authenticatable due to the way Warden handles cascading strategies!
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 = 'Your App'
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
  ```
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 CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  ## Reporting a Vulnerability
4
4
 
5
- Please report any vulnerabilities to the [Synopsys PSIRT](https://www.synopsys.com/company/legal/vulnerability-disclosure-policy.html).
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.
@@ -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
 
@@ -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
- # Guide to upgrading from 2.x to 3.x
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
- # Guide to upgrading from 1.x to 2.x
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
 
@@ -11,17 +11,12 @@ 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', '~> 7.0'
24
- s.add_runtime_dependency 'activesupport', '~> 7.0'
18
+ s.add_runtime_dependency 'railties', '>= 7.0', '< 8.2'
19
+ s.add_runtime_dependency 'activesupport', '>= 7.0', '< 8.2'
25
20
  s.add_runtime_dependency 'devise', '~> 4.0'
26
21
  s.add_runtime_dependency 'rotp', '~> 6.0'
27
22
 
@@ -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
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "railties", "~> 7.2.0"
6
+ gem "activesupport", "~> 7.2.0"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "railties", "~> 8.0.0"
6
+ gem "activesupport", "~> 8.0.0"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "railties", "8.1.0"
6
+ gem "activesupport", "8.1.0"
7
+
8
+ gemspec path: "../"
@@ -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 secrets
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 = 24
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 all generated OTP backup codes
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 * otp.interval
44
+ after_timestamp = self.consumed_timestep * totp.interval
45
45
  end
46
46
 
47
- if 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!
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
- if self.consumed_timestep != current_otp_timestep
83
- self.consumed_timestep = current_otp_timestep
84
- return save(validate: false)
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.random_base32(otp_secret_length)
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 / 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) }
@@ -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
 
@@ -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
 
@@ -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.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!
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
@@ -1,3 +1,3 @@
1
1
  module DeviseTwoFactor
2
- VERSION = '5.1.0'.freeze
2
+ VERSION = '6.2.0'.freeze
3
3
  end
@@ -18,7 +18,7 @@ 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
@@ -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
metadata CHANGED
@@ -1,121 +1,54 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise-two-factor
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.0
4
+ version: 6.2.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: 2025-10-22 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
@@ -214,16 +147,31 @@ dependencies:
214
147
  - - ">="
215
148
  - !ruby/object:Gem::Version
216
149
  version: '0'
150
+ - !ruby/object:Gem::Dependency
151
+ name: rake
152
+ requirement: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - "~>"
155
+ - !ruby/object:Gem::Version
156
+ version: '13'
157
+ type: :development
158
+ prerelease: false
159
+ version_requirements: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - "~>"
162
+ - !ruby/object:Gem::Version
163
+ version: '13'
217
164
  description: Devise-Two-Factor is a minimalist extension to Devise which offers support
218
165
  for two-factor authentication through the TOTP scheme.
219
- email:
220
166
  executables: []
221
167
  extensions: []
222
168
  extra_rdoc_files: []
223
169
  files:
224
170
  - ".github/dependabot.yml"
225
171
  - ".github/workflows/ci.yml"
172
+ - ".github/workflows/push.yml"
226
173
  - ".gitignore"
174
+ - ".markdownlint.json"
227
175
  - ".rspec"
228
176
  - Appraisals
229
177
  - CHANGELOG.md
@@ -234,11 +182,12 @@ files:
234
182
  - Rakefile
235
183
  - SECURITY.md
236
184
  - UPGRADING.md
237
- - certs/tinfoil-cacert.pem
238
- - certs/tinfoilsecurity-gems-cert.pem
239
185
  - devise-two-factor.gemspec
240
186
  - gemfiles/rails_7.0.gemfile
241
187
  - gemfiles/rails_7.1.gemfile
188
+ - gemfiles/rails_7.2.gemfile
189
+ - gemfiles/rails_8.0.gemfile
190
+ - gemfiles/rails_8.1.gemfile
242
191
  - lib/devise-two-factor.rb
243
192
  - lib/devise_two_factor/models.rb
244
193
  - lib/devise_two_factor/models/two_factor_authenticatable.rb
@@ -258,7 +207,6 @@ homepage: https://github.com/devise-two-factor/devise-two-factor
258
207
  licenses:
259
208
  - MIT
260
209
  metadata: {}
261
- post_install_message:
262
210
  rdoc_options: []
263
211
  require_paths:
264
212
  - lib
@@ -273,8 +221,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
273
221
  - !ruby/object:Gem::Version
274
222
  version: '0'
275
223
  requirements: []
276
- rubygems_version: 3.0.3.1
277
- signing_key:
224
+ rubygems_version: 3.6.2
278
225
  specification_version: 4
279
226
  summary: Barebones two-factor authentication with Devise
280
227
  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-----
data.tar.gz.sig DELETED
Binary file
metadata.gz.sig DELETED
Binary file