two_factor_authentication 2.0.1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +10 -11
- data/Gemfile +1 -2
- data/README.md +158 -20
- data/app/controllers/devise/two_factor_authentication_controller.rb +1 -1
- data/app/views/devise/two_factor_authentication/show.html.erb +3 -3
- data/lib/two_factor_authentication/controllers/helpers.rb +1 -1
- data/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb +5 -1
- data/lib/two_factor_authentication/models/two_factor_authenticatable.rb +14 -2
- data/lib/two_factor_authentication/orm/active_record.rb +2 -0
- data/lib/two_factor_authentication/version.rb +1 -1
- data/lib/two_factor_authentication.rb +4 -2
- data/spec/controllers/two_factor_authentication_controller_spec.rb +10 -2
- data/spec/features/two_factor_authenticatable_spec.rb +12 -0
- data/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb +10 -5
- data/spec/rails_app/db/migrate/20140403184646_devise_create_users.rb +1 -1
- data/spec/rails_app/db/migrate/20140407172619_two_factor_authentication_add_to_users.rb +1 -1
- data/spec/rails_app/db/migrate/20140407215513_add_nickanme_to_users.rb +1 -1
- data/spec/rails_app/db/migrate/20151224171231_add_encrypted_columns_to_user.rb +1 -1
- data/spec/rails_app/db/migrate/20151224180310_populate_otp_column.rb +1 -1
- data/spec/rails_app/db/migrate/20151228230340_remove_otp_secret_key_from_user.rb +1 -1
- data/spec/rails_app/db/migrate/20160209032439_devise_create_admins.rb +1 -1
- data/spec/rails_app/db/schema.rb +27 -30
- data/spec/support/authenticated_model_helper.rb +1 -1
- data/spec/support/totp_helper.rb +1 -1
- data/two_factor_authentication.gemspec +2 -2
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d6d6636a281220ef8fd5aee19a4c4aaa4129797
|
4
|
+
data.tar.gz: 4954c89fa183dcb47c3104bfb14035ceaf6d20f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c559cf0c9c2c21519efdf25ce615ca5930dfc657564e4209b77298944ea3a2342414e7772b493db65c657c617641d0af4398a31bca0d3fbf81025e417fb688c2
|
7
|
+
data.tar.gz: 7e0d5abb909bb53f04d3d0cb887a4e869944eda656318c7e782562ecdf7bd635c8079d144506b1633e71c83368604b989dd9b4a21f2d8c21cb41d1f2849b3b58
|
data/.travis.yml
CHANGED
@@ -1,29 +1,28 @@
|
|
1
1
|
language: ruby
|
2
2
|
|
3
3
|
env:
|
4
|
-
- "RAILS_VERSION=4.0"
|
5
|
-
- "RAILS_VERSION=4.1"
|
6
4
|
- "RAILS_VERSION=4.2"
|
5
|
+
- "RAILS_VERSION=5.2"
|
7
6
|
- "RAILS_VERSION=master"
|
8
7
|
|
9
8
|
rvm:
|
10
|
-
- 2.
|
11
|
-
- 2.
|
12
|
-
- 2.3
|
9
|
+
- 2.3.8
|
10
|
+
- 2.4.5
|
11
|
+
- 2.5.3
|
13
12
|
|
14
13
|
matrix:
|
14
|
+
fast_finish: true
|
15
15
|
allow_failures:
|
16
16
|
- env: "RAILS_VERSION=master"
|
17
|
-
|
18
|
-
- rvm: 2.1
|
19
|
-
env: RAILS_VERSION=master
|
17
|
+
include:
|
20
18
|
- rvm: 2.2
|
21
|
-
env: RAILS_VERSION=
|
19
|
+
env: RAILS_VERSION=4.2
|
22
20
|
|
23
21
|
before_install:
|
24
|
-
- gem
|
22
|
+
- gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
|
23
|
+
- gem install bundler -v '< 2'
|
25
24
|
|
26
25
|
before_script:
|
27
|
-
- bundle exec rake app:db:
|
26
|
+
- bundle exec rake app:db:setup
|
28
27
|
|
29
28
|
script: bundle exec rake spec
|
data/Gemfile
CHANGED
@@ -9,7 +9,7 @@ rails = case rails_version
|
|
9
9
|
when "master"
|
10
10
|
{github: "rails/rails"}
|
11
11
|
when "default"
|
12
|
-
"~>
|
12
|
+
"~> 5.2"
|
13
13
|
else
|
14
14
|
"~> #{rails_version}"
|
15
15
|
end
|
@@ -27,5 +27,4 @@ end
|
|
27
27
|
group :test do
|
28
28
|
gem 'rack_session_access'
|
29
29
|
gem 'ammeter'
|
30
|
-
gem 'pry'
|
31
30
|
end
|
data/README.md
CHANGED
@@ -54,7 +54,7 @@ migration in `db/migrate/`, which will add the following columns to your table:
|
|
54
54
|
#### Manual initial setup
|
55
55
|
|
56
56
|
If you prefer to set up the model and migration manually, add the
|
57
|
-
`:
|
57
|
+
`:two_factor_authenticatable` option to your existing devise options, such as:
|
58
58
|
|
59
59
|
```ruby
|
60
60
|
devise :database_authenticatable, :registerable, :recoverable, :rememberable,
|
@@ -97,6 +97,7 @@ config.direct_otp_length = 6 # Direct OTP code length
|
|
97
97
|
config.remember_otp_session_for_seconds = 30.days # Time before browser has to perform 2fA again. Default is 0.
|
98
98
|
config.otp_secret_encryption_key = ENV['OTP_SECRET_ENCRYPTION_KEY']
|
99
99
|
config.second_factor_resource_id = 'id' # Field or method name used to set value for 2fA remember cookie
|
100
|
+
config.delete_cookie_on_logout = false # Delete cookie when user signs out, to force 2fA again on login
|
100
101
|
```
|
101
102
|
The `otp_secret_encryption_key` must be a random key that is not stored in the
|
102
103
|
DB, and is not checked in to your repo. It is recommended to store it in an
|
@@ -224,25 +225,25 @@ steps:
|
|
224
225
|
Open the generated file, and replace its contents with the following:
|
225
226
|
```ruby
|
226
227
|
class PopulateEncryptedOtpFields < ActiveRecord::Migration
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
228
|
+
def up
|
229
|
+
User.reset_column_information
|
230
|
+
|
231
|
+
User.find_each do |user|
|
232
|
+
user.otp_secret_key = user.read_attribute('otp_secret_key')
|
233
|
+
user.save!
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def down
|
238
|
+
User.reset_column_information
|
239
|
+
|
240
|
+
User.find_each do |user|
|
241
|
+
user.otp_secret_key = ROTP::Base32.random_base32
|
242
|
+
user.save!
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
```
|
246
247
|
|
247
248
|
5. Generate a migration to remove the `:otp_secret_key` column:
|
248
249
|
```
|
@@ -262,6 +263,143 @@ after them):
|
|
262
263
|
bundle exec rake db:rollback STEP=3
|
263
264
|
```
|
264
265
|
|
266
|
+
#### Critical Security Note! Add before_action to your user registration controllers
|
267
|
+
|
268
|
+
You should have a file registrations_controller.rb in your controllers folder
|
269
|
+
to overwrite/customize user registrations. It should include the lines below, for 2FA protection of user model updates, meaning that users can only access the users/edit page after confirming 2FA fully, not simply by logging in. Otherwise the entire 2FA system can be bypassed!
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
class RegistrationsController < Devise::RegistrationsController
|
273
|
+
before_action :confirm_two_factor_authenticated, except: [:new, :create, :cancel]
|
274
|
+
|
275
|
+
protected
|
276
|
+
|
277
|
+
def confirm_two_factor_authenticated
|
278
|
+
return if is_fully_authenticated?
|
279
|
+
|
280
|
+
flash[:error] = t('devise.errors.messages.user_not_authenticated')
|
281
|
+
redirect_to user_two_factor_authentication_url
|
282
|
+
end
|
283
|
+
end
|
284
|
+
```
|
285
|
+
|
286
|
+
#### Critical Security Note! Add 2FA validation to your custom user actions
|
287
|
+
|
288
|
+
Make sure you are passing the 2FA secret codes securely and checking for them upon critical user actions, such as API key updates, user email or pgp pubkey updates, or any other changess to private/secure account-related details. Validate the secret during the initial 2FA key/secret verification by the user also, of course.
|
289
|
+
|
290
|
+
For example, a simple account_controller.rb may look something like this:
|
291
|
+
|
292
|
+
```
|
293
|
+
require 'json'
|
294
|
+
|
295
|
+
class AccountController < ApplicationController
|
296
|
+
before_action :require_signed_in!
|
297
|
+
before_action :authenticate_user!
|
298
|
+
respond_to :html, :json
|
299
|
+
|
300
|
+
def account_API
|
301
|
+
resp = {}
|
302
|
+
begin
|
303
|
+
if(account_params["twoFAKey"] && account_params["twoFASecret"])
|
304
|
+
current_user.otp_secret_key = account_params["twoFAKey"]
|
305
|
+
if(current_user.authenticate_totp(account_params["twoFASecret"]))
|
306
|
+
# user has validated their temporary 2FA code, save it to their account, enable 2FA on this account
|
307
|
+
current_user.save!
|
308
|
+
resp['success'] = "passed 2FA validation!"
|
309
|
+
else
|
310
|
+
resp['error'] = "failed 2FA validation!"
|
311
|
+
end
|
312
|
+
elsif(param[:userAccountStuff] && param[:userAccountWidget])
|
313
|
+
#before updating important user account stuff and widgets,
|
314
|
+
#check to see that the 2FA secret has also been passed in, and verify it...
|
315
|
+
if(account_params["twoFASecret"] && current_user.totp_enabled? && current_user.authenticate_totp(account_params["twoFASecret"]))
|
316
|
+
# user has passed 2FA checks, do cool user account stuff here
|
317
|
+
...
|
318
|
+
else
|
319
|
+
# user failed 2FA check! No cool user stuff happens!
|
320
|
+
resp[error] = 'You failed 2FA validation!'
|
321
|
+
end
|
322
|
+
|
323
|
+
...
|
324
|
+
end
|
325
|
+
else
|
326
|
+
resp['error'] = 'unknown format error, not saved!'
|
327
|
+
end
|
328
|
+
rescue Exception => e
|
329
|
+
puts "WARNING: account api threw error : '#{e}' for user #{current_user.username}"
|
330
|
+
#print "error trace: #{e.backtrace}\n"
|
331
|
+
resp['error'] = "unanticipated server response"
|
332
|
+
end
|
333
|
+
render json: resp.to_json
|
334
|
+
end
|
335
|
+
|
336
|
+
def account_params
|
337
|
+
params.require(:twoFA).permit(:userAccountStuff, :userAcountWidget, :twoFAKey, :twoFASecret)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
```
|
341
|
+
|
342
|
+
|
265
343
|
### Example App
|
266
344
|
|
267
345
|
[TwoFactorAuthenticationExample](https://github.com/Houdini/TwoFactorAuthenticationExample)
|
346
|
+
|
347
|
+
|
348
|
+
### Example user actions
|
349
|
+
|
350
|
+
to use an ENV VAR for the 2FA encryption key:
|
351
|
+
|
352
|
+
config.otp_secret_encryption_key = ENV['OTP_SECRET_ENCRYPTION_KEY']
|
353
|
+
|
354
|
+
to set up TOTP for Google Authenticator for user:
|
355
|
+
|
356
|
+
```
|
357
|
+
current_user.otp_secret_key = current_user.generate_totp_secret
|
358
|
+
current_user.save!
|
359
|
+
```
|
360
|
+
|
361
|
+
( encrypted db fields are set upon user model save action,
|
362
|
+
rails c access relies on setting env var: OTP_SECRET_ENCRYPTION_KEY )
|
363
|
+
|
364
|
+
to check if user has input the correct code (from the QR display page)
|
365
|
+
before saving the user model:
|
366
|
+
|
367
|
+
```
|
368
|
+
current_user.authenticate_totp('123456')
|
369
|
+
```
|
370
|
+
|
371
|
+
additional note:
|
372
|
+
|
373
|
+
```
|
374
|
+
current_user.otp_secret_key
|
375
|
+
```
|
376
|
+
|
377
|
+
This returns the OTP secret key in plaintext for the user (if you have set the env var) in the console
|
378
|
+
the string used for generating the QR given to the user for their Google Auth is something like:
|
379
|
+
|
380
|
+
otpauth://totp/LABEL?secret=p6wwetjnkjnrcmpd (example secret used here)
|
381
|
+
|
382
|
+
where LABEL should be something like "example.com (Username)", which shows up in their GA app to remind them the code is for example.com
|
383
|
+
|
384
|
+
this returns true or false with an allowed_otp_drift_seconds 'grace period'
|
385
|
+
|
386
|
+
to set TOTP to DISABLED for a user account:
|
387
|
+
|
388
|
+
```
|
389
|
+
current_user.second_factor_attempts_count=nil
|
390
|
+
current_user.encrypted_otp_secret_key=nil
|
391
|
+
current_user.encrypted_otp_secret_key_iv=nil
|
392
|
+
current_user.encrypted_otp_secret_key_salt=nil
|
393
|
+
current_user.direct_otp=nil
|
394
|
+
current_user.direct_otp_sent_at=nil
|
395
|
+
current_user.totp_timestamp=nil
|
396
|
+
current_user.direct_otp=nil
|
397
|
+
current_user.otp_secret_key=nil
|
398
|
+
current_user.otp_confirmed=nil
|
399
|
+
current_user.save! (if in ruby code instead of console)
|
400
|
+
current_user.direct_otp? => false
|
401
|
+
current_user.totp_enabled? => false
|
402
|
+
```
|
403
|
+
|
404
|
+
|
405
|
+
|
@@ -47,7 +47,7 @@ class Devise::TwoFactorAuthenticationController < DeviseController
|
|
47
47
|
if expires_seconds && expires_seconds > 0
|
48
48
|
cookies.signed[TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME] = {
|
49
49
|
value: "#{resource.class}-#{resource.public_send(Devise.second_factor_resource_id)}",
|
50
|
-
expires: expires_seconds.from_now
|
50
|
+
expires: expires_seconds.seconds.from_now
|
51
51
|
}
|
52
52
|
end
|
53
53
|
end
|
@@ -12,8 +12,8 @@
|
|
12
12
|
<% end %>
|
13
13
|
|
14
14
|
<% if resource.direct_otp %>
|
15
|
-
<%= link_to "Resend Code",
|
15
|
+
<%= link_to "Resend Code", send("resend_code_#{resource_name}_two_factor_authentication_path"), action: :get %>
|
16
16
|
<% else %>
|
17
|
-
<%= link_to "Send me a code instead",
|
17
|
+
<%= link_to "Send me a code instead", send("resend_code_#{resource_name}_two_factor_authentication_path"), action: :get %>
|
18
18
|
<% end %>
|
19
|
-
<%= link_to "Sign out",
|
19
|
+
<%= link_to "Sign out", send("destroy_#{resource_name}_session_path"), :method => :delete %>
|
@@ -7,7 +7,11 @@ Warden::Manager.after_authentication do |user, auth, options|
|
|
7
7
|
|
8
8
|
if user.respond_to?(:need_two_factor_authentication?) && !bypass_by_cookie
|
9
9
|
if auth.session(options[:scope])[TwoFactorAuthentication::NEED_AUTHENTICATION] = user.need_two_factor_authentication?(auth.request)
|
10
|
-
user.send_new_otp
|
10
|
+
user.send_new_otp if user.send_new_otp_after_login?
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
14
|
+
|
15
|
+
Warden::Manager.before_logout do |user, auth, _options|
|
16
|
+
auth.cookies.delete TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME if Devise.delete_cookie_on_logout
|
17
|
+
end
|
@@ -16,7 +16,8 @@ module Devise
|
|
16
16
|
::Devise::Models.config(
|
17
17
|
self, :max_login_attempts, :allowed_otp_drift_seconds, :otp_length,
|
18
18
|
:remember_otp_session_for_seconds, :otp_secret_encryption_key,
|
19
|
-
:direct_otp_length, :direct_otp_valid_for, :totp_timestamp
|
19
|
+
:direct_otp_length, :direct_otp_valid_for, :totp_timestamp, :delete_cookie_on_logout
|
20
|
+
)
|
20
21
|
end
|
21
22
|
|
22
23
|
module InstanceMethodsOnActivation
|
@@ -38,7 +39,10 @@ module Devise
|
|
38
39
|
drift = options[:drift] || self.class.allowed_otp_drift_seconds
|
39
40
|
raise "authenticate_totp called with no otp_secret_key set" if totp_secret.nil?
|
40
41
|
totp = ROTP::TOTP.new(totp_secret, digits: digits)
|
41
|
-
new_timestamp = totp.
|
42
|
+
new_timestamp = totp.verify(
|
43
|
+
without_spaces(code),
|
44
|
+
drift_ahead: drift, drift_behind: drift, after: totp_timestamp
|
45
|
+
)
|
42
46
|
return false unless new_timestamp
|
43
47
|
self.totp_timestamp = new_timestamp
|
44
48
|
true
|
@@ -61,6 +65,10 @@ module Devise
|
|
61
65
|
send_two_factor_authentication_code(direct_otp)
|
62
66
|
end
|
63
67
|
|
68
|
+
def send_new_otp_after_login?
|
69
|
+
!totp_enabled?
|
70
|
+
end
|
71
|
+
|
64
72
|
def send_two_factor_authentication_code(code)
|
65
73
|
raise NotImplementedError.new("No default implementation - please define in your class.")
|
66
74
|
end
|
@@ -98,6 +106,10 @@ module Devise
|
|
98
106
|
|
99
107
|
private
|
100
108
|
|
109
|
+
def without_spaces(code)
|
110
|
+
code.gsub(/\s/, '')
|
111
|
+
end
|
112
|
+
|
101
113
|
def random_base10(digits)
|
102
114
|
SecureRandom.random_number(10**digits).to_s.rjust(digits, '0')
|
103
115
|
end
|
@@ -2,7 +2,6 @@ require 'two_factor_authentication/version'
|
|
2
2
|
require 'devise'
|
3
3
|
require 'active_support/concern'
|
4
4
|
require "active_model"
|
5
|
-
require "active_record"
|
6
5
|
require "active_support/core_ext/class/attribute_accessors"
|
7
6
|
require "cgi"
|
8
7
|
|
@@ -30,6 +29,9 @@ module Devise
|
|
30
29
|
|
31
30
|
mattr_accessor :second_factor_resource_id
|
32
31
|
@@second_factor_resource_id = 'id'
|
32
|
+
|
33
|
+
mattr_accessor :delete_cookie_on_logout
|
34
|
+
@@delete_cookie_on_logout = false
|
33
35
|
end
|
34
36
|
|
35
37
|
module TwoFactorAuthentication
|
@@ -44,7 +46,7 @@ end
|
|
44
46
|
|
45
47
|
Devise.add_module :two_factor_authenticatable, :model => 'two_factor_authentication/models/two_factor_authenticatable', :controller => :two_factor_authentication, :route => :two_factor_authentication
|
46
48
|
|
47
|
-
require 'two_factor_authentication/orm/active_record'
|
49
|
+
require 'two_factor_authentication/orm/active_record' if defined?(ActiveRecord::Base)
|
48
50
|
require 'two_factor_authentication/routes'
|
49
51
|
require 'two_factor_authentication/models/two_factor_authenticatable'
|
50
52
|
require 'two_factor_authentication/rails'
|
@@ -2,6 +2,14 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Devise::TwoFactorAuthenticationController, type: :controller do
|
4
4
|
describe 'is_fully_authenticated? helper' do
|
5
|
+
def post_code(code)
|
6
|
+
if Rails::VERSION::MAJOR >= 5
|
7
|
+
post :update, params: { code: code }
|
8
|
+
else
|
9
|
+
post :update, code: code
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
5
13
|
before do
|
6
14
|
sign_in
|
7
15
|
end
|
@@ -9,7 +17,7 @@ describe Devise::TwoFactorAuthenticationController, type: :controller do
|
|
9
17
|
context 'after user enters valid OTP code' do
|
10
18
|
it 'returns true' do
|
11
19
|
controller.current_user.send_new_otp
|
12
|
-
|
20
|
+
post_code controller.current_user.direct_otp
|
13
21
|
expect(subject.is_fully_authenticated?).to eq true
|
14
22
|
end
|
15
23
|
end
|
@@ -24,7 +32,7 @@ describe Devise::TwoFactorAuthenticationController, type: :controller do
|
|
24
32
|
|
25
33
|
context 'when user enters an invalid OTP' do
|
26
34
|
it 'returns false' do
|
27
|
-
|
35
|
+
post_code '12345'
|
28
36
|
|
29
37
|
expect(subject.is_fully_authenticated?).to eq false
|
30
38
|
end
|
@@ -174,6 +174,18 @@ feature "User of two factor authentication" do
|
|
174
174
|
visit dashboard_path
|
175
175
|
expect(page).to have_content("Enter the code that was sent to you")
|
176
176
|
end
|
177
|
+
|
178
|
+
scenario 'Delete cookie when user logs out if enabled' do
|
179
|
+
user.class.delete_cookie_on_logout = true
|
180
|
+
|
181
|
+
login_as user
|
182
|
+
logout
|
183
|
+
|
184
|
+
login_as user
|
185
|
+
|
186
|
+
visit dashboard_path
|
187
|
+
expect(page).to have_content("Enter the code that was sent to you")
|
188
|
+
end
|
177
189
|
end
|
178
190
|
|
179
191
|
it 'sets the warden session need_two_factor_authentication key to true' do
|
@@ -86,6 +86,11 @@ describe Devise::Models::TwoFactorAuthenticatable do
|
|
86
86
|
expect(do_invoke(code, instance)).to eq(true)
|
87
87
|
end
|
88
88
|
|
89
|
+
it 'authenticates a code entered with a space' do
|
90
|
+
code = @totp_helper.totp_code.insert(3, ' ')
|
91
|
+
expect(do_invoke(code, instance)).to eq(true)
|
92
|
+
end
|
93
|
+
|
89
94
|
it 'does not authenticate an old code' do
|
90
95
|
code = @totp_helper.totp_code(1.minutes.ago.to_i)
|
91
96
|
expect(do_invoke(code, instance)).to eq(false)
|
@@ -133,12 +138,12 @@ describe Devise::Models::TwoFactorAuthenticatable do
|
|
133
138
|
|
134
139
|
it "returns uri with user's email" do
|
135
140
|
expect(instance.provisioning_uri).
|
136
|
-
to match(%r{otpauth://totp/houdini@example.com\?secret=\w{
|
141
|
+
to match(%r{otpauth://totp/houdini@example.com\?secret=\w{32}})
|
137
142
|
end
|
138
143
|
|
139
144
|
it 'returns uri with issuer option' do
|
140
145
|
expect(instance.provisioning_uri('houdini')).
|
141
|
-
to match(%r{otpauth://totp/houdini\?secret=\w{
|
146
|
+
to match(%r{otpauth://totp/houdini\?secret=\w{32}$})
|
142
147
|
end
|
143
148
|
|
144
149
|
it 'returns uri with issuer option' do
|
@@ -150,7 +155,7 @@ describe Devise::Models::TwoFactorAuthenticatable do
|
|
150
155
|
expect(uri.host).to eq('totp')
|
151
156
|
expect(uri.path).to eq('/Magic:houdini')
|
152
157
|
expect(params['issuer'].shift).to eq('Magic')
|
153
|
-
expect(params['secret'].shift).to match(/\w{
|
158
|
+
expect(params['secret'].shift).to match(/\w{32}/)
|
154
159
|
end
|
155
160
|
end
|
156
161
|
end
|
@@ -163,10 +168,10 @@ describe Devise::Models::TwoFactorAuthenticatable do
|
|
163
168
|
shared_examples 'generate_totp_secret' do |klass|
|
164
169
|
let(:instance) { klass.new }
|
165
170
|
|
166
|
-
it 'returns a
|
171
|
+
it 'returns a 32 character string' do
|
167
172
|
secret = instance.generate_totp_secret
|
168
173
|
|
169
|
-
expect(secret).to match(/\w{
|
174
|
+
expect(secret).to match(/\w{32}/)
|
170
175
|
end
|
171
176
|
end
|
172
177
|
|
data/spec/rails_app/db/schema.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# encoding: UTF-8
|
2
1
|
# This file is auto-generated from the current state of the database. Instead
|
3
2
|
# of editing this file, please use the migrations feature of Active Record to
|
4
3
|
# incrementally modify your database, and then regenerate this schema definition.
|
@@ -11,48 +10,46 @@
|
|
11
10
|
#
|
12
11
|
# It's strongly recommended that you check this file into your version control system.
|
13
12
|
|
14
|
-
ActiveRecord::Schema.define(version:
|
13
|
+
ActiveRecord::Schema.define(version: 2016_02_09_032439) do
|
15
14
|
|
16
15
|
create_table "admins", force: :cascade do |t|
|
17
|
-
t.string
|
18
|
-
t.string
|
19
|
-
t.string
|
16
|
+
t.string "email", default: "", null: false
|
17
|
+
t.string "encrypted_password", default: "", null: false
|
18
|
+
t.string "reset_password_token"
|
20
19
|
t.datetime "reset_password_sent_at"
|
21
20
|
t.datetime "remember_created_at"
|
22
|
-
t.integer
|
21
|
+
t.integer "sign_in_count", default: 0, null: false
|
23
22
|
t.datetime "current_sign_in_at"
|
24
23
|
t.datetime "last_sign_in_at"
|
25
|
-
t.string
|
26
|
-
t.string
|
27
|
-
t.datetime "created_at",
|
28
|
-
t.datetime "updated_at",
|
24
|
+
t.string "current_sign_in_ip"
|
25
|
+
t.string "last_sign_in_ip"
|
26
|
+
t.datetime "created_at", null: false
|
27
|
+
t.datetime "updated_at", null: false
|
28
|
+
t.index ["email"], name: "index_admins_on_email", unique: true
|
29
|
+
t.index ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true
|
29
30
|
end
|
30
31
|
|
31
|
-
add_index "admins", ["email"], name: "index_admins_on_email", unique: true
|
32
|
-
add_index "admins", ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true
|
33
|
-
|
34
32
|
create_table "users", force: :cascade do |t|
|
35
|
-
t.string
|
36
|
-
t.string
|
37
|
-
t.string
|
33
|
+
t.string "email", default: "", null: false
|
34
|
+
t.string "encrypted_password", default: "", null: false
|
35
|
+
t.string "reset_password_token"
|
38
36
|
t.datetime "reset_password_sent_at"
|
39
37
|
t.datetime "remember_created_at"
|
40
|
-
t.integer
|
38
|
+
t.integer "sign_in_count", default: 0, null: false
|
41
39
|
t.datetime "current_sign_in_at"
|
42
40
|
t.datetime "last_sign_in_at"
|
43
|
-
t.string
|
44
|
-
t.string
|
45
|
-
t.datetime "created_at",
|
46
|
-
t.datetime "updated_at",
|
47
|
-
t.integer
|
48
|
-
t.string
|
49
|
-
t.string
|
50
|
-
t.string
|
51
|
-
t.string
|
41
|
+
t.string "current_sign_in_ip"
|
42
|
+
t.string "last_sign_in_ip"
|
43
|
+
t.datetime "created_at", null: false
|
44
|
+
t.datetime "updated_at", null: false
|
45
|
+
t.integer "second_factor_attempts_count", default: 0
|
46
|
+
t.string "nickname", limit: 64
|
47
|
+
t.string "encrypted_otp_secret_key"
|
48
|
+
t.string "encrypted_otp_secret_key_iv"
|
49
|
+
t.string "encrypted_otp_secret_key_salt"
|
50
|
+
t.index ["email"], name: "index_users_on_email", unique: true
|
51
|
+
t.index ["encrypted_otp_secret_key"], name: "index_users_on_encrypted_otp_secret_key", unique: true
|
52
|
+
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
52
53
|
end
|
53
54
|
|
54
|
-
add_index "users", ["email"], name: "index_users_on_email", unique: true
|
55
|
-
add_index "users", ["encrypted_otp_secret_key"], name: "index_users_on_encrypted_otp_secret_key", unique: true
|
56
|
-
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
57
|
-
|
58
55
|
end
|
@@ -29,7 +29,7 @@ module AuthenticatedModelHelper
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def create_table_for_nonencrypted_user
|
32
|
-
|
32
|
+
ActiveRecord::Migration.suppress_messages do
|
33
33
|
ActiveRecord::Schema.define(version: 1) do
|
34
34
|
create_table 'users', force: :cascade do |t|
|
35
35
|
t.string 'email', default: '', null: false
|
data/spec/support/totp_helper.rb
CHANGED
@@ -27,13 +27,13 @@ Gem::Specification.new do |s|
|
|
27
27
|
s.add_runtime_dependency 'rails', '>= 3.1.1'
|
28
28
|
s.add_runtime_dependency 'devise'
|
29
29
|
s.add_runtime_dependency 'randexp'
|
30
|
-
s.add_runtime_dependency 'rotp', '>=
|
30
|
+
s.add_runtime_dependency 'rotp', '>= 4.0.0'
|
31
31
|
s.add_runtime_dependency 'encryptor'
|
32
32
|
|
33
33
|
s.add_development_dependency 'bundler'
|
34
34
|
s.add_development_dependency 'rake'
|
35
35
|
s.add_development_dependency 'rspec-rails', '>= 3.0.1'
|
36
|
-
s.add_development_dependency 'capybara', '2.
|
36
|
+
s.add_development_dependency 'capybara', '~> 2.5'
|
37
37
|
s.add_development_dependency 'pry'
|
38
38
|
s.add_development_dependency 'timecop'
|
39
39
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: two_factor_authentication
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dmitrii Golub
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 4.0.0
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 4.0.0
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: encryptor
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -126,16 +126,16 @@ dependencies:
|
|
126
126
|
name: capybara
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
|
-
- -
|
129
|
+
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: 2.
|
131
|
+
version: '2.5'
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
|
-
- -
|
136
|
+
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version: 2.
|
138
|
+
version: '2.5'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: pry
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -286,7 +286,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
286
286
|
version: '0'
|
287
287
|
requirements: []
|
288
288
|
rubyforge_project: two_factor_authentication
|
289
|
-
rubygems_version: 2.6.
|
289
|
+
rubygems_version: 2.6.14
|
290
290
|
signing_key:
|
291
291
|
specification_version: 4
|
292
292
|
summary: Two factor authentication plugin for devise
|