devise-two-factor 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
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/README.md +68 -8
- data/devise-two-factor.gemspec +5 -5
- data/lib/devise_two_factor/models/two_factor_authenticatable.rb +1 -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 +4 -4
- data/lib/devise_two_factor/version.rb +1 -1
- metadata +15 -14
- 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: b71e31499a33b73bf9091ea49d750379c9a65445
|
4
|
+
data.tar.gz: 2d5600d12491430e2ba1053d31f0757fb7159141
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bbe11caae0da8ea8a6fcbcd8b36592066c9fa73e027c1b67f0294d431219592623d832614e1db08c25f5bb39805e0945ba831996850934b2e191761a4cc06a44
|
7
|
+
data.tar.gz: 32ac8864fb19ad19a0d13514e1afb3eabcb2c7ffcddb329c8a801cc35e1598e4f0161fff6599ac7b24b89803d83e8220bf684e05f82ea745a3e3eca1f389d8c9
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@ By [Tinfoil Security](http://tinfoilsecurity.com/)
|
|
3
3
|
|
4
4
|
[![Build Status](https://travis-ci.org/tinfoil/devise-two-factor.svg?branch=master)](https://travis-ci.org/tinfoil/devise-two-factor)
|
5
5
|
|
6
|
-
Devise-two-factor is a minimalist extension to Devise which offers support for two-factor authentication. It:
|
6
|
+
Devise-two-factor is a minimalist extension to Devise which offers support for two-factor authentication, through the [TOTP](https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm) scheme. It:
|
7
7
|
|
8
8
|
* Allows you to incorporate two-factor authentication into your existing models
|
9
9
|
* Is opinionated about security, so you don't have to be
|
@@ -11,17 +11,22 @@ Devise-two-factor is a minimalist extension to Devise which offers support for t
|
|
11
11
|
* Is extensible, and includes two-factor backup codes as an example of how plugins can be structured
|
12
12
|
|
13
13
|
## Example App
|
14
|
-
An example Rails 4 application is provided in demo
|
15
|
-
|
16
|
-
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.
|
14
|
+
An example Rails 4 application is provided in demo/. 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.
|
17
15
|
|
16
|
+
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
17
|
|
19
18
|
## Getting Started
|
20
19
|
Devise-two-factor doesn't require much to get started, but there are a few prerequisites before you can start using it in your application.
|
21
20
|
|
22
21
|
First, you'll need a Rails application setup with Devise. Visit the Devise [homepage](https://github.com/plataformatec/devise) for instructions.
|
23
22
|
|
24
|
-
Next, since devise-two-factor encrypts its secrets before storing them in the database, you'll need to generate an encryption key, and store it in an environment variable of your choice.
|
23
|
+
Next, since devise-two-factor encrypts its secrets before storing them in the database, you'll need to generate an encryption key, and store it in an environment variable of your choice. Set the encryption key in the model that uses devise:
|
24
|
+
|
25
|
+
```
|
26
|
+
devise :two_factor_authenticatable,
|
27
|
+
:otp_secret_encryption_key => ENV['YOUR_ENCRYPTION_KEY_HERE']
|
28
|
+
|
29
|
+
```
|
25
30
|
|
26
31
|
Finally, you can automate all of the required setup by simply running:
|
27
32
|
|
@@ -45,14 +50,14 @@ If you're running Rails 3, or do not have strong parameters enabled, the generat
|
|
45
50
|
**After running the generator, verify that :database_authenticatable is not being loaded by your model. The generator will try to remove it, but if you have a non-standard Devise setup, this step may fail. Loading both :database_authenticatable and :two_factor_authenticatable in a model will allow users to bypass two-factor authenticatable due to the way Warden handles cascading strategies.**
|
46
51
|
|
47
52
|
## Designing Your Workflow
|
48
|
-
Devise-two-factor only worries about the backend, leaving the details of the integration up to you. This means that you're responsible for building the UI that drives the gem. While there is an example Rails application included in the gem, it is
|
53
|
+
Devise-two-factor only worries about the backend, leaving the details of the integration up to you. This means that you're responsible for building the UI that drives the gem. While there is an example Rails application included in the gem, it is important to remember that this gem is intentionally very open-ended, and you should build a user experience which fits your individual application.
|
49
54
|
|
50
55
|
There are two key workflows you'll have to think about:
|
51
56
|
|
52
57
|
1. Logging in with two-factor authentication
|
53
58
|
2. Enabling two-factor authentication for a given user
|
54
59
|
|
55
|
-
We chose to keep things as simple as possible, and our
|
60
|
+
We chose to keep things as simple as possible, and our implementation can be found by registering at [Tinfoil Security](https://tinfoilsecurity.com/), and enabling two-factor authentication from the [security settings page](https://www.tinfoilsecurity.com/account/security).
|
56
61
|
|
57
62
|
|
58
63
|
### Logging In
|
@@ -64,6 +69,53 @@ Logging in with two-factor authentication works extremely similarly to regular d
|
|
64
69
|
|
65
70
|
These parameters can be submitted to the standard Devise login route, and the strategy will handle the authentication of the user for you.
|
66
71
|
|
72
|
+
### Disabling Automatic Login After Password Resets
|
73
|
+
If you use the Devise ```recoverable``` strategy, the default behavior after a password reset is to automatically authenticate the user and log them in. This is obviously a problem if a user has two-factor authentication enabled, as resetting the password would get around the 2FA requirement.
|
74
|
+
|
75
|
+
Because of this, you need to override the controller and disable the automatic login on your own. If you don't use the ```recoverable``` strategy and don't provide the option of password resets, you don't need to worry about this. An example is as follows:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
# app/controllers/passwords_controller.rb
|
79
|
+
class PasswordsController < Devise::PasswordsController
|
80
|
+
# Overrides to require a user to log in after resetting the password
|
81
|
+
|
82
|
+
def update
|
83
|
+
self.resource = resource_class.reset_password_by_token(resource_params)
|
84
|
+
yield resource if block_given?
|
85
|
+
|
86
|
+
if resource.errors.empty?
|
87
|
+
resource.unlock_access! if unlockable?(resource)
|
88
|
+
flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
|
89
|
+
set_flash_message(:notice, flash_message) if is_flashing_format?
|
90
|
+
|
91
|
+
# Do not automatically login if two-factor is enabled for this user.
|
92
|
+
# Remove the following three lines entirely if you want to disable
|
93
|
+
# automatic login for all users regardless, after a password reset.
|
94
|
+
unless resource.try(:otp_required_for_login?)
|
95
|
+
sign_in(resource_name, resource)
|
96
|
+
end
|
97
|
+
|
98
|
+
respond_with resource, location: after_resetting_password_path_for(resource)
|
99
|
+
else
|
100
|
+
respond_with resource
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
protected
|
105
|
+
|
106
|
+
def after_resetting_password_path_for(resource)
|
107
|
+
new_session_path(resource)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
And then tell Devise to use your new controller instead of the default:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
# app/config/routes.rb
|
116
|
+
devise_for :users, :controllers => {:passwords => "passwords"}
|
117
|
+
```
|
118
|
+
|
67
119
|
### Enabling Two-Factor Authentication
|
68
120
|
Enabling two-factor authentication for a user is easy. For example, if my user model were named User, I could do the following:
|
69
121
|
|
@@ -77,6 +129,14 @@ Before you can do this however, you need to decide how you're going to transmit
|
|
77
129
|
|
78
130
|
At Tinfoil Security, we opted to use the excellent [rqrcode-rails3](https://github.com/samvincent/rqrcode-rails3) gem to generate a QR-code representing the user's secret key, which can then be scanned by any mobile two-factor authentication client.
|
79
131
|
|
132
|
+
If you instead to decide to send the one-time password to the user directly, such as via SMS, you'll need a mechanism for generating the one-time password on the server:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
current_user.current_otp
|
136
|
+
```
|
137
|
+
|
138
|
+
The generated code will be valid for the duration specified by otp_allowed_drift.
|
139
|
+
|
80
140
|
However you decide to handle enrollment, there are a few important considerations to be made:
|
81
141
|
|
82
142
|
* Whether you'll force the use of two-factor authentication, and if so, how you'll migrate existing users to system, and what your onboarding experience will look like
|
@@ -146,7 +206,7 @@ Now just continue with the setup in the previous section, skipping the generator
|
|
146
206
|
Devise-two-factor includes shared-examples for both TwoFactorAuthenticatable and TwoFactorBackupable. Adding the following two lines to the specs for your two-factor enabled models will allow you to test your models for two-factor functionality:
|
147
207
|
|
148
208
|
```ruby
|
149
|
-
require 'devise_two_factor/spec_helpers
|
209
|
+
require 'devise_two_factor/spec_helpers'
|
150
210
|
|
151
211
|
it_behaves_like "two_factor_authenticatable"
|
152
212
|
it_behaves_like "two_factor_backupable"
|
data/devise-two-factor.gemspec
CHANGED
@@ -27,12 +27,12 @@ Gem::Specification.new do |s|
|
|
27
27
|
s.add_runtime_dependency 'rails' # For generators
|
28
28
|
s.add_runtime_dependency 'activesupport'
|
29
29
|
s.add_runtime_dependency 'activemodel'
|
30
|
-
s.add_runtime_dependency 'attr_encrypted'
|
31
|
-
s.add_runtime_dependency 'devise'
|
32
|
-
s.add_runtime_dependency 'rotp'
|
30
|
+
s.add_runtime_dependency 'attr_encrypted', '~> 1.3.2'
|
31
|
+
s.add_runtime_dependency 'devise', '~> 3.2.4'
|
32
|
+
s.add_runtime_dependency 'rotp', '~> 1.6.1'
|
33
33
|
|
34
|
-
s.add_development_dependency 'bundler',
|
35
|
-
s.add_development_dependency 'rspec',
|
34
|
+
s.add_development_dependency 'bundler', '> 1.0'
|
35
|
+
s.add_development_dependency 'rspec', '~> 2.8'
|
36
36
|
s.add_development_dependency 'simplecov'
|
37
37
|
s.add_development_dependency 'faker'
|
38
38
|
s.add_development_dependency 'timecop'
|
@@ -10,7 +10,7 @@ module Devise
|
|
10
10
|
|
11
11
|
included do
|
12
12
|
attr_encrypted :otp_secret, :key => self.otp_secret_encryption_key,
|
13
|
-
:mode => :per_attribute_iv_and_salt
|
13
|
+
:mode => :per_attribute_iv_and_salt unless self.attr_encrypted?(:otp_secret)
|
14
14
|
|
15
15
|
attr_accessor :otp_attempt
|
16
16
|
end
|
@@ -41,22 +41,22 @@ shared_examples 'two_factor_authenticatable' do
|
|
41
41
|
|
42
42
|
it 'validates a precisely correct OTP' do
|
43
43
|
otp = ROTP::TOTP.new(otp_secret).at(Time.now)
|
44
|
-
subject.valid_otp?(otp).should
|
44
|
+
subject.valid_otp?(otp).should be true
|
45
45
|
end
|
46
46
|
|
47
47
|
it 'validates an OTP within the allowed drift' do
|
48
48
|
otp = ROTP::TOTP.new(otp_secret).at(Time.now + subject.class.otp_allowed_drift, true)
|
49
|
-
subject.valid_otp?(otp).should
|
49
|
+
subject.valid_otp?(otp).should be true
|
50
50
|
end
|
51
51
|
|
52
52
|
it 'does not validate an OTP above the allowed drift' do
|
53
53
|
otp = ROTP::TOTP.new(otp_secret).at(Time.now + subject.class.otp_allowed_drift * 2, true)
|
54
|
-
subject.valid_otp?(otp).should
|
54
|
+
subject.valid_otp?(otp).should be false
|
55
55
|
end
|
56
56
|
|
57
57
|
it 'does not validate an OTP below the allowed drift' do
|
58
58
|
otp = ROTP::TOTP.new(otp_secret).at(Time.now - subject.class.otp_allowed_drift * 2, true)
|
59
|
-
subject.valid_otp?(otp).should
|
59
|
+
subject.valid_otp?(otp).should be false
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
@@ -56,14 +56,14 @@ shared_examples 'two_factor_backupable' do
|
|
56
56
|
|
57
57
|
context 'given an invalid recovery code' do
|
58
58
|
it 'returns false' do
|
59
|
-
subject.invalidate_otp_backup_code!('password').should
|
59
|
+
subject.invalidate_otp_backup_code!('password').should be false
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
63
|
context 'given a valid recovery code' do
|
64
64
|
it 'returns true' do
|
65
65
|
@plaintext_codes.each do |code|
|
66
|
-
subject.invalidate_otp_backup_code!(code).should
|
66
|
+
subject.invalidate_otp_backup_code!(code).should be true
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
@@ -71,7 +71,7 @@ shared_examples 'two_factor_backupable' do
|
|
71
71
|
code = @plaintext_codes.sample
|
72
72
|
|
73
73
|
subject.invalidate_otp_backup_code!(code)
|
74
|
-
subject.invalidate_otp_backup_code!(code).should
|
74
|
+
subject.invalidate_otp_backup_code!(code).should be false
|
75
75
|
end
|
76
76
|
|
77
77
|
it 'does not invalidate the other recovery codes' do
|
@@ -81,7 +81,7 @@ shared_examples 'two_factor_backupable' do
|
|
81
81
|
@plaintext_codes.delete(code)
|
82
82
|
|
83
83
|
@plaintext_codes.each do |code|
|
84
|
-
subject.invalidate_otp_backup_code!(code).should
|
84
|
+
subject.invalidate_otp_backup_code!(code).should be true
|
85
85
|
end
|
86
86
|
end
|
87
87
|
end
|
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: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shane Wilton
|
@@ -84,7 +84,7 @@ cert_chain:
|
|
84
84
|
5C31v4YyRBnNCp0pN66nxYX2avEiQ8riTBP5mlkPPOhsIoYQHHe2Uj75aVpu0LZ3
|
85
85
|
cdFzuO4GC1dV0Wv+dsDm+MyF7DT5E9pUPXpnMJuPvPrFpCb+wrFlszW9hGjXbQ==
|
86
86
|
-----END CERTIFICATE-----
|
87
|
-
date:
|
87
|
+
date: 2015-04-07 00:00:00.000000000 Z
|
88
88
|
dependencies:
|
89
89
|
- !ruby/object:Gem::Dependency
|
90
90
|
name: rails
|
@@ -132,44 +132,44 @@ dependencies:
|
|
132
132
|
name: attr_encrypted
|
133
133
|
requirement: !ruby/object:Gem::Requirement
|
134
134
|
requirements:
|
135
|
-
- -
|
135
|
+
- - ~>
|
136
136
|
- !ruby/object:Gem::Version
|
137
|
-
version:
|
137
|
+
version: 1.3.2
|
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
|
-
version:
|
144
|
+
version: 1.3.2
|
145
145
|
- !ruby/object:Gem::Dependency
|
146
146
|
name: devise
|
147
147
|
requirement: !ruby/object:Gem::Requirement
|
148
148
|
requirements:
|
149
|
-
- -
|
149
|
+
- - ~>
|
150
150
|
- !ruby/object:Gem::Version
|
151
|
-
version:
|
151
|
+
version: 3.2.4
|
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
|
-
version:
|
158
|
+
version: 3.2.4
|
159
159
|
- !ruby/object:Gem::Dependency
|
160
160
|
name: rotp
|
161
161
|
requirement: !ruby/object:Gem::Requirement
|
162
162
|
requirements:
|
163
|
-
- -
|
163
|
+
- - ~>
|
164
164
|
- !ruby/object:Gem::Version
|
165
|
-
version:
|
165
|
+
version: 1.6.1
|
166
166
|
type: :runtime
|
167
167
|
prerelease: false
|
168
168
|
version_requirements: !ruby/object:Gem::Requirement
|
169
169
|
requirements:
|
170
|
-
- -
|
170
|
+
- - ~>
|
171
171
|
- !ruby/object:Gem::Version
|
172
|
-
version:
|
172
|
+
version: 1.6.1
|
173
173
|
- !ruby/object:Gem::Dependency
|
174
174
|
name: bundler
|
175
175
|
requirement: !ruby/object:Gem::Requirement
|
@@ -300,3 +300,4 @@ test_files:
|
|
300
300
|
- spec/devise/models/two_factor_authenticatable_spec.rb
|
301
301
|
- spec/devise/models/two_factor_backupable_spec.rb
|
302
302
|
- spec/spec_helper.rb
|
303
|
+
has_rdoc:
|
metadata.gz.sig
CHANGED
Binary file
|