quo_vadis 2.0.0 → 2.0.1
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 +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +0 -3
- data/README.md +4 -5
- data/lib/quo_vadis/version.rb +1 -1
- data/quo_vadis.gemspec +5 -3
- data/test/dummy/README.markdown +1 -0
- data/test/dummy/Rakefile +3 -0
- data/test/dummy/app/controllers/application_controller.rb +2 -0
- data/test/dummy/app/controllers/articles_controller.rb +17 -0
- data/test/dummy/app/controllers/sign_ups_controller.rb +42 -0
- data/test/dummy/app/controllers/users_controller.rb +25 -0
- data/test/dummy/app/models/application_record.rb +3 -0
- data/test/dummy/app/models/article.rb +3 -0
- data/test/dummy/app/models/person.rb +6 -0
- data/test/dummy/app/models/user.rb +6 -0
- data/test/dummy/app/views/articles/also_secret.html.erb +1 -0
- data/test/dummy/app/views/articles/index.html.erb +1 -0
- data/test/dummy/app/views/articles/secret.html.erb +1 -0
- data/test/dummy/app/views/articles/very_secret.html.erb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +46 -0
- data/test/dummy/app/views/quo_vadis/confirmations/edit.html.erb +10 -0
- data/test/dummy/app/views/quo_vadis/confirmations/index.html.erb +5 -0
- data/test/dummy/app/views/quo_vadis/confirmations/new.html.erb +16 -0
- data/test/dummy/app/views/quo_vadis/logs/index.html.erb +28 -0
- data/test/dummy/app/views/quo_vadis/mailer/account_confirmation.text.erb +4 -0
- data/test/dummy/app/views/quo_vadis/mailer/email_change_notification.text.erb +8 -0
- data/test/dummy/app/views/quo_vadis/mailer/identifier_change_notification.text.erb +8 -0
- data/test/dummy/app/views/quo_vadis/mailer/password_change_notification.text.erb +8 -0
- data/test/dummy/app/views/quo_vadis/mailer/password_reset_notification.text.erb +8 -0
- data/test/dummy/app/views/quo_vadis/mailer/recovery_codes_generation_notification.text.erb +8 -0
- data/test/dummy/app/views/quo_vadis/mailer/reset_password.text.erb +4 -0
- data/test/dummy/app/views/quo_vadis/mailer/totp_reuse_notification.text.erb +6 -0
- data/test/dummy/app/views/quo_vadis/mailer/totp_setup_notification.text.erb +8 -0
- data/test/dummy/app/views/quo_vadis/mailer/twofa_deactivated_notification.text.erb +8 -0
- data/test/dummy/app/views/quo_vadis/password_resets/edit.html.erb +25 -0
- data/test/dummy/app/views/quo_vadis/password_resets/index.html.erb +5 -0
- data/test/dummy/app/views/quo_vadis/password_resets/new.html.erb +12 -0
- data/test/dummy/app/views/quo_vadis/passwords/edit.html.erb +30 -0
- data/test/dummy/app/views/quo_vadis/recovery_codes/challenge.html.erb +11 -0
- data/test/dummy/app/views/quo_vadis/recovery_codes/index.html.erb +25 -0
- data/test/dummy/app/views/quo_vadis/sessions/index.html.erb +26 -0
- data/test/dummy/app/views/quo_vadis/sessions/new.html.erb +24 -0
- data/test/dummy/app/views/quo_vadis/totps/challenge.html.erb +11 -0
- data/test/dummy/app/views/quo_vadis/totps/new.html.erb +17 -0
- data/test/dummy/app/views/quo_vadis/twofas/show.html.erb +20 -0
- data/test/dummy/app/views/sign_ups/new.html.erb +37 -0
- data/test/dummy/app/views/sign_ups/show.html.erb +5 -0
- data/test/dummy/app/views/users/new.html.erb +37 -0
- data/test/dummy/config.ru +7 -0
- data/test/dummy/config/application.rb +30 -0
- data/test/dummy/config/boot.rb +4 -0
- data/test/dummy/config/database.yml +10 -0
- data/test/dummy/config/environment.rb +4 -0
- data/test/dummy/config/initializers/quo_vadis.rb +7 -0
- data/test/dummy/config/routes.rb +13 -0
- data/test/dummy/db/migrate/202102121932_create_users.rb +10 -0
- data/test/dummy/db/migrate/202102121935_create_people.rb +10 -0
- data/test/dummy/db/schema.rb +92 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/fixtures/quo_vadis/mailer/account_confirmation.text +4 -0
- data/test/fixtures/quo_vadis/mailer/email_change_notification.text +8 -0
- data/test/fixtures/quo_vadis/mailer/identifier_change_notification.text +8 -0
- data/test/fixtures/quo_vadis/mailer/password_change_notification.text +8 -0
- data/test/fixtures/quo_vadis/mailer/password_reset_notification.text +8 -0
- data/test/fixtures/quo_vadis/mailer/recovery_codes_generation_notification.text +8 -0
- data/test/fixtures/quo_vadis/mailer/reset_password.text +4 -0
- data/test/fixtures/quo_vadis/mailer/totp_reuse_notification.text +6 -0
- data/test/fixtures/quo_vadis/mailer/totp_setup_notification.text +8 -0
- data/test/fixtures/quo_vadis/mailer/twofa_deactivated_notification.text +8 -0
- data/test/integration/account_confirmation_test.rb +112 -0
- data/test/integration/controller_test.rb +280 -0
- data/test/integration/logging_test.rb +235 -0
- data/test/integration/password_change_test.rb +93 -0
- data/test/integration/password_login_test.rb +125 -0
- data/test/integration/password_reset_test.rb +136 -0
- data/test/integration/recovery_codes_test.rb +48 -0
- data/test/integration/sessions_test.rb +86 -0
- data/test/integration/sign_up_test.rb +35 -0
- data/test/integration/totps_test.rb +96 -0
- data/test/integration/twofa_test.rb +82 -0
- data/test/mailers/mailer_test.rb +200 -0
- data/test/models/account_test.rb +34 -0
- data/test/models/crypt_test.rb +22 -0
- data/test/models/log_test.rb +16 -0
- data/test/models/mask_ip_test.rb +27 -0
- data/test/models/model_test.rb +66 -0
- data/test/models/password_test.rb +163 -0
- data/test/models/recovery_code_test.rb +54 -0
- data/test/models/session_test.rb +67 -0
- data/test/models/token_test.rb +70 -0
- data/test/models/totp_test.rb +68 -0
- data/test/quo_vadis_test.rb +43 -0
- data/test/test_helper.rb +58 -0
- metadata +119 -4
- data/Gemfile.lock +0 -178
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class PasswordResetTest < IntegrationTest
|
|
4
|
+
|
|
5
|
+
setup do
|
|
6
|
+
@user = User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
test 'new password reset' do
|
|
11
|
+
get quo_vadis.new_password_reset_path
|
|
12
|
+
assert_response :success
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
test 'unknown identifier' do
|
|
17
|
+
post quo_vadis.password_resets_path(email: 'foo@example.com')
|
|
18
|
+
assert_response :success
|
|
19
|
+
assert_equal 'A link to change your password has been emailed to you.', flash[:notice]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
test 'known identifier' do
|
|
24
|
+
assert_emails 1 do
|
|
25
|
+
post quo_vadis.password_resets_path(email: 'bob@example.com')
|
|
26
|
+
end
|
|
27
|
+
assert_redirected_to quo_vadis.password_resets_path
|
|
28
|
+
assert_equal 'A link to change your password has been emailed to you.', flash[:notice]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
test 'click link in email' do
|
|
33
|
+
assert_emails 1 do
|
|
34
|
+
post quo_vadis.password_resets_path(email: 'bob@example.com')
|
|
35
|
+
end
|
|
36
|
+
get extract_url_from_email
|
|
37
|
+
assert_response :success
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
test 'expired link' do
|
|
42
|
+
assert_emails 1 do
|
|
43
|
+
post quo_vadis.password_resets_path(email: 'bob@example.com')
|
|
44
|
+
end
|
|
45
|
+
travel QuoVadis.password_reset_token_lifetime + 1.minute
|
|
46
|
+
get extract_url_from_email
|
|
47
|
+
assert_redirected_to quo_vadis.new_password_reset_path
|
|
48
|
+
assert_equal 'Either the link has expired or you have already reset your password.', flash[:alert]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
test 'link cannot be reused' do
|
|
53
|
+
assert_emails 1 do
|
|
54
|
+
post quo_vadis.password_resets_path(email: 'bob@example.com')
|
|
55
|
+
end
|
|
56
|
+
put quo_vadis.password_reset_path(extract_token_from_email, password: 'xxxxxxxxxxxx', password_confirmation: 'xxxxxxxxxxxx')
|
|
57
|
+
assert controller.logged_in?
|
|
58
|
+
|
|
59
|
+
get quo_vadis.edit_password_reset_url(extract_token_from_email)
|
|
60
|
+
assert_redirected_to quo_vadis.new_password_reset_path
|
|
61
|
+
assert_equal 'Either the link has expired or you have already reset your password.', flash[:alert]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
test 'new password invalid' do
|
|
66
|
+
digest = @user.qv_account.password.password_digest
|
|
67
|
+
|
|
68
|
+
assert_emails 1 do
|
|
69
|
+
post quo_vadis.password_resets_path(email: 'bob@example.com')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
assert_no_difference 'QuoVadis::Session.count' do
|
|
73
|
+
put quo_vadis.password_reset_path(extract_token_from_email, password: '', password_confirmation: '')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
assert_equal digest, @user.qv_account.password.reload.password_digest
|
|
77
|
+
assert_response :success
|
|
78
|
+
assert_equal quo_vadis.password_reset_path(extract_token_from_email), path
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
test 'new password valid' do
|
|
83
|
+
QuoVadis.two_factor_authentication_mandatory false
|
|
84
|
+
|
|
85
|
+
digest = @user.qv_account.password.password_digest
|
|
86
|
+
|
|
87
|
+
desktop = session_login
|
|
88
|
+
phone = session_login
|
|
89
|
+
|
|
90
|
+
get articles_url
|
|
91
|
+
refute controller.logged_in?
|
|
92
|
+
|
|
93
|
+
assert_emails 1 do
|
|
94
|
+
post quo_vadis.password_resets_path(email: 'bob@example.com')
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
assert_difference 'QuoVadis::Session.count', (- 2 + 1) do
|
|
98
|
+
put quo_vadis.password_reset_path(extract_token_from_email, password: 'xxxxxxxxxxxx', password_confirmation: 'xxxxxxxxxxxx')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
assert controller.logged_in?
|
|
102
|
+
|
|
103
|
+
desktop.get articles_url
|
|
104
|
+
refute desktop.controller.logged_in? # NOTE: flaky; if this fails, re-migrate the database.
|
|
105
|
+
|
|
106
|
+
phone.get articles_url
|
|
107
|
+
refute phone.controller.logged_in?
|
|
108
|
+
|
|
109
|
+
refute_equal digest, @user.qv_account.password.reload.password_digest
|
|
110
|
+
|
|
111
|
+
assert_redirected_to '/articles/secret'
|
|
112
|
+
assert_equal 'Your password has been changed and you are logged in.', flash[:notice]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
def session_login
|
|
119
|
+
open_session do |sess|
|
|
120
|
+
sess.post quo_vadis.login_path(email: 'bob@example.com', password: '123456789abc')
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def extract_url_from_email
|
|
125
|
+
ActionMailer::Base.deliveries.last.decoded[%r{^http://.*$}, 0]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def extract_path_from_email
|
|
129
|
+
extract_url_from_email.sub 'http://www.example.com', ''
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def extract_token_from_email
|
|
133
|
+
extract_url_from_email[%r{/([^/]*)$}, 1]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class RecoveryCodesTest < IntegrationTest
|
|
4
|
+
|
|
5
|
+
setup do
|
|
6
|
+
u = User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
|
7
|
+
u.qv_account.create_totp last_used_at: 1.day.ago
|
|
8
|
+
QuoVadis.two_factor_authentication_mandatory true
|
|
9
|
+
@codes = u.qv_account.generate_recovery_codes
|
|
10
|
+
login
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
test 'use recovery code' do
|
|
15
|
+
get quo_vadis.challenge_recovery_codes_path
|
|
16
|
+
assert_response :success
|
|
17
|
+
|
|
18
|
+
assert_difference 'QuoVadis::Totp.count', -1 do
|
|
19
|
+
assert_difference 'QuoVadis::RecoveryCode.count', -1 do
|
|
20
|
+
assert_session_replaced do
|
|
21
|
+
post quo_vadis.authenticate_recovery_codes_path(code: @codes.first)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
assert_nil User.last.qv_account.totp
|
|
27
|
+
|
|
28
|
+
assert_redirected_to '/articles/secret'
|
|
29
|
+
|
|
30
|
+
# use another recovery code to verify another TOTP-reset doesn't error
|
|
31
|
+
post quo_vadis.authenticate_recovery_codes_path(code: @codes[1])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
test 'generate recovery codes' do
|
|
36
|
+
assert_emails 1 do
|
|
37
|
+
post quo_vadis.generate_recovery_codes_path
|
|
38
|
+
end
|
|
39
|
+
assert_redirected_to quo_vadis.recovery_codes_path
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def login
|
|
46
|
+
post quo_vadis.login_path(email: 'bob@example.com', password: '123456789abc')
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
# sqlite: primary key needs AUTOINCREMENT to not reuse previous values
|
|
4
|
+
# https://www.sqlite.org/faq.html#q1
|
|
5
|
+
# AR should do this by default (but it seems sometimes doesn't)
|
|
6
|
+
|
|
7
|
+
class SessionsTest < IntegrationTest
|
|
8
|
+
|
|
9
|
+
setup do
|
|
10
|
+
User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
|
11
|
+
QuoVadis.session_lifetime :session
|
|
12
|
+
QuoVadis.two_factor_authentication_mandatory false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
test 'sessions require authentication' do
|
|
17
|
+
get quo_vadis.sessions_path
|
|
18
|
+
assert_redirected_to quo_vadis.login_path
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
test "user's sessions are independent" do
|
|
23
|
+
desktop = login
|
|
24
|
+
phone = login
|
|
25
|
+
|
|
26
|
+
refute_equal jar(desktop).encrypted[QuoVadis.cookie_name],
|
|
27
|
+
jar(phone).encrypted[QuoVadis.cookie_name]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
test 'user can logout without logging out another session' do
|
|
32
|
+
desktop = login
|
|
33
|
+
phone = login
|
|
34
|
+
|
|
35
|
+
# logout on phone
|
|
36
|
+
phone.delete quo_vadis.logout_path
|
|
37
|
+
|
|
38
|
+
# assert phone logged out
|
|
39
|
+
phone.assert_response :redirect
|
|
40
|
+
assert_equal 'You have logged out.', phone.flash[:notice]
|
|
41
|
+
refute jar(phone).encrypted[QuoVadis.cookie_name]
|
|
42
|
+
refute phone.controller.logged_in?
|
|
43
|
+
|
|
44
|
+
# assert desktop still logged in
|
|
45
|
+
assert jar(desktop).encrypted[QuoVadis.cookie_name]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
test "user can log out a separate session" do
|
|
50
|
+
desktop = login
|
|
51
|
+
phone = login
|
|
52
|
+
|
|
53
|
+
# on phone, list sessions
|
|
54
|
+
phone.get quo_vadis.sessions_path
|
|
55
|
+
phone.assert_response :success
|
|
56
|
+
phone.assert_select 'td', 'This session'
|
|
57
|
+
phone.assert_select 'td input[type=submit][value="Log out"]', 1
|
|
58
|
+
|
|
59
|
+
# on phone, log out the desktop session
|
|
60
|
+
phone.delete quo_vadis.session_path(QuoVadis::Session.first.id)
|
|
61
|
+
phone.assert_redirected_to quo_vadis.sessions_path
|
|
62
|
+
|
|
63
|
+
# phone is still logged in
|
|
64
|
+
assert_equal 'You have logged out of the other session.', phone.flash[:notice]
|
|
65
|
+
|
|
66
|
+
# desktop is logged out
|
|
67
|
+
desktop.get '/articles'
|
|
68
|
+
refute desktop.controller.logged_in?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# starts a new rails session and logs in
|
|
75
|
+
def login
|
|
76
|
+
open_session do |sess|
|
|
77
|
+
sess.post quo_vadis.login_path(email: 'bob@example.com', password: '123456789abc')
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# logs in in current session
|
|
82
|
+
def plain_login
|
|
83
|
+
post quo_vadis.login_path(email: 'bob@example.com', password: '123456789abc')
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class SignUpTest < IntegrationTest
|
|
4
|
+
|
|
5
|
+
test 'successful sign up' do
|
|
6
|
+
assert_difference 'User.count' do
|
|
7
|
+
post "/users", params: {
|
|
8
|
+
user: {
|
|
9
|
+
name: 'Bob',
|
|
10
|
+
email: 'billy@example.com',
|
|
11
|
+
password: '123456789abc',
|
|
12
|
+
password_confirmation: '123456789abc'
|
|
13
|
+
}
|
|
14
|
+
}, headers: { 'HTTP_USER_AGENT' => 'Blah' }
|
|
15
|
+
assert_redirected_to '/articles'
|
|
16
|
+
follow_redirect!
|
|
17
|
+
assert_select 'p', 'Public Articles'
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
test 'failed sign up' do
|
|
23
|
+
assert_no_difference 'User.count' do
|
|
24
|
+
post "/users", params: {
|
|
25
|
+
user: {
|
|
26
|
+
name: 'Bob',
|
|
27
|
+
email: 'billy@example.com',
|
|
28
|
+
password: 'x'
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
assert_response :success
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class TotpsTest < IntegrationTest
|
|
4
|
+
|
|
5
|
+
setup do
|
|
6
|
+
User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
|
7
|
+
QuoVadis.two_factor_authentication_mandatory true
|
|
8
|
+
login
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
test ':challenge redirects to :new when no second factor setup' do
|
|
13
|
+
get quo_vadis.challenge_totps_path
|
|
14
|
+
assert_redirected_to quo_vadis.new_totp_path
|
|
15
|
+
assert_equal 'Please set up two factor authentication.', flash[:alert]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
test 'set up second factor' do
|
|
20
|
+
get quo_vadis.new_totp_path
|
|
21
|
+
assert_response :success
|
|
22
|
+
|
|
23
|
+
totp = controller.instance_variable_get :@totp
|
|
24
|
+
assert_emails 1 do
|
|
25
|
+
assert_difference 'QuoVadis::RecoveryCode.count', 5 do
|
|
26
|
+
assert_difference 'QuoVadis::Totp.count' do
|
|
27
|
+
post quo_vadis.totps_path(totp: {
|
|
28
|
+
key: totp.key,
|
|
29
|
+
hmac_key: totp.hmac_key,
|
|
30
|
+
otp: ROTP::TOTP.new(totp.key).now
|
|
31
|
+
})
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
assert_redirected_to quo_vadis.recovery_codes_path
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
test 'does not set up second factor when key tampered with' do
|
|
41
|
+
get quo_vadis.new_totp_path
|
|
42
|
+
|
|
43
|
+
totp = controller.instance_variable_get :@totp
|
|
44
|
+
assert_no_difference 'QuoVadis::Totp.count' do
|
|
45
|
+
post quo_vadis.totps_path(totp: {
|
|
46
|
+
key: 'dodgy',
|
|
47
|
+
hmac_key: totp.hmac_key,
|
|
48
|
+
otp: ROTP::TOTP.new('dodgy').now
|
|
49
|
+
})
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
assert_equal 'Sorry, the code was incorrect. Please check your system clock is correct and try again.', flash[:alert]
|
|
53
|
+
assert_redirected_to quo_vadis.new_totp_path
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
test 'authenticate with second factor' do
|
|
58
|
+
totp = User.last.qv_account.create_totp(last_used_at: 1.minute.ago)
|
|
59
|
+
|
|
60
|
+
get quo_vadis.challenge_totps_path
|
|
61
|
+
assert_response :success
|
|
62
|
+
|
|
63
|
+
assert_session_replaced do
|
|
64
|
+
post quo_vadis.authenticate_totps_path(totp: ROTP::TOTP.new(totp.key).now)
|
|
65
|
+
end
|
|
66
|
+
assert QuoVadis::Session.last.second_factor_authenticated?
|
|
67
|
+
assert_equal 'Welcome back!', flash[:notice]
|
|
68
|
+
assert_redirected_to '/articles/secret'
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
test 'failed authentication with second factor' do
|
|
73
|
+
User.last.qv_account.create_totp(last_used_at: 1.minute.ago)
|
|
74
|
+
|
|
75
|
+
post quo_vadis.authenticate_totps_path(totp: '123456')
|
|
76
|
+
refute QuoVadis::Session.last.second_factor_authenticated?
|
|
77
|
+
assert_response :success
|
|
78
|
+
assert_equal 'Sorry, the code was incorrect. Please check your system clock is correct and try again.', flash[:alert]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
test '2fa code reused' do
|
|
83
|
+
totp = User.last.qv_account.create_totp(last_used_at: 1.minute.ago)
|
|
84
|
+
post quo_vadis.authenticate_totps_path(totp: ROTP::TOTP.new(totp.key).now)
|
|
85
|
+
assert_emails 1 do
|
|
86
|
+
post quo_vadis.authenticate_totps_path(totp: ROTP::TOTP.new(totp.key).now)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def login
|
|
94
|
+
post quo_vadis.login_path(email: 'bob@example.com', password: '123456789abc')
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class TwofaTest < IntegrationTest
|
|
4
|
+
|
|
5
|
+
setup do
|
|
6
|
+
u = User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
|
7
|
+
@account = u.qv_account
|
|
8
|
+
QuoVadis.two_factor_authentication_mandatory true
|
|
9
|
+
# @codes = u.qv_account.generate_recovery_codes
|
|
10
|
+
login
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
test 'no totp, no recovery codes' do
|
|
15
|
+
get quo_vadis.twofa_path
|
|
16
|
+
assert_response :success
|
|
17
|
+
assert_select "a[href=?]", quo_vadis.new_totp_path
|
|
18
|
+
assert_select "a[href=?]", quo_vadis.recovery_codes_path
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
test 'totp, no recovery codes' do
|
|
23
|
+
setup_totp
|
|
24
|
+
get quo_vadis.twofa_path
|
|
25
|
+
assert_select 'form[action=?]', quo_vadis.twofa_path do
|
|
26
|
+
assert_select 'input[value=delete]'
|
|
27
|
+
end
|
|
28
|
+
assert_select "a[href=?]", quo_vadis.recovery_codes_path
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
test 'no totp, recovery codes' do
|
|
33
|
+
setup_recovery_codes
|
|
34
|
+
get quo_vadis.twofa_path
|
|
35
|
+
assert_select "a[href=?]", quo_vadis.new_totp_path
|
|
36
|
+
assert_select "a[href=?]", quo_vadis.recovery_codes_path
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
test 'totp, recovery codes' do
|
|
41
|
+
setup_totp
|
|
42
|
+
setup_recovery_codes
|
|
43
|
+
get quo_vadis.twofa_path
|
|
44
|
+
assert_select 'form[action=?]', quo_vadis.twofa_path do
|
|
45
|
+
assert_select 'input[value=delete]'
|
|
46
|
+
end
|
|
47
|
+
assert_select "a[href=?]", quo_vadis.recovery_codes_path
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
test 'deactivate' do
|
|
52
|
+
setup_totp
|
|
53
|
+
setup_recovery_codes
|
|
54
|
+
|
|
55
|
+
assert_emails 1 do
|
|
56
|
+
delete quo_vadis.twofa_path
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# The flash only seems to be set before the redirect. No idea why.
|
|
60
|
+
assert_equal 'You have invalidated your 2FA credentials and recovery codes.', flash[:notice]
|
|
61
|
+
@account.reload
|
|
62
|
+
assert_nil @account.totp
|
|
63
|
+
assert_empty @account.recovery_codes
|
|
64
|
+
@account.sessions.each { |s| refute s.second_factor_authenticated? }
|
|
65
|
+
assert_redirected_to quo_vadis.twofa_path
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def login
|
|
72
|
+
post quo_vadis.login_path(email: 'bob@example.com', password: '123456789abc')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def setup_totp
|
|
76
|
+
@account.create_totp last_used_at: 1.day.ago
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def setup_recovery_codes
|
|
80
|
+
@account.generate_recovery_codes
|
|
81
|
+
end
|
|
82
|
+
end
|