devise-otp-rails5 0.2.4
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 +7 -0
- data/.gitignore +42 -0
- data/.travis.yml +12 -0
- data/Gemfile +25 -0
- data/LICENSE.txt +22 -0
- data/README.md +140 -0
- data/Rakefile +42 -0
- data/app/assets/javascripts/devise-otp.js +1 -0
- data/app/assets/javascripts/qrcode.js +609 -0
- data/app/controllers/devise_otp/credentials_controller.rb +106 -0
- data/app/controllers/devise_otp/tokens_controller.rb +111 -0
- data/app/views/devise_otp/credentials/refresh.html.erb +20 -0
- data/app/views/devise_otp/credentials/show.html.erb +23 -0
- data/app/views/devise_otp/tokens/_token_secret.html.erb +19 -0
- data/app/views/devise_otp/tokens/_trusted_devices.html.erb +10 -0
- data/app/views/devise_otp/tokens/recovery.html.erb +21 -0
- data/app/views/devise_otp/tokens/recovery_codes.text.erb +3 -0
- data/app/views/devise_otp/tokens/show.html.erb +19 -0
- data/config/locales/en.yml +66 -0
- data/devise-otp.gemspec +25 -0
- data/lib/devise-otp.rb +83 -0
- data/lib/devise-otp/version.rb +5 -0
- data/lib/devise_otp_authenticatable/controllers/helpers.rb +168 -0
- data/lib/devise_otp_authenticatable/controllers/url_helpers.rb +33 -0
- data/lib/devise_otp_authenticatable/engine.rb +23 -0
- data/lib/devise_otp_authenticatable/hooks.rb +13 -0
- data/lib/devise_otp_authenticatable/hooks/sessions.rb +59 -0
- data/lib/devise_otp_authenticatable/mapping.rb +19 -0
- data/lib/devise_otp_authenticatable/models/otp_authenticatable.rb +137 -0
- data/lib/devise_otp_authenticatable/routes.rb +32 -0
- data/lib/generators/active_record/devise_otp_generator.rb +13 -0
- data/lib/generators/active_record/templates/migration.rb +27 -0
- data/lib/generators/devise_otp/devise_otp_generator.rb +17 -0
- data/lib/generators/devise_otp/install_generator.rb +53 -0
- data/lib/generators/devise_otp/views_generator.rb +19 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +4 -0
- data/test/dummy/app/controllers/posts_controller.rb +83 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/helpers/posts_helper.rb +2 -0
- data/test/dummy/app/mailers/.gitkeep +0 -0
- data/test/dummy/app/models/post.rb +2 -0
- data/test/dummy/app/models/user.rb +20 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/app/views/posts/_form.html.erb +25 -0
- data/test/dummy/app/views/posts/edit.html.erb +6 -0
- data/test/dummy/app/views/posts/index.html.erb +25 -0
- data/test/dummy/app/views/posts/new.html.erb +5 -0
- data/test/dummy/app/views/posts/show.html.erb +15 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +67 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +30 -0
- data/test/dummy/config/environments/production.rb +69 -0
- data/test/dummy/config/environments/test.rb +36 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/devise.rb +253 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +8 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +6 -0
- data/test/dummy/db/migrate/20130125101430_create_users.rb +9 -0
- data/test/dummy/db/migrate/20130131092406_add_devise_to_users.rb +53 -0
- data/test/dummy/db/migrate/20130131142320_create_posts.rb +10 -0
- data/test/dummy/db/migrate/20130131160351_devise_otp_add_to_users.rb +28 -0
- data/test/dummy/lib/assets/.gitkeep +0 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/integration/persistence_test.rb +65 -0
- data/test/integration/refresh_test.rb +106 -0
- data/test/integration/sign_in_test.rb +87 -0
- data/test/integration/token_test.rb +34 -0
- data/test/integration_tests_helper.rb +66 -0
- data/test/model_tests_helper.rb +22 -0
- data/test/models/otp_authenticatable_test.rb +122 -0
- data/test/orm/active_record.rb +4 -0
- data/test/test_helper.rb +22 -0
- metadata +253 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
class DeviseOtpAddToUsers < ActiveRecord::Migration[5.0]
|
2
|
+
def self.up
|
3
|
+
change_table :users do |t|
|
4
|
+
t.string :otp_auth_secret
|
5
|
+
t.string :otp_recovery_secret
|
6
|
+
t.boolean :otp_enabled, :default => false, :null => false
|
7
|
+
t.boolean :otp_mandatory, :default => false, :null => false
|
8
|
+
t.datetime :otp_enabled_on
|
9
|
+
t.integer :otp_time_drift, :default => 0, :null => false
|
10
|
+
t.integer :otp_failed_attempts, :default => 0, :null => false
|
11
|
+
t.integer :otp_recovery_counter, :default => 0, :null => false
|
12
|
+
t.string :otp_persistence_seed
|
13
|
+
|
14
|
+
t.string :otp_session_challenge
|
15
|
+
t.datetime :otp_challenge_expires
|
16
|
+
end
|
17
|
+
|
18
|
+
add_index :users, :otp_session_challenge, :unique => true
|
19
|
+
add_index :users, :otp_challenge_expires
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.down
|
23
|
+
change_table :users do |t|
|
24
|
+
t.remove :otp_auth_secret, :otp_recovery_secret, :otp_enabled, :otp_mandatory, :otp_enabled_on, :otp_session_challenge,
|
25
|
+
:otp_challenge_expires, :otp_time_drift, :otp_failed_attempts, :otp_recovery_counter, :otp_persistence_seed
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
File without changes
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The page you were looking for doesn't exist (404)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/404.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>The page you were looking for doesn't exist.</h1>
|
23
|
+
<p>You may have mistyped the address or the page may have moved.</p>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The change you wanted was rejected (422)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/422.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>The change you wanted was rejected.</h1>
|
23
|
+
<p>Maybe you tried to change something you didn't have access to.</p>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/500.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>We're sorry, but something went wrong.</h1>
|
23
|
+
</div>
|
24
|
+
</body>
|
25
|
+
</html>
|
File without changes
|
@@ -0,0 +1,6 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
|
3
|
+
|
4
|
+
APP_PATH = File.expand_path('../../config/application', __FILE__)
|
5
|
+
require File.expand_path('../../config/boot', __FILE__)
|
6
|
+
require 'rails/commands'
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'integration_tests_helper'
|
3
|
+
|
4
|
+
class PersistenceTest < ActionDispatch::IntegrationTest
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@old_persistence = User.otp_trust_persistence
|
8
|
+
User.otp_trust_persistence = 3.seconds
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
User.otp_trust_persistence = @old_persistence
|
13
|
+
Capybara.reset_sessions!
|
14
|
+
end
|
15
|
+
|
16
|
+
test 'a user should be requested the otp challenge every log in' do
|
17
|
+
# log in 1fa
|
18
|
+
user = enable_otp_and_sign_in
|
19
|
+
otp_challenge_for user
|
20
|
+
|
21
|
+
visit user_otp_token_path
|
22
|
+
assert_equal user_otp_token_path, current_path
|
23
|
+
|
24
|
+
sign_out
|
25
|
+
sign_user_in
|
26
|
+
|
27
|
+
assert_equal user_otp_credential_path, current_path
|
28
|
+
end
|
29
|
+
|
30
|
+
test 'a user should be able to set their browser as trusted' do
|
31
|
+
# log in 1fa
|
32
|
+
user = enable_otp_and_sign_in
|
33
|
+
otp_challenge_for user
|
34
|
+
|
35
|
+
visit user_otp_token_path
|
36
|
+
assert_equal user_otp_token_path, current_path
|
37
|
+
|
38
|
+
click_link('Trust this browser')
|
39
|
+
assert_text 'Your browser is trusted.'
|
40
|
+
sign_out
|
41
|
+
|
42
|
+
sign_user_in
|
43
|
+
|
44
|
+
assert_equal root_path, current_path
|
45
|
+
end
|
46
|
+
|
47
|
+
test 'trusted status should expire' do
|
48
|
+
# log in 1fa
|
49
|
+
user = enable_otp_and_sign_in
|
50
|
+
otp_challenge_for user
|
51
|
+
|
52
|
+
visit user_otp_token_path
|
53
|
+
assert_equal user_otp_token_path, current_path
|
54
|
+
|
55
|
+
click_link('Trust this browser')
|
56
|
+
assert_text 'Your browser is trusted.'
|
57
|
+
sign_out
|
58
|
+
|
59
|
+
sleep User.otp_trust_persistence.to_i + 1
|
60
|
+
sign_user_in
|
61
|
+
|
62
|
+
assert_equal user_otp_credential_path, current_path
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'integration_tests_helper'
|
3
|
+
|
4
|
+
class RefreshTest < ActionDispatch::IntegrationTest
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@old_refresh = User.otp_credentials_refresh
|
8
|
+
User.otp_credentials_refresh = 1.second
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
User.otp_credentials_refresh = @old_refresh
|
13
|
+
Capybara.reset_sessions!
|
14
|
+
end
|
15
|
+
|
16
|
+
test 'a user that just signed in should be able to access their OTP settings without refreshing' do
|
17
|
+
sign_user_in
|
18
|
+
|
19
|
+
visit user_otp_token_path
|
20
|
+
assert_equal user_otp_token_path, current_path
|
21
|
+
end
|
22
|
+
|
23
|
+
test 'a user should be prompted for credentials when the credentials_refresh time is expired' do
|
24
|
+
|
25
|
+
sign_user_in
|
26
|
+
visit user_otp_token_path
|
27
|
+
assert_equal user_otp_token_path, current_path
|
28
|
+
|
29
|
+
sleep(2)
|
30
|
+
|
31
|
+
visit user_otp_token_path
|
32
|
+
assert_equal refresh_user_otp_credential_path, current_path
|
33
|
+
end
|
34
|
+
|
35
|
+
test 'a user should be able to access their OTP settings after refreshing' do
|
36
|
+
sign_user_in
|
37
|
+
visit user_otp_token_path
|
38
|
+
assert_equal user_otp_token_path, current_path
|
39
|
+
|
40
|
+
sleep(2)
|
41
|
+
|
42
|
+
visit user_otp_token_path
|
43
|
+
assert_equal refresh_user_otp_credential_path, current_path
|
44
|
+
|
45
|
+
fill_in 'user_refresh_password', :with => '12345678'
|
46
|
+
click_button 'Continue...'
|
47
|
+
assert_equal user_otp_token_path, current_path
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
test 'a user should NOT be able to access their OTP settings unless refreshing' do
|
52
|
+
sign_user_in
|
53
|
+
visit user_otp_token_path
|
54
|
+
assert_equal user_otp_token_path, current_path
|
55
|
+
|
56
|
+
sleep(2)
|
57
|
+
|
58
|
+
visit user_otp_token_path
|
59
|
+
assert_equal refresh_user_otp_credential_path, current_path
|
60
|
+
|
61
|
+
fill_in 'user_refresh_password', :with => '12345670'
|
62
|
+
click_button 'Continue...'
|
63
|
+
assert_equal refresh_user_otp_credential_path, current_path
|
64
|
+
end
|
65
|
+
|
66
|
+
test 'user should be asked their OTP challenge in order to refresh, if they have OTP' do
|
67
|
+
enable_otp_and_sign_in_with_otp
|
68
|
+
|
69
|
+
sleep(2)
|
70
|
+
visit user_otp_token_path
|
71
|
+
assert_equal refresh_user_otp_credential_path, current_path
|
72
|
+
|
73
|
+
fill_in 'user_refresh_password', :with => '12345678'
|
74
|
+
click_button 'Continue...'
|
75
|
+
|
76
|
+
assert_equal refresh_user_otp_credential_path, current_path
|
77
|
+
end
|
78
|
+
|
79
|
+
test 'user should be finally be able to access their settings, if they provide both a password and a valid OTP token' do
|
80
|
+
user = enable_otp_and_sign_in_with_otp
|
81
|
+
|
82
|
+
sleep(2)
|
83
|
+
visit user_otp_token_path
|
84
|
+
assert_equal refresh_user_otp_credential_path, current_path
|
85
|
+
|
86
|
+
fill_in 'user_refresh_password', :with => '12345678'
|
87
|
+
fill_in 'user_token', :with => ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
88
|
+
click_button 'Continue...'
|
89
|
+
|
90
|
+
assert_equal user_otp_token_path, current_path
|
91
|
+
end
|
92
|
+
|
93
|
+
test 'and rejected when the token is blank or null' do
|
94
|
+
user = enable_otp_and_sign_in_with_otp
|
95
|
+
|
96
|
+
sleep(2)
|
97
|
+
visit user_otp_token_path
|
98
|
+
assert_equal refresh_user_otp_credential_path, current_path
|
99
|
+
|
100
|
+
fill_in 'user_refresh_password', :with => '12345678'
|
101
|
+
fill_in 'user_token', :with => ''
|
102
|
+
click_button 'Continue...'
|
103
|
+
|
104
|
+
assert_equal refresh_user_otp_credential_path, current_path
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'integration_tests_helper'
|
3
|
+
|
4
|
+
class SignInTest < ActionDispatch::IntegrationTest
|
5
|
+
|
6
|
+
def teardown
|
7
|
+
Capybara.reset_sessions!
|
8
|
+
end
|
9
|
+
|
10
|
+
test 'a new user should be able to sign in without using their token' do
|
11
|
+
create_full_user
|
12
|
+
|
13
|
+
visit new_user_session_path
|
14
|
+
fill_in 'user_email', :with => 'user@email.invalid'
|
15
|
+
fill_in 'user_password', :with => '12345678'
|
16
|
+
page.has_content?('Log in') ? click_button('Log in') : click_button('Sign in')
|
17
|
+
|
18
|
+
assert_equal root_path, current_path
|
19
|
+
end
|
20
|
+
|
21
|
+
test 'a new user, just signed in, should be able to sign in and enable their OTP authentication' do
|
22
|
+
user = sign_user_in
|
23
|
+
|
24
|
+
visit user_otp_token_path
|
25
|
+
assert !page.has_content?('Your token secret')
|
26
|
+
|
27
|
+
check 'user_otp_enabled'
|
28
|
+
click_button 'Continue...'
|
29
|
+
|
30
|
+
assert_equal user_otp_token_path, current_path
|
31
|
+
|
32
|
+
assert page.has_content?('Your token secret')
|
33
|
+
assert !user.otp_auth_secret.nil?
|
34
|
+
assert !user.otp_persistence_seed.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
test 'a new user should be able to sign in enable OTP and be prompted for their token' do
|
38
|
+
enable_otp_and_sign_in
|
39
|
+
|
40
|
+
assert_equal user_otp_credential_path, current_path
|
41
|
+
end
|
42
|
+
|
43
|
+
test 'fail token authentication' do
|
44
|
+
enable_otp_and_sign_in
|
45
|
+
assert_equal user_otp_credential_path, current_path
|
46
|
+
|
47
|
+
fill_in 'user_token', :with => '123456'
|
48
|
+
click_button 'Submit Token'
|
49
|
+
|
50
|
+
assert_equal new_user_session_path, current_path
|
51
|
+
end
|
52
|
+
|
53
|
+
test 'fail blank token authentication' do
|
54
|
+
enable_otp_and_sign_in
|
55
|
+
assert_equal user_otp_credential_path, current_path
|
56
|
+
|
57
|
+
fill_in 'user_token', :with => ''
|
58
|
+
click_button 'Submit Token'
|
59
|
+
|
60
|
+
assert_equal user_otp_credential_path, current_path
|
61
|
+
end
|
62
|
+
|
63
|
+
test 'successful token authentication' do
|
64
|
+
user = enable_otp_and_sign_in
|
65
|
+
|
66
|
+
fill_in 'user_token', :with => ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
67
|
+
click_button 'Submit Token'
|
68
|
+
|
69
|
+
assert_equal root_path, current_path
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
test 'should fail if the the challenge times out' do
|
74
|
+
old_timeout = User.otp_authentication_timeout
|
75
|
+
User.otp_authentication_timeout = 1.second
|
76
|
+
|
77
|
+
user = enable_otp_and_sign_in
|
78
|
+
|
79
|
+
sleep(2)
|
80
|
+
|
81
|
+
fill_in 'user_token', :with => ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
82
|
+
click_button 'Submit Token'
|
83
|
+
|
84
|
+
User.otp_authentication_timeout = old_timeout
|
85
|
+
assert_equal new_user_session_path, current_path
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'integration_tests_helper'
|
3
|
+
|
4
|
+
class TokenTest < ActionDispatch::IntegrationTest
|
5
|
+
|
6
|
+
|
7
|
+
def teardown
|
8
|
+
Capybara.reset_sessions!
|
9
|
+
end
|
10
|
+
|
11
|
+
test 'disabling OTP after successfully enabling' do
|
12
|
+
|
13
|
+
# log in 1fa
|
14
|
+
user = enable_otp_and_sign_in
|
15
|
+
assert_equal user_otp_credential_path, current_path
|
16
|
+
|
17
|
+
# otp 2fa
|
18
|
+
fill_in 'user_token', :with => ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
19
|
+
click_button 'Submit Token'
|
20
|
+
assert_equal root_path, current_path
|
21
|
+
|
22
|
+
# disable OTP
|
23
|
+
disable_otp
|
24
|
+
|
25
|
+
# logout
|
26
|
+
sign_out
|
27
|
+
|
28
|
+
# log back in 1fa
|
29
|
+
sign_user_in(user)
|
30
|
+
|
31
|
+
assert_equal root_path, current_path
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class ActionDispatch::IntegrationTest
|
2
|
+
include Warden::Test::Helpers
|
3
|
+
|
4
|
+
def warden
|
5
|
+
request.env['warden']
|
6
|
+
end
|
7
|
+
|
8
|
+
def create_full_user
|
9
|
+
@user ||= begin
|
10
|
+
user = User.create!(
|
11
|
+
:email => 'user@email.invalid',
|
12
|
+
:password => '12345678',
|
13
|
+
:password_confirmation => '12345678'
|
14
|
+
)
|
15
|
+
user
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def enable_otp_and_sign_in_with_otp
|
20
|
+
enable_otp_and_sign_in.tap do |user|
|
21
|
+
fill_in 'user_token', :with => ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
22
|
+
click_button 'Submit Token'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def enable_otp_and_sign_in
|
28
|
+
user = create_full_user
|
29
|
+
sign_user_in(user)
|
30
|
+
visit user_otp_token_path
|
31
|
+
check 'user_otp_enabled'
|
32
|
+
click_button 'Continue...'
|
33
|
+
|
34
|
+
Capybara.reset_sessions!
|
35
|
+
|
36
|
+
sign_user_in(user)
|
37
|
+
user
|
38
|
+
end
|
39
|
+
|
40
|
+
def otp_challenge_for(user)
|
41
|
+
fill_in 'user_token', :with => ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
42
|
+
click_button 'Submit Token'
|
43
|
+
end
|
44
|
+
|
45
|
+
def disable_otp
|
46
|
+
visit user_otp_token_path
|
47
|
+
uncheck 'user_otp_enabled'
|
48
|
+
click_button 'Continue...'
|
49
|
+
end
|
50
|
+
|
51
|
+
def sign_out
|
52
|
+
logout :user
|
53
|
+
end
|
54
|
+
|
55
|
+
def sign_user_in(user = nil)
|
56
|
+
user ||= create_full_user
|
57
|
+
resource_name = user.class.name.underscore
|
58
|
+
visit send("new_#{resource_name}_session_path")
|
59
|
+
fill_in "#{resource_name}_email", :with => user.email
|
60
|
+
fill_in "#{resource_name}_password", :with => user.password
|
61
|
+
|
62
|
+
page.has_content?('Log in') ? click_button('Log in') : click_button('Sign in')
|
63
|
+
user
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|