devise-two-factor 1.1.0 → 2.0.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.
Potentially problematic release.
This version of devise-two-factor might be problematic. Click here for more details.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/UPGRADING.md +11 -0
- data/devise-two-factor.gemspec +1 -1
- data/lib/devise_two_factor/models/two_factor_authenticatable.rb +22 -4
- data/lib/devise_two_factor/spec_helpers/two_factor_authenticatable_shared_examples.rb +38 -12
- data/lib/devise_two_factor/spec_helpers/two_factor_backupable_shared_examples.rb +10 -11
- data/lib/devise_two_factor/strategies/two_factor_authenticatable.rb +1 -1
- data/lib/devise_two_factor/version.rb +1 -1
- data/lib/generators/devise_two_factor/devise_two_factor_generator.rb +1 -0
- data/spec/devise/models/two_factor_authenticatable_spec.rb +7 -0
- metadata +32 -36
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 139ae00abf04084452d11ccb85fba46e18ba36bb
|
4
|
+
data.tar.gz: f8ebe13d8db9f67663306e854be967bd08d39460
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4114ed8083d8daacbd15355132dc02c1bc33770bc8018a8a858e0888e1200eae1420be1eccc79af96b7ba5374af60c6a81562300b39b629ccdaba9f373c3b968
|
7
|
+
data.tar.gz: 74bc2b4571a54206c9d3cf256eaa27bedf1e57f3c99ae1a7424beef6217afd8bfb4b22b5e6f5be3fcf8a3a7c894d820e46e019c1f14c47cd83e2f80a16089dd9
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/UPGRADING.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Guide to upgrading from 1.x to 2.x
|
2
|
+
|
3
|
+
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.
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
class AddConsumedTimestepToUsers < ActiveRecord::Migration
|
7
|
+
def change
|
8
|
+
add_column :users, :consumed_timestep, :integer
|
9
|
+
end
|
10
|
+
end
|
11
|
+
```
|
data/devise-two-factor.gemspec
CHANGED
@@ -32,7 +32,7 @@ Gem::Specification.new do |s|
|
|
32
32
|
|
33
33
|
s.add_development_dependency 'activemodel'
|
34
34
|
s.add_development_dependency 'bundler', '> 1.0'
|
35
|
-
s.add_development_dependency 'rspec', '>
|
35
|
+
s.add_development_dependency 'rspec', '> 3'
|
36
36
|
s.add_development_dependency 'simplecov'
|
37
37
|
s.add_development_dependency 'faker'
|
38
38
|
s.add_development_dependency 'timecop'
|
@@ -15,17 +15,19 @@ module Devise
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.required_fields(klass)
|
18
|
-
[:encrypted_otp_secret, :encrypted_otp_secret_iv, :encrypted_otp_secret_salt]
|
18
|
+
[:encrypted_otp_secret, :encrypted_otp_secret_iv, :encrypted_otp_secret_salt, :consumed_timestep]
|
19
19
|
end
|
20
20
|
|
21
21
|
# This defaults to the model's otp_secret
|
22
|
-
# If this hasn't been generated yet, pass a secret as an
|
23
|
-
def
|
22
|
+
# If this hasn't been generated yet, pass a secret as an option
|
23
|
+
def validate_and_consume_otp!(code, options = {})
|
24
24
|
otp_secret = options[:otp_secret] || self.otp_secret
|
25
25
|
return false unless otp_secret.present?
|
26
26
|
|
27
27
|
totp = self.otp(otp_secret)
|
28
|
-
totp.verify_with_drift(code, self.class.otp_allowed_drift)
|
28
|
+
return consume_otp! if totp.verify_with_drift(code, self.class.otp_allowed_drift)
|
29
|
+
|
30
|
+
false
|
29
31
|
end
|
30
32
|
|
31
33
|
def otp(otp_secret = self.otp_secret)
|
@@ -36,6 +38,11 @@ module Devise
|
|
36
38
|
otp.at(Time.now)
|
37
39
|
end
|
38
40
|
|
41
|
+
# ROTP's TOTP#timecode is private, so we duplicate it here
|
42
|
+
def current_otp_timestep
|
43
|
+
Time.now.utc.to_i / otp.interval
|
44
|
+
end
|
45
|
+
|
39
46
|
def otp_provisioning_uri(account, options = {})
|
40
47
|
otp_secret = options[:otp_secret] || self.otp_secret
|
41
48
|
ROTP::TOTP.new(otp_secret, options).provisioning_uri(account)
|
@@ -47,6 +54,17 @@ module Devise
|
|
47
54
|
|
48
55
|
protected
|
49
56
|
|
57
|
+
# An OTP cannot be used more than once in a given timestep
|
58
|
+
# Storing timestep of last valid OTP is sufficient to satisfy this requirement
|
59
|
+
def consume_otp!
|
60
|
+
if self.consumed_timestep != current_otp_timestep
|
61
|
+
self.consumed_timestep = current_otp_timestep
|
62
|
+
return save(validate: false)
|
63
|
+
end
|
64
|
+
|
65
|
+
false
|
66
|
+
end
|
67
|
+
|
50
68
|
module ClassMethods
|
51
69
|
Devise::Models.config(self, :otp_secret_length,
|
52
70
|
:otp_allowed_drift,
|
@@ -5,29 +5,29 @@ shared_examples 'two_factor_authenticatable' do
|
|
5
5
|
|
6
6
|
describe 'required_fields' do
|
7
7
|
it 'should have the attr_encrypted fields for otp_secret' do
|
8
|
-
Devise::Models::TwoFactorAuthenticatable.required_fields(subject.class).
|
8
|
+
expect(Devise::Models::TwoFactorAuthenticatable.required_fields(subject.class)).to contain_exactly(:encrypted_otp_secret, :encrypted_otp_secret_iv, :encrypted_otp_secret_salt, :consumed_timestep)
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
12
|
describe '#otp_secret' do
|
13
13
|
it 'should be of the configured length' do
|
14
|
-
subject.otp_secret.length.
|
14
|
+
expect(subject.otp_secret.length).to eq(subject.class.otp_secret_length)
|
15
15
|
end
|
16
16
|
|
17
17
|
it 'stores the encrypted otp_secret' do
|
18
|
-
subject.encrypted_otp_secret.
|
18
|
+
expect(subject.encrypted_otp_secret).to_not be_nil
|
19
19
|
end
|
20
20
|
|
21
21
|
it 'stores an iv for otp_secret' do
|
22
|
-
subject.encrypted_otp_secret_iv.
|
22
|
+
expect(subject.encrypted_otp_secret_iv).to_not be_nil
|
23
23
|
end
|
24
24
|
|
25
25
|
it 'stores a salt for otp_secret' do
|
26
|
-
subject.encrypted_otp_secret_salt.
|
26
|
+
expect(subject.encrypted_otp_secret_salt).to_not be_nil
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
describe '#
|
30
|
+
describe '#validate_and_consume_otp!' do
|
31
31
|
let(:otp_secret) { '2z6hxkdwi3uvrnpn' }
|
32
32
|
|
33
33
|
before :each do
|
@@ -39,24 +39,50 @@ shared_examples 'two_factor_authenticatable' do
|
|
39
39
|
Timecop.return
|
40
40
|
end
|
41
41
|
|
42
|
+
context 'with a stored consumed_timestep' do
|
43
|
+
context 'given a precisely correct OTP' do
|
44
|
+
let(:consumed_otp) { ROTP::TOTP.new(otp_secret).at(Time.now) }
|
45
|
+
|
46
|
+
before do
|
47
|
+
subject.validate_and_consume_otp!(consumed_otp)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'fails to validate' do
|
51
|
+
expect(subject.validate_and_consume_otp!(consumed_otp)).to be false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'given a previously valid OTP within the allowed drift' do
|
56
|
+
let(:consumed_otp) { ROTP::TOTP.new(otp_secret).at(Time.now + subject.class.otp_allowed_drift, true) }
|
57
|
+
|
58
|
+
before do
|
59
|
+
subject.validate_and_consume_otp!(consumed_otp)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'fails to validate' do
|
63
|
+
expect(subject.validate_and_consume_otp!(consumed_otp)).to be false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
42
68
|
it 'validates a precisely correct OTP' do
|
43
69
|
otp = ROTP::TOTP.new(otp_secret).at(Time.now)
|
44
|
-
subject.
|
70
|
+
expect(subject.validate_and_consume_otp!(otp)).to be true
|
45
71
|
end
|
46
72
|
|
47
73
|
it 'validates an OTP within the allowed drift' do
|
48
74
|
otp = ROTP::TOTP.new(otp_secret).at(Time.now + subject.class.otp_allowed_drift, true)
|
49
|
-
subject.
|
75
|
+
expect(subject.validate_and_consume_otp!(otp)).to be true
|
50
76
|
end
|
51
77
|
|
52
78
|
it 'does not validate an OTP above the allowed drift' do
|
53
79
|
otp = ROTP::TOTP.new(otp_secret).at(Time.now + subject.class.otp_allowed_drift * 2, true)
|
54
|
-
subject.
|
80
|
+
expect(subject.validate_and_consume_otp!(otp)).to be false
|
55
81
|
end
|
56
82
|
|
57
83
|
it 'does not validate an OTP below the allowed drift' do
|
58
84
|
otp = ROTP::TOTP.new(otp_secret).at(Time.now - subject.class.otp_allowed_drift * 2, true)
|
59
|
-
subject.
|
85
|
+
expect(subject.validate_and_consume_otp!(otp)).to be false
|
60
86
|
end
|
61
87
|
end
|
62
88
|
|
@@ -66,11 +92,11 @@ shared_examples 'two_factor_authenticatable' do
|
|
66
92
|
let(:issuer) { "Tinfoil" }
|
67
93
|
|
68
94
|
it "should return uri with specified account" do
|
69
|
-
subject.otp_provisioning_uri(account).
|
95
|
+
expect(subject.otp_provisioning_uri(account)).to match(%r{otpauth://totp/#{account}\?secret=\w{#{otp_secret_length}}})
|
70
96
|
end
|
71
97
|
|
72
98
|
it 'should return uri with issuer option' do
|
73
|
-
subject.otp_provisioning_uri(account, issuer: issuer).
|
99
|
+
expect(subject.otp_provisioning_uri(account, issuer: issuer)).to match(%r{otpauth://totp/#{account}\?secret=\w{#{otp_secret_length}}&issuer=#{issuer}$})
|
74
100
|
end
|
75
101
|
end
|
76
102
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
shared_examples 'two_factor_backupable' do
|
2
2
|
describe 'required_fields' do
|
3
3
|
it 'has the attr_encrypted fields for otp_backup_codes' do
|
4
|
-
Devise::Models::TwoFactorBackupable.required_fields(subject.class).
|
4
|
+
expect(Devise::Models::TwoFactorBackupable.required_fields(subject.class)).to contain_exactly(:otp_backup_codes)
|
5
5
|
end
|
6
6
|
end
|
7
7
|
|
@@ -12,24 +12,23 @@ shared_examples 'two_factor_backupable' do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
it 'generates the correct number of new recovery codes' do
|
15
|
-
subject.otp_backup_codes.length.
|
16
|
-
eq(subject.class.otp_number_of_backup_codes)
|
15
|
+
expect(subject.otp_backup_codes.length).to eq(subject.class.otp_number_of_backup_codes)
|
17
16
|
end
|
18
17
|
|
19
18
|
it 'generates recovery codes of the correct length' do
|
20
19
|
@plaintext_codes.each do |code|
|
21
|
-
code.length.
|
20
|
+
expect(code.length).to eq(subject.class.otp_backup_code_length)
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
25
24
|
it 'generates distinct recovery codes' do
|
26
|
-
@plaintext_codes.uniq.
|
25
|
+
expect(@plaintext_codes.uniq).to contain_exactly(*@plaintext_codes)
|
27
26
|
end
|
28
27
|
|
29
28
|
it 'stores the codes as BCrypt hashes' do
|
30
29
|
subject.otp_backup_codes.each do |code|
|
31
30
|
# $algorithm$cost$(22 character salt + 31 character hash)
|
32
|
-
code.
|
31
|
+
expect(code).to match(/\A\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}\z/)
|
33
32
|
end
|
34
33
|
end
|
35
34
|
end
|
@@ -44,7 +43,7 @@ shared_examples 'two_factor_backupable' do
|
|
44
43
|
end
|
45
44
|
|
46
45
|
it 'invalidates the existing recovery codes' do
|
47
|
-
(subject.otp_backup_codes & old_codes_hashed).
|
46
|
+
expect((subject.otp_backup_codes & old_codes_hashed)).to match []
|
48
47
|
end
|
49
48
|
end
|
50
49
|
end
|
@@ -56,14 +55,14 @@ shared_examples 'two_factor_backupable' do
|
|
56
55
|
|
57
56
|
context 'given an invalid recovery code' do
|
58
57
|
it 'returns false' do
|
59
|
-
subject.invalidate_otp_backup_code!('password').
|
58
|
+
expect(subject.invalidate_otp_backup_code!('password')).to be false
|
60
59
|
end
|
61
60
|
end
|
62
61
|
|
63
62
|
context 'given a valid recovery code' do
|
64
63
|
it 'returns true' do
|
65
64
|
@plaintext_codes.each do |code|
|
66
|
-
subject.invalidate_otp_backup_code!(code).
|
65
|
+
expect(subject.invalidate_otp_backup_code!(code)).to be true
|
67
66
|
end
|
68
67
|
end
|
69
68
|
|
@@ -71,7 +70,7 @@ shared_examples 'two_factor_backupable' do
|
|
71
70
|
code = @plaintext_codes.sample
|
72
71
|
|
73
72
|
subject.invalidate_otp_backup_code!(code)
|
74
|
-
subject.invalidate_otp_backup_code!(code).
|
73
|
+
expect(subject.invalidate_otp_backup_code!(code)).to be false
|
75
74
|
end
|
76
75
|
|
77
76
|
it 'does not invalidate the other recovery codes' do
|
@@ -81,7 +80,7 @@ shared_examples 'two_factor_backupable' do
|
|
81
80
|
@plaintext_codes.delete(code)
|
82
81
|
|
83
82
|
@plaintext_codes.each do |code|
|
84
|
-
subject.invalidate_otp_backup_code!(code).
|
83
|
+
expect(subject.invalidate_otp_backup_code!(code)).to be true
|
85
84
|
end
|
86
85
|
end
|
87
86
|
end
|
@@ -22,7 +22,7 @@ module Devise
|
|
22
22
|
def validate_otp(resource)
|
23
23
|
return true unless resource.otp_required_for_login
|
24
24
|
return if params[scope]['otp_attempt'].nil?
|
25
|
-
resource.
|
25
|
+
resource.validate_and_consume_otp!(params[scope]['otp_attempt'])
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
@@ -6,6 +6,13 @@ class TwoFactorAuthenticatableDouble
|
|
6
6
|
extend ::Devise::Models
|
7
7
|
|
8
8
|
devise :two_factor_authenticatable, :otp_secret_encryption_key => 'test-key'
|
9
|
+
|
10
|
+
attr_accessor :consumed_timestep
|
11
|
+
|
12
|
+
def save(validate)
|
13
|
+
# noop for testing
|
14
|
+
true
|
15
|
+
end
|
9
16
|
end
|
10
17
|
|
11
18
|
describe ::Devise::Models::TwoFactorAuthenticatable do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: devise-two-factor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shane Wilton
|
@@ -84,166 +84,160 @@ cert_chain:
|
|
84
84
|
5C31v4YyRBnNCp0pN66nxYX2avEiQ8riTBP5mlkPPOhsIoYQHHe2Uj75aVpu0LZ3
|
85
85
|
cdFzuO4GC1dV0Wv+dsDm+MyF7DT5E9pUPXpnMJuPvPrFpCb+wrFlszW9hGjXbQ==
|
86
86
|
-----END CERTIFICATE-----
|
87
|
-
date: 2015-
|
87
|
+
date: 2015-09-16 00:00:00.000000000 Z
|
88
88
|
dependencies:
|
89
89
|
- !ruby/object:Gem::Dependency
|
90
90
|
name: railties
|
91
91
|
requirement: !ruby/object:Gem::Requirement
|
92
92
|
requirements:
|
93
|
-
- -
|
93
|
+
- - '>='
|
94
94
|
- !ruby/object:Gem::Version
|
95
95
|
version: '0'
|
96
96
|
type: :runtime
|
97
97
|
prerelease: false
|
98
98
|
version_requirements: !ruby/object:Gem::Requirement
|
99
99
|
requirements:
|
100
|
-
- -
|
100
|
+
- - '>='
|
101
101
|
- !ruby/object:Gem::Version
|
102
102
|
version: '0'
|
103
103
|
- !ruby/object:Gem::Dependency
|
104
104
|
name: activesupport
|
105
105
|
requirement: !ruby/object:Gem::Requirement
|
106
106
|
requirements:
|
107
|
-
- -
|
107
|
+
- - '>='
|
108
108
|
- !ruby/object:Gem::Version
|
109
109
|
version: '0'
|
110
110
|
type: :runtime
|
111
111
|
prerelease: false
|
112
112
|
version_requirements: !ruby/object:Gem::Requirement
|
113
113
|
requirements:
|
114
|
-
- -
|
114
|
+
- - '>='
|
115
115
|
- !ruby/object:Gem::Version
|
116
116
|
version: '0'
|
117
117
|
- !ruby/object:Gem::Dependency
|
118
118
|
name: attr_encrypted
|
119
119
|
requirement: !ruby/object:Gem::Requirement
|
120
120
|
requirements:
|
121
|
-
- -
|
121
|
+
- - ~>
|
122
122
|
- !ruby/object:Gem::Version
|
123
123
|
version: 1.3.2
|
124
124
|
type: :runtime
|
125
125
|
prerelease: false
|
126
126
|
version_requirements: !ruby/object:Gem::Requirement
|
127
127
|
requirements:
|
128
|
-
- -
|
128
|
+
- - ~>
|
129
129
|
- !ruby/object:Gem::Version
|
130
130
|
version: 1.3.2
|
131
131
|
- !ruby/object:Gem::Dependency
|
132
132
|
name: devise
|
133
133
|
requirement: !ruby/object:Gem::Requirement
|
134
134
|
requirements:
|
135
|
-
- -
|
135
|
+
- - ~>
|
136
136
|
- !ruby/object:Gem::Version
|
137
137
|
version: 3.5.0
|
138
138
|
type: :runtime
|
139
139
|
prerelease: false
|
140
140
|
version_requirements: !ruby/object:Gem::Requirement
|
141
141
|
requirements:
|
142
|
-
- -
|
142
|
+
- - ~>
|
143
143
|
- !ruby/object:Gem::Version
|
144
144
|
version: 3.5.0
|
145
145
|
- !ruby/object:Gem::Dependency
|
146
146
|
name: rotp
|
147
147
|
requirement: !ruby/object:Gem::Requirement
|
148
148
|
requirements:
|
149
|
-
- -
|
149
|
+
- - ~>
|
150
150
|
- !ruby/object:Gem::Version
|
151
151
|
version: '2'
|
152
152
|
type: :runtime
|
153
153
|
prerelease: false
|
154
154
|
version_requirements: !ruby/object:Gem::Requirement
|
155
155
|
requirements:
|
156
|
-
- -
|
156
|
+
- - ~>
|
157
157
|
- !ruby/object:Gem::Version
|
158
158
|
version: '2'
|
159
159
|
- !ruby/object:Gem::Dependency
|
160
160
|
name: activemodel
|
161
161
|
requirement: !ruby/object:Gem::Requirement
|
162
162
|
requirements:
|
163
|
-
- -
|
163
|
+
- - '>='
|
164
164
|
- !ruby/object:Gem::Version
|
165
165
|
version: '0'
|
166
166
|
type: :development
|
167
167
|
prerelease: false
|
168
168
|
version_requirements: !ruby/object:Gem::Requirement
|
169
169
|
requirements:
|
170
|
-
- -
|
170
|
+
- - '>='
|
171
171
|
- !ruby/object:Gem::Version
|
172
172
|
version: '0'
|
173
173
|
- !ruby/object:Gem::Dependency
|
174
174
|
name: bundler
|
175
175
|
requirement: !ruby/object:Gem::Requirement
|
176
176
|
requirements:
|
177
|
-
- -
|
177
|
+
- - '>'
|
178
178
|
- !ruby/object:Gem::Version
|
179
179
|
version: '1.0'
|
180
180
|
type: :development
|
181
181
|
prerelease: false
|
182
182
|
version_requirements: !ruby/object:Gem::Requirement
|
183
183
|
requirements:
|
184
|
-
- -
|
184
|
+
- - '>'
|
185
185
|
- !ruby/object:Gem::Version
|
186
186
|
version: '1.0'
|
187
187
|
- !ruby/object:Gem::Dependency
|
188
188
|
name: rspec
|
189
189
|
requirement: !ruby/object:Gem::Requirement
|
190
190
|
requirements:
|
191
|
-
- -
|
192
|
-
- !ruby/object:Gem::Version
|
193
|
-
version: '2'
|
194
|
-
- - "<"
|
191
|
+
- - '>'
|
195
192
|
- !ruby/object:Gem::Version
|
196
193
|
version: '3'
|
197
194
|
type: :development
|
198
195
|
prerelease: false
|
199
196
|
version_requirements: !ruby/object:Gem::Requirement
|
200
197
|
requirements:
|
201
|
-
- -
|
202
|
-
- !ruby/object:Gem::Version
|
203
|
-
version: '2'
|
204
|
-
- - "<"
|
198
|
+
- - '>'
|
205
199
|
- !ruby/object:Gem::Version
|
206
200
|
version: '3'
|
207
201
|
- !ruby/object:Gem::Dependency
|
208
202
|
name: simplecov
|
209
203
|
requirement: !ruby/object:Gem::Requirement
|
210
204
|
requirements:
|
211
|
-
- -
|
205
|
+
- - '>='
|
212
206
|
- !ruby/object:Gem::Version
|
213
207
|
version: '0'
|
214
208
|
type: :development
|
215
209
|
prerelease: false
|
216
210
|
version_requirements: !ruby/object:Gem::Requirement
|
217
211
|
requirements:
|
218
|
-
- -
|
212
|
+
- - '>='
|
219
213
|
- !ruby/object:Gem::Version
|
220
214
|
version: '0'
|
221
215
|
- !ruby/object:Gem::Dependency
|
222
216
|
name: faker
|
223
217
|
requirement: !ruby/object:Gem::Requirement
|
224
218
|
requirements:
|
225
|
-
- -
|
219
|
+
- - '>='
|
226
220
|
- !ruby/object:Gem::Version
|
227
221
|
version: '0'
|
228
222
|
type: :development
|
229
223
|
prerelease: false
|
230
224
|
version_requirements: !ruby/object:Gem::Requirement
|
231
225
|
requirements:
|
232
|
-
- -
|
226
|
+
- - '>='
|
233
227
|
- !ruby/object:Gem::Version
|
234
228
|
version: '0'
|
235
229
|
- !ruby/object:Gem::Dependency
|
236
230
|
name: timecop
|
237
231
|
requirement: !ruby/object:Gem::Requirement
|
238
232
|
requirements:
|
239
|
-
- -
|
233
|
+
- - '>='
|
240
234
|
- !ruby/object:Gem::Version
|
241
235
|
version: '0'
|
242
236
|
type: :development
|
243
237
|
prerelease: false
|
244
238
|
version_requirements: !ruby/object:Gem::Requirement
|
245
239
|
requirements:
|
246
|
-
- -
|
240
|
+
- - '>='
|
247
241
|
- !ruby/object:Gem::Version
|
248
242
|
version: '0'
|
249
243
|
description: Barebones two-factor authentication with Devise
|
@@ -252,14 +246,15 @@ executables: []
|
|
252
246
|
extensions: []
|
253
247
|
extra_rdoc_files: []
|
254
248
|
files:
|
255
|
-
-
|
256
|
-
-
|
257
|
-
-
|
249
|
+
- .gitignore
|
250
|
+
- .rspec
|
251
|
+
- .travis.yml
|
258
252
|
- CONTRIBUTING.md
|
259
253
|
- Gemfile
|
260
254
|
- LICENSE
|
261
255
|
- README.md
|
262
256
|
- Rakefile
|
257
|
+
- UPGRADING.md
|
263
258
|
- certs/tinfoil-cacert.pem
|
264
259
|
- certs/tinfoilsecurity-gems-cert.pem
|
265
260
|
- devise-two-factor.gemspec
|
@@ -288,17 +283,17 @@ require_paths:
|
|
288
283
|
- lib
|
289
284
|
required_ruby_version: !ruby/object:Gem::Requirement
|
290
285
|
requirements:
|
291
|
-
- -
|
286
|
+
- - '>='
|
292
287
|
- !ruby/object:Gem::Version
|
293
288
|
version: '0'
|
294
289
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
295
290
|
requirements:
|
296
|
-
- -
|
291
|
+
- - '>='
|
297
292
|
- !ruby/object:Gem::Version
|
298
293
|
version: '0'
|
299
294
|
requirements: []
|
300
295
|
rubyforge_project: devise-two-factor
|
301
|
-
rubygems_version: 2.4.
|
296
|
+
rubygems_version: 2.4.7
|
302
297
|
signing_key:
|
303
298
|
specification_version: 4
|
304
299
|
summary: Barebones two-factor authentication with Devise
|
@@ -306,3 +301,4 @@ test_files:
|
|
306
301
|
- spec/devise/models/two_factor_authenticatable_spec.rb
|
307
302
|
- spec/devise/models/two_factor_backupable_spec.rb
|
308
303
|
- spec/spec_helper.rb
|
304
|
+
has_rdoc:
|
metadata.gz.sig
CHANGED
Binary file
|