devise-otp 0.6.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +0 -2
- data/.gitignore +3 -1
- data/CHANGELOG.md +51 -8
- data/README.md +8 -2
- data/app/controllers/devise_otp/devise/otp_credentials_controller.rb +46 -27
- data/app/controllers/devise_otp/devise/otp_tokens_controller.rb +24 -6
- data/app/views/devise/otp_credentials/show.html.erb +6 -6
- data/app/views/devise/otp_tokens/_token_secret.html.erb +3 -4
- data/app/views/devise/otp_tokens/edit.html.erb +26 -0
- data/app/views/devise/otp_tokens/show.html.erb +7 -14
- data/config/locales/en.yml +23 -14
- data/devise-otp.gemspec +1 -2
- data/lib/devise/strategies/database_authenticatable.rb +64 -0
- data/lib/devise-otp/version.rb +1 -1
- data/lib/devise-otp.rb +31 -11
- data/lib/devise_otp_authenticatable/controllers/helpers.rb +9 -10
- data/lib/devise_otp_authenticatable/controllers/public_helpers.rb +39 -0
- data/lib/devise_otp_authenticatable/controllers/url_helpers.rb +10 -0
- data/lib/devise_otp_authenticatable/engine.rb +2 -5
- data/lib/devise_otp_authenticatable/hooks/refreshable.rb +5 -0
- data/lib/devise_otp_authenticatable/models/otp_authenticatable.rb +22 -20
- data/lib/devise_otp_authenticatable/routes.rb +3 -1
- data/test/dummy/app/controllers/admin_posts_controller.rb +85 -0
- data/test/dummy/app/controllers/application_controller.rb +0 -1
- data/test/dummy/app/controllers/base_controller.rb +6 -0
- data/test/dummy/app/models/admin.rb +25 -0
- data/test/dummy/app/views/admin_posts/_form.html.erb +25 -0
- data/test/dummy/app/views/admin_posts/edit.html.erb +6 -0
- data/test/dummy/app/views/admin_posts/index.html.erb +25 -0
- data/test/dummy/app/views/admin_posts/new.html.erb +5 -0
- data/test/dummy/app/views/admin_posts/show.html.erb +15 -0
- data/test/dummy/app/views/base/home.html.erb +1 -0
- data/test/dummy/config/application.rb +0 -2
- data/test/dummy/config/routes.rb +4 -1
- data/test/dummy/db/migrate/20240604000001_create_admins.rb +9 -0
- data/test/dummy/db/migrate/20240604000002_add_devise_to_admins.rb +52 -0
- data/test/dummy/db/migrate/20240604000003_devise_otp_add_to_admins.rb +28 -0
- data/test/integration/disable_token_test.rb +53 -0
- data/test/integration/enable_otp_form_test.rb +57 -0
- data/test/integration/persistence_test.rb +3 -6
- data/test/integration/refresh_test.rb +32 -0
- data/test/integration/reset_token_test.rb +45 -0
- data/test/integration/sign_in_test.rb +10 -14
- data/test/integration/trackable_test.rb +50 -0
- data/test/integration_tests_helper.rb +24 -6
- data/test/models/otp_authenticatable_test.rb +62 -27
- data/test/test_helper.rb +1 -71
- metadata +26 -23
- data/lib/devise_otp_authenticatable/hooks/sessions.rb +0 -58
- data/lib/devise_otp_authenticatable/hooks.rb +0 -11
- data/test/integration/token_test.rb +0 -30
@@ -51,13 +51,10 @@ class PersistenceTest < ActionDispatch::IntegrationTest
|
|
51
51
|
visit user_otp_token_path
|
52
52
|
assert_equal user_otp_token_path, current_path
|
53
53
|
|
54
|
-
|
54
|
+
click_link("Download recovery codes")
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
assert_equal 1, DownloadHelper.downloads.size
|
56
|
+
assert current_path.match?(/recovery\.text/)
|
57
|
+
assert page.body.match?(user.next_otp_recovery_tokens.values.join("\n"))
|
61
58
|
end
|
62
59
|
|
63
60
|
test "trusted status should expire" do
|
@@ -5,10 +5,12 @@ class RefreshTest < ActionDispatch::IntegrationTest
|
|
5
5
|
def setup
|
6
6
|
@old_refresh = User.otp_credentials_refresh
|
7
7
|
User.otp_credentials_refresh = 1.second
|
8
|
+
Admin.otp_credentials_refresh = 1.second
|
8
9
|
end
|
9
10
|
|
10
11
|
def teardown
|
11
12
|
User.otp_credentials_refresh = @old_refresh
|
13
|
+
Admin.otp_credentials_refresh = @old_refresh
|
12
14
|
Capybara.reset_sessions!
|
13
15
|
end
|
14
16
|
|
@@ -72,4 +74,34 @@ class RefreshTest < ActionDispatch::IntegrationTest
|
|
72
74
|
|
73
75
|
assert_equal user_otp_token_path, current_path
|
74
76
|
end
|
77
|
+
|
78
|
+
test "works for non-default warden scopes" do
|
79
|
+
admin = create_full_admin
|
80
|
+
|
81
|
+
admin.populate_otp_secrets!
|
82
|
+
admin.enable_otp!
|
83
|
+
|
84
|
+
visit new_admin_session_path
|
85
|
+
fill_in "admin_email", with: admin.email
|
86
|
+
fill_in "admin_password", with: admin.password
|
87
|
+
|
88
|
+
page.has_content?("Log in") ? click_button("Log in") : click_button("Sign in")
|
89
|
+
|
90
|
+
assert_equal admin_otp_credential_path, current_path
|
91
|
+
|
92
|
+
fill_in "token", with: ROTP::TOTP.new(admin.otp_auth_secret).at(Time.now)
|
93
|
+
click_button "Submit Token"
|
94
|
+
assert_equal "/", current_path
|
95
|
+
|
96
|
+
sleep(2)
|
97
|
+
|
98
|
+
visit admin_otp_token_path
|
99
|
+
assert_equal refresh_admin_otp_credential_path, current_path
|
100
|
+
|
101
|
+
fill_in "admin_refresh_password", with: "12345678"
|
102
|
+
click_button "Continue..."
|
103
|
+
|
104
|
+
assert_equal admin_otp_token_path, current_path
|
105
|
+
end
|
106
|
+
|
75
107
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "integration_tests_helper"
|
3
|
+
|
4
|
+
class ResetTokenTest < ActionDispatch::IntegrationTest
|
5
|
+
|
6
|
+
def setup
|
7
|
+
# log in 1fa
|
8
|
+
@user = enable_otp_and_sign_in
|
9
|
+
assert_equal user_otp_credential_path, current_path
|
10
|
+
|
11
|
+
# otp 2fa
|
12
|
+
fill_in "token", with: ROTP::TOTP.new(@user.otp_auth_secret).at(Time.now)
|
13
|
+
click_button "Submit Token"
|
14
|
+
assert_equal root_path, current_path
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def teardown
|
19
|
+
Capybara.reset_sessions!
|
20
|
+
end
|
21
|
+
|
22
|
+
test "redirects to otp_tokens#edit page" do
|
23
|
+
reset_otp
|
24
|
+
|
25
|
+
assert_equal "/users/otp/token/edit", current_path
|
26
|
+
end
|
27
|
+
|
28
|
+
test "generates new token secrets" do
|
29
|
+
# get auth secrets
|
30
|
+
auth_secret = @user.otp_auth_secret
|
31
|
+
recovery_secret = @user.otp_recovery_secret
|
32
|
+
|
33
|
+
# reset otp
|
34
|
+
reset_otp
|
35
|
+
|
36
|
+
# compare auth secrets
|
37
|
+
@user.reload
|
38
|
+
assert_not_nil @user.otp_auth_secret
|
39
|
+
assert_not_equal @user.otp_auth_secret, auth_secret
|
40
|
+
|
41
|
+
assert_not_nil @user.otp_recovery_secret
|
42
|
+
assert_not_equal @user.otp_recovery_secret, recovery_secret
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -17,20 +17,16 @@ class SignInTest < ActionDispatch::IntegrationTest
|
|
17
17
|
assert_equal posts_path, current_path
|
18
18
|
end
|
19
19
|
|
20
|
-
test "a new user, just signed in, should be able to
|
20
|
+
test "a new user, just signed in, should be able to see and click the 'Enable Two-Factor Authentication' link" do
|
21
21
|
user = sign_user_in
|
22
22
|
|
23
23
|
visit user_otp_token_path
|
24
|
-
assert
|
24
|
+
assert page.has_content?("Disabled")
|
25
25
|
|
26
|
-
|
27
|
-
click_button "Continue..."
|
26
|
+
click_link "Enable Two-Factor Authentication"
|
28
27
|
|
29
|
-
|
30
|
-
|
31
|
-
assert page.has_content?("Your token secret")
|
32
|
-
assert !user.otp_auth_secret.nil?
|
33
|
-
assert !user.otp_persistence_seed.nil?
|
28
|
+
assert page.has_content?("Enable Two-Factor Authentication")
|
29
|
+
assert_equal edit_user_otp_token_path, current_path
|
34
30
|
end
|
35
31
|
|
36
32
|
test "a new user should be able to sign in enable OTP and be prompted for their token" do
|
@@ -43,17 +39,17 @@ class SignInTest < ActionDispatch::IntegrationTest
|
|
43
39
|
enable_otp_and_sign_in
|
44
40
|
assert_equal user_otp_credential_path, current_path
|
45
41
|
|
46
|
-
fill_in "
|
42
|
+
fill_in "token", with: "123456"
|
47
43
|
click_button "Submit Token"
|
48
44
|
|
49
|
-
assert_equal
|
45
|
+
assert_equal user_otp_credential_path, current_path
|
50
46
|
end
|
51
47
|
|
52
48
|
test "fail blank token authentication" do
|
53
49
|
enable_otp_and_sign_in
|
54
50
|
assert_equal user_otp_credential_path, current_path
|
55
51
|
|
56
|
-
fill_in "
|
52
|
+
fill_in "token", with: ""
|
57
53
|
click_button "Submit Token"
|
58
54
|
|
59
55
|
assert_equal user_otp_credential_path, current_path
|
@@ -62,7 +58,7 @@ class SignInTest < ActionDispatch::IntegrationTest
|
|
62
58
|
test "successful token authentication" do
|
63
59
|
user = enable_otp_and_sign_in
|
64
60
|
|
65
|
-
fill_in "
|
61
|
+
fill_in "token", with: ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
66
62
|
click_button "Submit Token"
|
67
63
|
|
68
64
|
assert_equal root_path, current_path
|
@@ -76,7 +72,7 @@ class SignInTest < ActionDispatch::IntegrationTest
|
|
76
72
|
|
77
73
|
sleep(2)
|
78
74
|
|
79
|
-
fill_in "
|
75
|
+
fill_in "token", with: ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
80
76
|
click_button "Submit Token"
|
81
77
|
|
82
78
|
User.otp_authentication_timeout = old_timeout
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "integration_tests_helper"
|
3
|
+
|
4
|
+
class TrackableTest < ActionDispatch::IntegrationTest
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@user = sign_user_in
|
8
|
+
|
9
|
+
@user.reload
|
10
|
+
|
11
|
+
@sign_in_count = @user.sign_in_count
|
12
|
+
@current_sign_in_at = @user.current_sign_in_at
|
13
|
+
|
14
|
+
sign_out
|
15
|
+
end
|
16
|
+
|
17
|
+
def teardown
|
18
|
+
Capybara.reset_sessions!
|
19
|
+
end
|
20
|
+
|
21
|
+
test "if otp is disabled, it should update devise trackable fields as usual when the user signs in" do
|
22
|
+
sign_user_in(@user)
|
23
|
+
|
24
|
+
@user.reload
|
25
|
+
|
26
|
+
assert_not_equal @sign_in_count, @user.sign_in_count
|
27
|
+
assert_not_equal @current_sign_in_at, @user.current_sign_in_at
|
28
|
+
end
|
29
|
+
|
30
|
+
test "if otp is enabled, it should not update devise trackable fields until user enters their user token to complete their sign in" do
|
31
|
+
@user.populate_otp_secrets!
|
32
|
+
@user.enable_otp!
|
33
|
+
|
34
|
+
sign_user_in(@user)
|
35
|
+
|
36
|
+
@user.reload
|
37
|
+
|
38
|
+
assert_equal @sign_in_count, @user.sign_in_count
|
39
|
+
assert_equal @current_sign_in_at, @user.current_sign_in_at
|
40
|
+
|
41
|
+
fill_in "token", with: ROTP::TOTP.new(@user.otp_auth_secret).at(Time.now)
|
42
|
+
click_button "Submit Token"
|
43
|
+
|
44
|
+
@user.reload
|
45
|
+
|
46
|
+
assert_not_equal @sign_in_count, @user.sign_in_count
|
47
|
+
assert_not_equal @current_sign_in_at, @user.current_sign_in_at
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -16,18 +16,31 @@ class ActionDispatch::IntegrationTest
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
+
def create_full_admin
|
20
|
+
@admin ||= begin
|
21
|
+
admin = Admin.create!(
|
22
|
+
email: "admin@email.invalid",
|
23
|
+
password: "12345678",
|
24
|
+
password_confirmation: "12345678"
|
25
|
+
)
|
26
|
+
admin
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
19
30
|
def enable_otp_and_sign_in_with_otp
|
20
31
|
enable_otp_and_sign_in.tap do |user|
|
21
|
-
fill_in "
|
32
|
+
fill_in "token", with: ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
22
33
|
click_button "Submit Token"
|
23
34
|
end
|
24
35
|
end
|
25
36
|
|
26
37
|
def enable_otp_and_sign_in
|
27
38
|
user = create_full_user
|
39
|
+
user.populate_otp_secrets!
|
40
|
+
|
28
41
|
sign_user_in(user)
|
29
|
-
visit
|
30
|
-
|
42
|
+
visit edit_user_otp_token_path
|
43
|
+
fill_in "confirmation_code", with: ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
31
44
|
click_button "Continue..."
|
32
45
|
|
33
46
|
Capybara.reset_sessions!
|
@@ -37,14 +50,18 @@ class ActionDispatch::IntegrationTest
|
|
37
50
|
end
|
38
51
|
|
39
52
|
def otp_challenge_for(user)
|
40
|
-
fill_in "
|
53
|
+
fill_in "token", with: ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
41
54
|
click_button "Submit Token"
|
42
55
|
end
|
43
56
|
|
44
57
|
def disable_otp
|
45
58
|
visit user_otp_token_path
|
46
|
-
|
47
|
-
|
59
|
+
click_button "Disable Two-Factor Authentication"
|
60
|
+
end
|
61
|
+
|
62
|
+
def reset_otp
|
63
|
+
visit user_otp_token_path
|
64
|
+
click_button "Reset Token Secret"
|
48
65
|
end
|
49
66
|
|
50
67
|
def sign_out
|
@@ -61,4 +78,5 @@ class ActionDispatch::IntegrationTest
|
|
61
78
|
page.has_content?("Log in") ? click_button("Log in") : click_button("Sign in")
|
62
79
|
user
|
63
80
|
end
|
81
|
+
|
64
82
|
end
|
@@ -6,42 +6,70 @@ class OtpAuthenticatableTest < ActiveSupport::TestCase
|
|
6
6
|
new_user
|
7
7
|
end
|
8
8
|
|
9
|
-
test "new users have a
|
10
|
-
|
9
|
+
test "new users do not have a secret set" do
|
10
|
+
user = User.first
|
11
|
+
|
12
|
+
[:otp_auth_secret, :otp_recovery_secret, :otp_persistence_seed].each do |field|
|
13
|
+
assert_nil user.send(field)
|
14
|
+
end
|
11
15
|
end
|
12
16
|
|
13
17
|
test "new users have OTP disabled by default" do
|
14
18
|
assert !User.first.otp_enabled
|
15
19
|
end
|
16
20
|
|
17
|
-
test "
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
test "populating otp secrets should populate all required fields" do
|
22
|
+
user = User.first
|
23
|
+
user.populate_otp_secrets!
|
24
|
+
|
25
|
+
[:otp_auth_secret, :otp_recovery_secret, :otp_persistence_seed].each do |field|
|
26
|
+
assert_not_nil user.send(field)
|
27
|
+
end
|
21
28
|
end
|
22
29
|
|
23
|
-
test "
|
24
|
-
|
25
|
-
|
30
|
+
test "time_based_otp and recover_otp fields should be an instance of TOTP/ROTP objects" do
|
31
|
+
user = User.first
|
32
|
+
user.populate_otp_secrets!
|
33
|
+
|
34
|
+
assert user.time_based_otp.is_a? ROTP::TOTP
|
35
|
+
assert user.recovery_otp.is_a? ROTP::HOTP
|
26
36
|
end
|
27
37
|
|
28
|
-
test "
|
29
|
-
|
30
|
-
|
31
|
-
assert u.otp_enabled
|
32
|
-
otp_auth_secret = u.otp_auth_secret
|
33
|
-
otp_persistence_seed = u.otp_persistence_seed
|
38
|
+
test "clear_otp_fields should clear all otp fields" do
|
39
|
+
user = User.first
|
40
|
+
user.populate_otp_secrets!
|
34
41
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
42
|
+
user.enable_otp!
|
43
|
+
user.generate_otp_challenge!
|
44
|
+
user.update(
|
45
|
+
:otp_failed_attempts => 1,
|
46
|
+
:otp_recovery_counter => 1
|
47
|
+
)
|
48
|
+
|
49
|
+
|
50
|
+
assert user.otp_enabled
|
51
|
+
[:otp_auth_secret, :otp_recovery_secret, :otp_persistence_seed].each do |field|
|
52
|
+
assert_not_nil user.send(field)
|
53
|
+
end
|
54
|
+
[:otp_failed_attempts, :otp_recovery_counter].each do |field|
|
55
|
+
assert_not user.send(field) == 0
|
56
|
+
end
|
57
|
+
|
58
|
+
user.clear_otp_fields!
|
59
|
+
[:otp_auth_secret, :otp_recovery_secret, :otp_persistence_seed].each do |field|
|
60
|
+
assert_nil user.send(field)
|
61
|
+
end
|
62
|
+
[:otp_failed_attempts, :otp_recovery_counter].each do |field|
|
63
|
+
assert user.send(field) == 0
|
64
|
+
end
|
39
65
|
end
|
40
66
|
|
41
67
|
test "reset_otp_persistence should generate new persistence_seed but NOT change the otp_auth_secret" do
|
42
68
|
u = User.first
|
43
|
-
u.
|
69
|
+
u.populate_otp_secrets!
|
70
|
+
u.enable_otp!
|
44
71
|
assert u.otp_enabled
|
72
|
+
|
45
73
|
otp_auth_secret = u.otp_auth_secret
|
46
74
|
otp_persistence_seed = u.otp_persistence_seed
|
47
75
|
|
@@ -53,7 +81,8 @@ class OtpAuthenticatableTest < ActiveSupport::TestCase
|
|
53
81
|
|
54
82
|
test "generating a challenge, should retrieve the user later" do
|
55
83
|
u = User.first
|
56
|
-
u.
|
84
|
+
u.populate_otp_secrets!
|
85
|
+
u.enable_otp!
|
57
86
|
challenge = u.generate_otp_challenge!
|
58
87
|
|
59
88
|
w = User.find_valid_otp_challenge(challenge)
|
@@ -63,7 +92,8 @@ class OtpAuthenticatableTest < ActiveSupport::TestCase
|
|
63
92
|
|
64
93
|
test "expiring the challenge, should retrieve nothing" do
|
65
94
|
u = User.first
|
66
|
-
u.
|
95
|
+
u.populate_otp_secrets!
|
96
|
+
u.enable_otp!
|
67
97
|
challenge = u.generate_otp_challenge!(1.second)
|
68
98
|
sleep(2)
|
69
99
|
|
@@ -73,7 +103,8 @@ class OtpAuthenticatableTest < ActiveSupport::TestCase
|
|
73
103
|
|
74
104
|
test "expired challenges should not be valid" do
|
75
105
|
u = User.first
|
76
|
-
u.
|
106
|
+
u.populate_otp_secrets!
|
107
|
+
u.enable_otp!
|
77
108
|
challenge = u.generate_otp_challenge!(1.second)
|
78
109
|
sleep(2)
|
79
110
|
assert_equal false, u.otp_challenge_valid?
|
@@ -81,14 +112,16 @@ class OtpAuthenticatableTest < ActiveSupport::TestCase
|
|
81
112
|
|
82
113
|
test "null otp challenge" do
|
83
114
|
u = User.first
|
84
|
-
u.
|
115
|
+
u.populate_otp_secrets!
|
116
|
+
u.enable_otp!
|
85
117
|
assert_equal false, u.validate_otp_token("")
|
86
118
|
assert_equal false, u.validate_otp_token(nil)
|
87
119
|
end
|
88
120
|
|
89
121
|
test "generated otp token should be valid for the user" do
|
90
122
|
u = User.first
|
91
|
-
u.
|
123
|
+
u.populate_otp_secrets!
|
124
|
+
u.enable_otp!
|
92
125
|
|
93
126
|
secret = u.otp_auth_secret
|
94
127
|
token = ROTP::TOTP.new(secret).now
|
@@ -98,7 +131,8 @@ class OtpAuthenticatableTest < ActiveSupport::TestCase
|
|
98
131
|
|
99
132
|
test "generated otp token, out of drift window, should be NOT valid for the user" do
|
100
133
|
u = User.first
|
101
|
-
u.
|
134
|
+
u.populate_otp_secrets!
|
135
|
+
u.enable_otp!
|
102
136
|
|
103
137
|
secret = u.otp_auth_secret
|
104
138
|
|
@@ -110,7 +144,8 @@ class OtpAuthenticatableTest < ActiveSupport::TestCase
|
|
110
144
|
|
111
145
|
test "recovery secrets should be valid, and valid only once" do
|
112
146
|
u = User.first
|
113
|
-
u.
|
147
|
+
u.populate_otp_secrets!
|
148
|
+
u.enable_otp!
|
114
149
|
recovery = u.next_otp_recovery_tokens
|
115
150
|
|
116
151
|
assert u.valid_otp_recovery_token? recovery.fetch(0)
|
data/test/test_helper.rb
CHANGED
@@ -6,86 +6,16 @@ require "dummy/config/environment"
|
|
6
6
|
require "orm/#{DEVISE_ORM}"
|
7
7
|
require "rails/test_help"
|
8
8
|
require "capybara/rails"
|
9
|
-
require "capybara/cuprite"
|
10
9
|
require "minitest/reporters"
|
11
10
|
|
12
|
-
|
11
|
+
Minitest::Reporters.use!
|
13
12
|
|
14
13
|
# I18n.load_path << File.expand_path("../support/locale/en.yml", __FILE__) if DEVISE_ORM == :mongoid
|
15
14
|
|
16
15
|
# ActiveSupport::Deprecation.silenced = true
|
17
16
|
|
18
|
-
# Use a module to not pollute the global namespace
|
19
|
-
module CapybaraHelper
|
20
|
-
def self.register_driver(driver_name, args = [])
|
21
|
-
opts = {headless: true, js_errors: true, window_size: [1920, 1200], browser_options: {}}
|
22
|
-
args.each do |arg|
|
23
|
-
opts[:browser_options][arg] = nil
|
24
|
-
end
|
25
|
-
|
26
|
-
Capybara.register_driver(driver_name) do |app|
|
27
|
-
Capybara::Cuprite::Driver.new(app, opts)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Register our own custom drivers
|
33
|
-
CapybaraHelper.register_driver(:headless_chrome, %w[disable-gpu no-sandbox disable-dev-shm-usage])
|
34
|
-
|
35
|
-
# Configure Capybara JS driver
|
36
|
-
Capybara.current_driver = :headless_chrome
|
37
|
-
Capybara.javascript_driver = :headless_chrome
|
38
|
-
|
39
|
-
# Configure Capybara server
|
40
|
-
Capybara.run_server = true
|
41
|
-
Capybara.server = :puma, {Silent: true}
|
42
|
-
|
43
17
|
class ActionDispatch::IntegrationTest
|
44
18
|
include Capybara::DSL
|
45
|
-
|
46
|
-
# What capybara calls a "page" in its DSL is actually a Capybara::Session
|
47
|
-
# and doesn't know about the *command* method that allows us to play with
|
48
|
-
# the Chrome API.
|
49
|
-
# See: https://rubydoc.info/github/jnicklas/capybara/master/Capybara/Session
|
50
|
-
#
|
51
|
-
# To enable downloads we need to do it on the browser's page object, so fetch it
|
52
|
-
# from this long method chain.
|
53
|
-
# See: https://github.com/rubycdp/ferrum/blob/master/lib/ferrum/page.rb
|
54
|
-
def enable_chrome_headless_downloads(session, directory)
|
55
|
-
page = session.driver.browser.page
|
56
|
-
page.command("Page.setDownloadBehavior", behavior: "allow", downloadPath: directory)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
# From https://collectiveidea.com/blog/archives/2012/01/27/testing-file-downloads-with-capybara-and-chromedriver
|
61
|
-
module DownloadHelper
|
62
|
-
extend self
|
63
|
-
|
64
|
-
TIMEOUT = 10
|
65
|
-
|
66
|
-
def downloads
|
67
|
-
Dir["/tmp/devise-otp/*"]
|
68
|
-
end
|
69
|
-
|
70
|
-
def wait_for_download(count: 1)
|
71
|
-
yield if block_given?
|
72
|
-
|
73
|
-
Timeout.timeout(TIMEOUT) do
|
74
|
-
sleep 0.2 until downloaded?(count)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def downloaded?(count)
|
79
|
-
!downloading? && downloads.size == count
|
80
|
-
end
|
81
|
-
|
82
|
-
def downloading?
|
83
|
-
downloads.grep(/\.crdownload$/).any?
|
84
|
-
end
|
85
|
-
|
86
|
-
def clear_downloads
|
87
|
-
FileUtils.rm_f(downloads)
|
88
|
-
end
|
89
19
|
end
|
90
20
|
|
91
21
|
require "devise-otp"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: devise-otp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lele Forzani
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-06-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -79,20 +79,6 @@ dependencies:
|
|
79
79
|
- - ">="
|
80
80
|
- !ruby/object:Gem::Version
|
81
81
|
version: '0'
|
82
|
-
- !ruby/object:Gem::Dependency
|
83
|
-
name: cuprite
|
84
|
-
requirement: !ruby/object:Gem::Requirement
|
85
|
-
requirements:
|
86
|
-
- - ">="
|
87
|
-
- !ruby/object:Gem::Version
|
88
|
-
version: '0'
|
89
|
-
type: :development
|
90
|
-
prerelease: false
|
91
|
-
version_requirements: !ruby/object:Gem::Requirement
|
92
|
-
requirements:
|
93
|
-
- - ">="
|
94
|
-
- !ruby/object:Gem::Version
|
95
|
-
version: '0'
|
96
82
|
- !ruby/object:Gem::Dependency
|
97
83
|
name: minitest-reporters
|
98
84
|
requirement: !ruby/object:Gem::Requirement
|
@@ -167,16 +153,16 @@ dependencies:
|
|
167
153
|
name: sqlite3
|
168
154
|
requirement: !ruby/object:Gem::Requirement
|
169
155
|
requirements:
|
170
|
-
- - "
|
156
|
+
- - "~>"
|
171
157
|
- !ruby/object:Gem::Version
|
172
|
-
version: '
|
158
|
+
version: '1.4'
|
173
159
|
type: :development
|
174
160
|
prerelease: false
|
175
161
|
version_requirements: !ruby/object:Gem::Requirement
|
176
162
|
requirements:
|
177
|
-
- - "
|
163
|
+
- - "~>"
|
178
164
|
- !ruby/object:Gem::Version
|
179
|
-
version: '
|
165
|
+
version: '1.4'
|
180
166
|
- !ruby/object:Gem::Dependency
|
181
167
|
name: standardrb
|
182
168
|
requirement: !ruby/object:Gem::Requirement
|
@@ -214,6 +200,7 @@ files:
|
|
214
200
|
- app/views/devise/otp_credentials/show.html.erb
|
215
201
|
- app/views/devise/otp_tokens/_token_secret.html.erb
|
216
202
|
- app/views/devise/otp_tokens/_trusted_devices.html.erb
|
203
|
+
- app/views/devise/otp_tokens/edit.html.erb
|
217
204
|
- app/views/devise/otp_tokens/recovery.html.erb
|
218
205
|
- app/views/devise/otp_tokens/recovery_codes.text.erb
|
219
206
|
- app/views/devise/otp_tokens/show.html.erb
|
@@ -222,11 +209,12 @@ files:
|
|
222
209
|
- docs/QR_CODES.md
|
223
210
|
- lib/devise-otp.rb
|
224
211
|
- lib/devise-otp/version.rb
|
212
|
+
- lib/devise/strategies/database_authenticatable.rb
|
225
213
|
- lib/devise_otp_authenticatable/controllers/helpers.rb
|
214
|
+
- lib/devise_otp_authenticatable/controllers/public_helpers.rb
|
226
215
|
- lib/devise_otp_authenticatable/controllers/url_helpers.rb
|
227
216
|
- lib/devise_otp_authenticatable/engine.rb
|
228
|
-
- lib/devise_otp_authenticatable/hooks.rb
|
229
|
-
- lib/devise_otp_authenticatable/hooks/sessions.rb
|
217
|
+
- lib/devise_otp_authenticatable/hooks/refreshable.rb
|
230
218
|
- lib/devise_otp_authenticatable/models/otp_authenticatable.rb
|
231
219
|
- lib/devise_otp_authenticatable/routes.rb
|
232
220
|
- lib/generators/active_record/devise_otp_generator.rb
|
@@ -239,13 +227,22 @@ files:
|
|
239
227
|
- test/dummy/app/assets/config/manifest.js
|
240
228
|
- test/dummy/app/assets/javascripts/application.js
|
241
229
|
- test/dummy/app/assets/stylesheets/application.css
|
230
|
+
- test/dummy/app/controllers/admin_posts_controller.rb
|
242
231
|
- test/dummy/app/controllers/application_controller.rb
|
232
|
+
- test/dummy/app/controllers/base_controller.rb
|
243
233
|
- test/dummy/app/controllers/posts_controller.rb
|
244
234
|
- test/dummy/app/helpers/application_helper.rb
|
245
235
|
- test/dummy/app/helpers/posts_helper.rb
|
246
236
|
- test/dummy/app/mailers/.gitkeep
|
237
|
+
- test/dummy/app/models/admin.rb
|
247
238
|
- test/dummy/app/models/post.rb
|
248
239
|
- test/dummy/app/models/user.rb
|
240
|
+
- test/dummy/app/views/admin_posts/_form.html.erb
|
241
|
+
- test/dummy/app/views/admin_posts/edit.html.erb
|
242
|
+
- test/dummy/app/views/admin_posts/index.html.erb
|
243
|
+
- test/dummy/app/views/admin_posts/new.html.erb
|
244
|
+
- test/dummy/app/views/admin_posts/show.html.erb
|
245
|
+
- test/dummy/app/views/base/home.html.erb
|
249
246
|
- test/dummy/app/views/layouts/application.html.erb
|
250
247
|
- test/dummy/app/views/posts/_form.html.erb
|
251
248
|
- test/dummy/app/views/posts/edit.html.erb
|
@@ -273,16 +270,22 @@ files:
|
|
273
270
|
- test/dummy/db/migrate/20130131092406_add_devise_to_users.rb
|
274
271
|
- test/dummy/db/migrate/20130131142320_create_posts.rb
|
275
272
|
- test/dummy/db/migrate/20130131160351_devise_otp_add_to_users.rb
|
273
|
+
- test/dummy/db/migrate/20240604000001_create_admins.rb
|
274
|
+
- test/dummy/db/migrate/20240604000002_add_devise_to_admins.rb
|
275
|
+
- test/dummy/db/migrate/20240604000003_devise_otp_add_to_admins.rb
|
276
276
|
- test/dummy/lib/assets/.gitkeep
|
277
277
|
- test/dummy/public/404.html
|
278
278
|
- test/dummy/public/422.html
|
279
279
|
- test/dummy/public/500.html
|
280
280
|
- test/dummy/public/favicon.ico
|
281
281
|
- test/dummy/script/rails
|
282
|
+
- test/integration/disable_token_test.rb
|
283
|
+
- test/integration/enable_otp_form_test.rb
|
282
284
|
- test/integration/persistence_test.rb
|
283
285
|
- test/integration/refresh_test.rb
|
286
|
+
- test/integration/reset_token_test.rb
|
284
287
|
- test/integration/sign_in_test.rb
|
285
|
-
- test/integration/
|
288
|
+
- test/integration/trackable_test.rb
|
286
289
|
- test/integration_tests_helper.rb
|
287
290
|
- test/model_tests_helper.rb
|
288
291
|
- test/models/otp_authenticatable_test.rb
|